Show Menu.cpp syntax highlighted
/* GG is a GUI for SDL and OpenGL.
Copyright (C) 2003 T. Zachary Laine
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public License
as published by the Free Software Foundation; either version 2.1
of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free
Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
02111-1307 USA
If you do not wish to comply with the terms of the LGPL please
contact the author as other terms are available for a fee.
Zach Laine
whatwasthataddress@hotmail.com */
#include <GG/Menu.h>
#include <GG/GUI.h>
#include <GG/DrawUtil.h>
#include <GG/StyleFactory.h>
#include <GG/TextControl.h>
#include <GG/WndEditor.h>
#include <GG/WndEvent.h>
using namespace GG;
namespace {
const int BORDER_THICKNESS = 1; // thickness with which to draw menu borders
const int MENU_SEPARATION = 10; // distance between menu texts in a MenuBar, in pixels
}
struct GG::SetFontAction : AttributeChangedAction<boost::shared_ptr<Font> >
{
SetFontAction(MenuBar* menu_bar) : m_menu_bar(menu_bar) {}
void operator()(const boost::shared_ptr<Font>&) {m_menu_bar->AdjustLayout(true);}
private:
MenuBar* m_menu_bar;
};
struct GG::SetTextColorAction : AttributeChangedAction<Clr>
{
SetTextColorAction(MenuBar* menu_bar) : m_menu_bar(menu_bar) {}
void operator()(const Clr&) {m_menu_bar->AdjustLayout(true);}
private:
MenuBar* m_menu_bar;
};
////////////////////////////////////////////////
// GG::MenuItem
////////////////////////////////////////////////
MenuItem::MenuItem() :
SelectedIDSignal(new SelectedIDSignalType()),
SelectedSignal(new SelectedSignalType()),
item_ID(0),
disabled(false),
checked(false)
{}
MenuItem::MenuItem(const std::string& str, int id, bool disable, bool check) :
SelectedIDSignal(new SelectedIDSignalType()),
SelectedSignal(new SelectedSignalType()),
label(str),
item_ID(id),
disabled(disable),
checked(check)
{}
MenuItem::MenuItem(const std::string& str, int id, bool disable, bool check, const SelectedIDSlotType& slot) :
SelectedIDSignal(new SelectedIDSignalType()),
SelectedSignal(new SelectedSignalType()),
label(str),
item_ID(id),
disabled(disable),
checked(check)
{
SelectedIDSignal->connect(slot);
}
MenuItem::MenuItem(const std::string& str, int id, bool disable, bool check, const SelectedSlotType& slot) :
SelectedIDSignal(new SelectedIDSignalType()),
SelectedSignal(new SelectedSignalType()),
label(str),
item_ID(id),
disabled(disable),
checked(check)
{
SelectedSignal->connect(slot);
}
MenuItem::~MenuItem()
{}
////////////////////////////////////////////////
// GG::MenuBar
////////////////////////////////////////////////
MenuBar::MenuBar() :
Control(),
m_caret(-1)
{}
MenuBar::MenuBar(int x, int y, int w, const boost::shared_ptr<Font>& font, Clr text_color/* = CLR_WHITE*/,
Clr color/* = CLR_BLACK*/, Clr interior/* = CLR_SHADOW*/) :
Control(x, y, w, font->Lineskip()),
m_font(font),
m_border_color(color),
m_int_color(interior),
m_text_color(text_color),
m_sel_text_color(text_color),
m_caret(-1)
{
// use opaque interior color as hilite color
interior.a = 255;
m_hilite_color = interior;
AdjustLayout();
}
MenuBar::MenuBar(int x, int y, int w, const boost::shared_ptr<Font>& font, const MenuItem& m,
Clr text_color/* = CLR_WHITE*/, Clr color/* = CLR_BLACK*/, Clr interior/* = CLR_SHADOW*/) :
Control(x, y, w, font->Lineskip()),
m_font(font),
m_border_color(color),
m_int_color(interior),
m_text_color(text_color),
m_sel_text_color(text_color),
m_menu_data(m),
m_caret(-1)
{
// use opaque interior color as hilite color
interior.a = 255;
m_hilite_color = interior;
AdjustLayout();
}
Pt MenuBar::MinUsableSize() const
{
Pt retval;
for (unsigned int i = 0; i < m_menu_labels.size(); ++i) {
Pt min_size = m_menu_labels[i]->MinUsableSize();
retval.y = std::max(retval.y, min_size.y);
retval.x += min_size.x;
}
return retval;
}
const MenuItem& MenuBar::AllMenus() const
{
return m_menu_data;
}
bool MenuBar::ContainsMenu(const std::string& str) const
{
bool retval = false;
for (std::vector<MenuItem>::const_iterator it = m_menu_data.next_level.begin(); it != m_menu_data.next_level.end(); ++it) {
if (it->label == str) {
retval = true;
break;
}
}
return retval;
}
int MenuBar::NumMenus() const
{
return m_menu_data.next_level.size();
}
const MenuItem& MenuBar::GetMenu(const std::string& str) const
{
std::vector<MenuItem>::const_iterator it = m_menu_data.next_level.begin();
for (; it != m_menu_data.next_level.end(); ++it) {
if (it->label == str)
break;
}
return *it;
}
const MenuItem& MenuBar::GetMenu(int n) const
{
return *(m_menu_data.next_level.begin() + n);
}
Clr MenuBar::BorderColor() const
{
return m_border_color;
}
Clr MenuBar::InteriorColor() const
{
return m_int_color;
}
Clr MenuBar::TextColor() const
{
return m_text_color;
}
Clr MenuBar::HiliteColor() const
{
return m_hilite_color;
}
Clr MenuBar::SelectedTextColor() const
{
return m_sel_text_color;
}
void MenuBar::Render()
{
Pt ul = UpperLeft();
Pt lr = LowerRight();
FlatRectangle(ul.x, ul.y, lr.x, lr.y, m_int_color, m_border_color, BORDER_THICKNESS);
// paint caret, if any
if (m_caret != -1) {
Pt caret_ul = m_menu_labels[m_caret]->UpperLeft() + Pt((!m_caret ? BORDER_THICKNESS : 0), BORDER_THICKNESS);
Pt caret_lr = m_menu_labels[m_caret]->LowerRight() - Pt((m_caret == static_cast<int>(m_menu_labels.size()) - 1 ? BORDER_THICKNESS : 0), BORDER_THICKNESS);
FlatRectangle(caret_ul.x, caret_ul.y, caret_lr.x, caret_lr.y, m_hilite_color, CLR_ZERO, 0);
}
}
void MenuBar::LButtonDown(const Pt& pt, Flags<ModKey> mod_keys)
{
if (!Disabled()) {
for (int i = 0; i < static_cast<int>(m_menu_labels.size()); ++i) {
if (m_menu_labels[i]->InWindow(pt)) {
m_caret = -1;
BrowsedSignal(0);
// since a MenuBar is usually modeless, but becomes modal when a menu is opened, we do something kludgey here:
// we launch a PopupMenu whenever a menu item is selected, then use the ID returned from it to find the
// menu item that was chosen; we then emit a signal from that item
if (m_menu_data.next_level[i].next_level.empty()) {
(*m_menu_data.next_level[i].SelectedIDSignal)(m_menu_data.next_level[i].item_ID);
(*m_menu_data.next_level[i].SelectedSignal)();
} else {
MenuItem popup_data;
PopupMenu menu(m_menu_labels[i]->UpperLeft().x, m_menu_labels[i]->LowerRight().y, m_font, m_menu_data.next_level[i], m_text_color, m_border_color, m_int_color);
menu.SetHiliteColor(m_hilite_color);
menu.SetSelectedTextColor(m_sel_text_color);
Connect(menu.BrowsedSignal, &MenuBar::BrowsedSlot, this);
menu.Run();
}
}
}
}
}
void MenuBar::MouseHere(const Pt& pt, Flags<ModKey> mod_keys)
{
if (!Disabled()) {
m_caret = -1;
for (unsigned int i = 0; i < m_menu_data.next_level.size(); ++i) {
if (m_menu_labels[i]->InWindow(pt)) {
m_caret = i;
break;
}
}
}
}
void MenuBar::MouseLeave()
{
m_caret = -1;
}
void MenuBar::SizeMove(const Pt& ul, const Pt& lr)
{
Wnd::SizeMove(ul, lr);
AdjustLayout();
}
MenuItem& MenuBar::AllMenus()
{
return m_menu_data;
}
MenuItem& MenuBar::GetMenu(const std::string& str)
{
std::vector<MenuItem>::iterator it = m_menu_data.next_level.begin();
for (; it != m_menu_data.next_level.end(); ++it) {
if (it->label == str)
break;
}
return *it;
}
MenuItem& MenuBar::GetMenu(int n)
{
return m_menu_data.next_level[n];
}
void MenuBar::AddMenu(const MenuItem& menu)
{
m_menu_data.next_level.push_back(menu);
AdjustLayout();
}
void MenuBar::SetBorderColor(Clr clr)
{
m_border_color = clr;
}
void MenuBar::SetInteriorColor(Clr clr)
{
m_int_color = clr;
}
void MenuBar::SetTextColor(Clr clr)
{
m_text_color = clr;
}
void MenuBar::SetHiliteColor(Clr clr)
{
m_hilite_color = clr;
}
void MenuBar::SetSelectedTextColor(Clr clr)
{
m_sel_text_color = clr;
}
void MenuBar::DefineAttributes(WndEditor* editor)
{
if (!editor)
return;
Control::DefineAttributes(editor);
editor->Label("MenuBar");
boost::shared_ptr<SetFontAction> set_font_action(new SetFontAction(this));
editor->Attribute<boost::shared_ptr<Font> >("Font", m_font, set_font_action);
editor->Attribute("Border Color", m_border_color);
editor->Attribute("Interior Color", m_int_color);
boost::shared_ptr<SetTextColorAction> set_text_color_action(new SetTextColorAction(this));
editor->Attribute<Clr>("Text Color", m_text_color, set_text_color_action);
editor->Attribute("Highlighting Color", m_hilite_color);
editor->Attribute("Selected Text Color", m_sel_text_color);
// TODO: handle assigning menu items
}
const boost::shared_ptr<Font>& MenuBar::GetFont() const
{
return m_font;
}
const std::vector<TextControl*>& MenuBar::MenuLabels() const
{
return m_menu_labels;
}
int MenuBar::Caret() const
{
return m_caret;
}
void MenuBar::AdjustLayout(bool reset/* = false*/)
{
if (reset) {
DeleteChildren();
m_menu_labels.clear();
}
// create any needed labels
for (unsigned int i = m_menu_labels.size(); i < m_menu_data.next_level.size(); ++i) {
m_menu_labels.push_back(GetStyleFactory()->NewTextControl(0, 0, m_menu_data.next_level[i].label, m_font, m_text_color));
m_menu_labels.back()->Resize(Pt(m_menu_labels.back()->Width() + 2 * MENU_SEPARATION, m_font->Lineskip()));
AttachChild(m_menu_labels.back());
}
// determine rows layout
std::vector<int> menu_rows; // each element is the last + 1 index displayable on that row
int space = Width();
for (unsigned int i = 0; i < m_menu_labels.size(); ++i) {
space -= m_menu_labels[i]->Width();
if (space < 0) { // if this menu's text won't fit in the available space
space = Width();
// if moving this menu to the next row would leave an empty row, leave it here even though it won't quite fit
if (!menu_rows.empty() && menu_rows.back() == static_cast<int>(i) - 1) {
menu_rows.push_back(i + 1);
} else {
menu_rows.push_back(i);
space -= m_menu_labels[i]->Width();
}
}
}
if (menu_rows.empty() || menu_rows.back() < static_cast<int>(m_menu_labels.size()))
menu_rows.push_back(m_menu_labels.size());
// place labels
int label_i = 0;
for (unsigned int row = 0; row < menu_rows.size(); ++row) {
int x = 0;
for (; label_i < menu_rows[row]; ++label_i) {
m_menu_labels[label_i]->MoveTo(Pt(x, row * m_font->Lineskip()));
x += m_menu_labels[label_i]->Width();
}
}
// resize MenuBar if needed
int desired_ht = std::max(size_t(1), menu_rows.size()) * m_font->Lineskip();
if (Height() != desired_ht)
Resize(Pt(Width(), desired_ht));
}
void MenuBar::BrowsedSlot(int n)
{
BrowsedSignal(n);
}
////////////////////////////////////////////////
// GG::PopupMenu
////////////////////////////////////////////////
namespace {
const int HORIZONTAL_MARGIN = 3; // distance to leave between edge of PopupMenu contents and the control's border
}
PopupMenu::PopupMenu(int x, int y, const boost::shared_ptr<Font>& font, const MenuItem& m, Clr text_color/* = CLR_WHITE*/,
Clr color/* = CLR_BLACK*/, Clr interior/* = CLR_SHADOW*/) :
Wnd(0, 0, GUI::GetGUI()->AppWidth() - 1, GUI::GetGUI()->AppHeight() - 1, CLICKABLE | MODAL),
m_font(font),
m_border_color(color),
m_int_color(interior),
m_text_color(text_color),
m_sel_text_color(text_color),
m_menu_data(m),
m_open_levels(),
m_caret(std::vector<int>(1, -1)),
m_origin(Pt(x, y)),
m_item_selected(0)
{
// use opaque interior color as hilite color
interior.a = 255;
m_hilite_color = interior;
m_open_levels.resize(1);
}
Pt PopupMenu::ClientUpperLeft() const
{
return m_origin;
}
int PopupMenu::MenuID() const
{
return (m_item_selected ? m_item_selected->item_ID : 0);
}
Clr PopupMenu::BorderColor() const
{
return m_border_color;
}
Clr PopupMenu::InteriorColor() const
{
return m_int_color;
}
Clr PopupMenu::TextColor() const
{
return m_text_color;
}
Clr PopupMenu::HiliteColor() const
{
return m_hilite_color;
}
Clr PopupMenu::SelectedTextColor() const
{
return m_sel_text_color;
}
void PopupMenu::Render()
{
if (m_menu_data.next_level.size())
{
Pt ul = ClientUpperLeft();
const int INDICATOR_VERTICAL_MARGIN = 3;
const int INDICATOR_HEIGHT = m_font->Lineskip() - 2 * INDICATOR_VERTICAL_MARGIN;
const int CHECK_HEIGHT = INDICATOR_HEIGHT;
const int CHECK_WIDTH = CHECK_HEIGHT;
int next_menu_x_offset = 0;
int next_menu_y_offset = 0;
for (unsigned int i = 0; i < m_caret.size(); ++i) {
bool needs_indicator = false;
// get the correct submenu
MenuItem* menu_ptr = &m_menu_data;
for (unsigned int j = 0; j < i; ++j)
menu_ptr = &menu_ptr->next_level[m_caret[j]];
MenuItem& menu = *menu_ptr;
// determine the total size of the menu, render it, and record its bounding rect
std::string str;
for (unsigned int j = 0; j < menu.next_level.size(); ++j) {
str += menu.next_level[j].label + (static_cast<int>(j) < static_cast<int>(menu.next_level.size()) - 1 ? "\n" : "");
if (menu.next_level[j].next_level.size() || menu.next_level[j].checked)
needs_indicator = true;
}
std::vector<Font::LineData> lines;
Flags<TextFormat> fmt = FORMAT_LEFT | FORMAT_TOP;
Pt menu_sz = m_font->DetermineLines(str, fmt, 0, lines); // get dimensions of text in menu
menu_sz.x += 2 * HORIZONTAL_MARGIN;
if (needs_indicator)
menu_sz.x += CHECK_WIDTH + 2 * HORIZONTAL_MARGIN; // make room for the little arrow
Rect r(ul.x + next_menu_x_offset, ul.y + next_menu_y_offset,
ul.x + next_menu_x_offset + menu_sz.x, ul.y + next_menu_y_offset + menu_sz.y);
if (r.lr.x > GUI::GetGUI()->AppWidth()) {
int offset = r.lr.x - GUI::GetGUI()->AppWidth();
r.ul.x -= offset;
r.lr.x -= offset;
}
if (r.lr.y > GUI::GetGUI()->AppHeight()) {
int offset = r.lr.y - GUI::GetGUI()->AppHeight();
r.ul.y -= offset;
r.lr.y -= offset;
}
next_menu_x_offset = menu_sz.x;
next_menu_y_offset = m_caret[i] * m_font->Lineskip();
FlatRectangle(r.ul.x, r.ul.y, r.lr.x, r.lr.y, m_int_color, m_border_color, BORDER_THICKNESS);
m_open_levels[i] = r;
// paint caret, if any
if (m_caret[i] != -1) {
Rect tmp_r = r;
tmp_r.ul.y += m_caret[i] * m_font->Lineskip();
tmp_r.lr.y = tmp_r.ul.y + m_font->Lineskip() + 3;
tmp_r.ul.x += BORDER_THICKNESS;
tmp_r.lr.x -= BORDER_THICKNESS;
if (m_caret[i] == 0)
tmp_r.ul.y += BORDER_THICKNESS;
if (m_caret[i] == static_cast<int>(menu.next_level.size()) - 1)
tmp_r.lr.y -= BORDER_THICKNESS;
FlatRectangle(tmp_r.ul.x, tmp_r.ul.y, tmp_r.lr.x, tmp_r.lr.y, m_hilite_color, CLR_ZERO, 0);
}
// paint menu text and submenu indicator arrows
Rect line_rect = r;
line_rect.ul.x += HORIZONTAL_MARGIN;
line_rect.lr.x -= HORIZONTAL_MARGIN;
for (unsigned int j = 0; j < menu.next_level.size(); ++j) {
Clr clr = (m_caret[i] == static_cast<int>(j)) ?
(menu.next_level[j].disabled ? DisabledColor(m_sel_text_color) : m_sel_text_color) :
(menu.next_level[j].disabled ? DisabledColor(m_text_color) : m_text_color);
glColor3ub(clr.r, clr.g, clr.b);
m_font->RenderText(line_rect.ul.x, line_rect.ul.y, line_rect.lr.x, line_rect.lr.y, menu.next_level[j].label, fmt);
if (menu.next_level[j].checked) {
FlatCheck(line_rect.lr.x - CHECK_WIDTH - HORIZONTAL_MARGIN, line_rect.ul.y + INDICATOR_VERTICAL_MARGIN,
line_rect.lr.x - HORIZONTAL_MARGIN, line_rect.ul.y + INDICATOR_VERTICAL_MARGIN + CHECK_HEIGHT, clr);
}
if (menu.next_level[j].next_level.size()) {
glDisable(GL_TEXTURE_2D);
glBegin(GL_TRIANGLES);
glVertex2d(line_rect.lr.x - INDICATOR_HEIGHT / 2.0 - HORIZONTAL_MARGIN, line_rect.ul.y + INDICATOR_VERTICAL_MARGIN);
glVertex2d(line_rect.lr.x - INDICATOR_HEIGHT / 2.0 - HORIZONTAL_MARGIN, line_rect.ul.y + m_font->Lineskip() - INDICATOR_VERTICAL_MARGIN);
glVertex2d(line_rect.lr.x - HORIZONTAL_MARGIN, line_rect.ul.y + m_font->Lineskip() / 2.0);
glEnd();
glEnable(GL_TEXTURE_2D);
}
line_rect.ul.y += m_font->Lineskip();
}
}
}
}
void PopupMenu::LButtonUp(const Pt& pt, Flags<ModKey> mod_keys)
{
if (m_caret[0] != -1) {
MenuItem* menu_ptr = &m_menu_data;
for (unsigned int i = 0; i < m_caret.size(); ++i)
menu_ptr = &menu_ptr->next_level[m_caret[i]];
if (!menu_ptr->disabled) {
m_item_selected = menu_ptr;
}
}
BrowsedSignal(0);
m_done = true;
}
void PopupMenu::LClick(const Pt& pt, Flags<ModKey> mod_keys)
{
LButtonUp(pt, mod_keys);
}
void PopupMenu::LDrag(const Pt& pt, const Pt& move, Flags<ModKey> mod_keys)
{
bool cursor_is_in_menu = false;
for (int i = static_cast<int>(m_open_levels.size()) - 1; i >= 0; --i) {
// get the correct submenu
MenuItem* menu_ptr = &m_menu_data;
for (int j = 0; j < i; ++j)
menu_ptr = &menu_ptr->next_level[m_caret[j]];
MenuItem& menu = *menu_ptr;
if (pt.x >= m_open_levels[i].ul.x && pt.x <= m_open_levels[i].lr.x &&
pt.y >= m_open_levels[i].ul.y && pt.y <= m_open_levels[i].lr.y) {
int row_selected = (pt.y - m_open_levels[i].ul.y) / m_font->Lineskip();
if (row_selected == m_caret[i]) {
cursor_is_in_menu = true;
} else if (row_selected >= 0 && row_selected < static_cast<int>(menu.next_level.size())) {
m_caret[i] = row_selected;
m_open_levels.resize(i + 1);
m_caret.resize(i + 1);
if (!menu.next_level[row_selected].disabled && menu.next_level[row_selected].next_level.size()) {
m_caret.push_back(0);
m_open_levels.push_back(Rect());
}
cursor_is_in_menu = true;
}
}
}
if (!cursor_is_in_menu) {
m_open_levels.resize(1);
m_caret.resize(1);
m_caret[0] = -1;
}
int update_ID = 0;
if (m_caret[0] != -1) {
MenuItem* menu_ptr = &m_menu_data;
for (unsigned int i = 0; i < m_caret.size(); ++i)
menu_ptr = &menu_ptr->next_level[m_caret[i]];
update_ID = menu_ptr->item_ID;
}
BrowsedSignal(update_ID);
}
void PopupMenu::RButtonUp(const Pt& pt, Flags<ModKey> mod_keys)
{
LButtonUp(pt, mod_keys);
}
void PopupMenu::RClick(const Pt& pt, Flags<ModKey> mod_keys)
{
LButtonUp(pt, mod_keys);
}
void PopupMenu::MouseHere(const Pt& pt, Flags<ModKey> mod_keys)
{
LDrag(pt, Pt(), mod_keys);
}
int PopupMenu::Run()
{
int retval = Wnd::Run();
if (m_item_selected) {
(*m_item_selected->SelectedIDSignal)(m_item_selected->item_ID);
(*m_item_selected->SelectedSignal)();
}
return retval;
}
void PopupMenu::SetBorderColor(Clr clr)
{
m_border_color = clr;
}
void PopupMenu::SetInteriorColor(Clr clr)
{
m_int_color = clr;
}
void PopupMenu::SetTextColor(Clr clr)
{
m_text_color = clr;
}
void PopupMenu::SetHiliteColor(Clr clr)
{
m_hilite_color = clr;
}
void PopupMenu::SetSelectedTextColor(Clr clr)
{
m_sel_text_color = clr;
}
const boost::shared_ptr<Font>& PopupMenu::GetFont() const
{
return m_font;
}
const MenuItem& PopupMenu::MenuData() const
{
return m_menu_data;
}
const std::vector<Rect>& PopupMenu::OpenLevels() const
{
return m_open_levels;
}
const std::vector<int>& PopupMenu::Caret() const
{
return m_caret;
}
const MenuItem* PopupMenu::ItemSelected() const
{
return m_item_selected;
}
See more files for this project here