/* * pawsmenu.cpp - Author: Ondrej Hurt * * Copyright (C) 2003 Atomic Blue (info@planeshift.it, http://www.atomicblue.org) * * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation (version 2 of the License) * This program 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 General Public License for more details. * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * */ // CS INCLUDES #include // COMMON INCLUDES #include "util/log.h" #include "util/psxmlparser.h" #include "util/localization.h" // PAWS INCLUDES #include "pawsmenu.h" #include "pawsborder.h" #include "pawsmanager.h" #include "pawsmainwidget.h" #define BORDER_SIZE 6 #define ITEM_SPACING 5 #define BUTTON_SIZE 16 #define SUBMENU_ACTION_NAME "submenu" #define SUBMENU_ARROW_NAME "SubmenuArrow" #define SUBMENU_ARROW_SIZE 16 int ParseColor(const csString & str, iGraphics2D *g2d); static csRef FindFirstWidget(iDocument * doc); void DrawRect(iGraphics2D * graphics2D, int xmin, int ymin, int xmax, int ymax, int color) { graphics2D->DrawLine(xmin, ymin, xmax, ymin, color); graphics2D->DrawLine(xmax, ymin, xmax, ymax, color); graphics2D->DrawLine(xmin, ymax, xmax, ymax, color); graphics2D->DrawLine(xmin, ymin, xmin, ymax, color); } static csRef FindFirstWidget(iDocument * doc) { csRef root, topNode, widgetNode; root = doc->GetRoot(); if (root == NULL) { Error1("No root in XML"); return NULL; } topNode = root->GetNode("widget_description"); if (topNode == NULL) { Error1("No in XML"); return NULL; } widgetNode = topNode->GetNode("widget"); if (widgetNode == NULL) { Error1("No in "); return NULL; } return widgetNode; } ////////////////////////////////////////////////////////////////////// // class pawsMenuItem ////////////////////////////////////////////////////////////////////// pawsMenuItem::pawsMenuItem() { checkbox = NULL; image = NULL; label = new pawsTextBox(); AddChild(label); imageEnabled = false; checkboxEnabled = false; graphics2d = PawsManager::GetSingleton().GetGraphics2D(); } bool pawsMenuItem::Load(iDocumentNode * node) { return Setup(node); } bool pawsMenuItem::Setup(iDocumentNode * node) { csRef attr, onImg, offImg; int xmlLabelWidth, xmlSpacing, xmlBorder; csString on, off; attr = node->GetAttribute("image"); if (attr != NULL) { EnableImage(true); SetImage(attr->GetValue()); } attr = node->GetAttribute("label"); if (attr != NULL) { label->SetText(PawsManager::GetSingleton().Translate(attr->GetValue())); label->SetSizeByText(); } attr = node->GetAttribute("colour"); if (attr != NULL) label->SetColour(ParseColor(attr->GetValue(), PawsManager::GetSingleton().GetGraphics2D())); else label->SetColour(0xffffff); attr = node->GetAttribute("checked"); if (attr != NULL) { EnableCheckbox(true); SetCheckboxState((strcmp(attr->GetValue(), "false")? true:false)); onImg = node->GetAttribute("CheckboxOn"); if (onImg != NULL) on = onImg->GetValue(); else on = "Up Arrow"; onImg = node->GetAttribute("CheckboxOff"); if (offImg != NULL) off = onImg->GetValue(); else off = "Down Arrow"; SetCheckboxImages(on, off); } attr = node->GetAttribute("LabelWidth"); if (attr != NULL) xmlLabelWidth = attr->GetValueAsInt(); else xmlLabelWidth = -1; attr = node->GetAttribute("spacing"); if (attr != NULL) xmlSpacing = attr->GetValueAsInt(); else xmlSpacing = 1; attr = node->GetAttribute("border"); if (attr != NULL) xmlBorder = attr->GetValueAsInt(); else xmlBorder = 1; SetSizes(xmlLabelWidth, xmlSpacing, xmlBorder); LoadAction(node); return true; } void pawsMenuItem::LoadAction(iDocumentNode * node) { csRef attr; int paramNum; csString paramName; pawsMenuAction newAction; newAction.name = node->GetAttributeValue("action"); paramNum = 1; while (true) { paramName = "param "; paramName.SetAt(5, paramNum + '0'); attr = node->GetAttribute(paramName); // LOOP EXIT: if (attr == NULL) break; newAction.params.SetLength(paramNum); newAction.params[paramNum-1] = attr->GetValue(); paramNum++; } SetAction(newAction); } void pawsMenuItem::EnableCheckbox(bool enable) { checkboxEnabled = enable; if (enable && (checkbox == NULL)) { checkbox = new pawsButton(); checkbox->SetRelativeFrameSize(BUTTON_SIZE, BUTTON_SIZE); checkbox->Show(); AddChild(checkbox); } else if (!enable && (image != NULL)) { checkbox = NULL; pawsWidget::DeleteChild(checkbox); } SetLayout(); } void pawsMenuItem::EnableImage(bool enable) { imageEnabled = enable; if (enable && (image == NULL)) { image = new pawsWidget(); image->SetRelativeFrameSize(BUTTON_SIZE, BUTTON_SIZE); image->Show(); AddChild(image); } else if (!enable && (image != NULL)) { image = NULL; pawsWidget::DeleteChild(image); } SetLayout(); } void pawsMenuItem::SetCheckboxImages(const csString & on, const csString & off) { if (checkbox != NULL) { checkbox->SetUpImage(off); checkbox->SetDownImage(on); } } void pawsMenuItem::SetCheckboxState(bool checked) { if (checkbox != NULL) checkbox->SetState(checked); } void pawsMenuItem::SetLabel(const csString & newLabel) { label->SetText(newLabel); } void pawsMenuItem::SetImage(const csString & newImage) { assert(image != NULL); image->SetBackground(newImage); } void pawsMenuItem::SetSizes(int labelWidth, int _spacing, int _border) { if (labelWidth == -1) label->SetSizeByText(); else label->SetRelativeFrameSize(labelWidth, label->DefaultFrame().Height()); spacing = _spacing; border = _border; SetLayout(); } void pawsMenuItem::SetLayout() { int maxHeight; int x, y; maxHeight = 0; x = border; y = border; if (imageEnabled) { maxHeight = MAX(maxHeight, image->DefaultFrame().Height()); image->SetRelativeFramePos(x, y); x += image->DefaultFrame().Width() + spacing; } maxHeight = MAX(maxHeight, label->DefaultFrame().Height()); label->SetRelativeFramePos(x, y); x += label->DefaultFrame().Width() + spacing; if (checkboxEnabled) { maxHeight = MAX(maxHeight, checkbox->DefaultFrame().Height()); checkbox->SetRelativeFramePos(x, y); x += checkbox->DefaultFrame().Width(); } x += SUBMENU_ARROW_SIZE; x += border; SetRelativeFrameSize(x, maxHeight + 2*border); } void pawsMenuItem::SetAction(const pawsMenuAction & newAction) { action = newAction; } void pawsMenuItem::Draw() { psPoint mousePos; csRect rect; pawsWidget * widgetUnderMouse; pawsWidget::Draw(); mousePos = PawsManager::GetSingleton().GetMouse()->GetPosition(); rect = screenFrame; rect.xmin = parent->ScreenFrame().xmin + BORDER_SIZE-2; rect.xmax = parent->ScreenFrame().xmax - BORDER_SIZE+2; if (rect.Contains(mousePos.x, mousePos.y)) { /* * The rectangle is drawn only if our menu item is not covered (hidden) by another widget: */ widgetUnderMouse = PawsManager::GetSingleton().GetMainWidget()->WidgetAt(mousePos.x, mousePos.y); if ( (widgetUnderMouse == this->WidgetAt(mousePos.x, mousePos.y)) || (widgetUnderMouse == parent) ) { graphics2D->SetClipRect( 0,0, graphics2D->GetWidth(), graphics2D->GetHeight()); DrawRect(graphics2d, rect.xmin+1, rect.ymin+1, rect.xmax-3, rect.ymax+1, graphics2d->FindRGB(240, 194, 89)); } } } void pawsMenuItem::Invoke() { pawsIMenu * menu; menu = dynamic_cast (parent); assert(menu); menu->DoAction(this); } ////////////////////////////////////////////////////////////////////// // class pawsMenu ////////////////////////////////////////////////////////////////////// pawsMenu::pawsMenu() { parentMenu = NULL; stickyButton = NULL; closeButton = NULL; notifyTarget = NULL; label = NULL; arrowImage = NULL; align = alignLeft; autosize = true; sticky = false; graphics2d = PawsManager::GetSingleton().GetGraphics2D(); } pawsMenu::~pawsMenu() { if (arrowImage != NULL) delete arrowImage; } void pawsMenu::AddItem(pawsIMenuItem * item, pawsIMenuItem * nextItem) { if (nextItem == NULL) items.Push(item); else { for ( size_t x = 0; x < items.Length(); x++ ) { if (nextItem == items[x] ) { if ( x == 0 ) items.Insert( x, item ); else items.Insert( x-1, item ); } } } AddChild(item); item->Show(); if (autosize) Autosize(); SetPositionsOfItems(); } void pawsMenu::SetPositionsOfItems() { csRect rect; int contHeight; int itemX, itemY; contHeight = GetContentHeight(); itemY = label->ScreenFrame().Height() + GetActualHeight(2*BORDER_SIZE + ITEM_SPACING) + (screenFrame.Height() - GetActualHeight(label->DefaultFrame().Height()+2*BORDER_SIZE+2*ITEM_SPACING) - contHeight) / 2; for ( size_t x = 0; x < items.Length(); x++ ) { rect = items[x]->DefaultFrame(); if (align == alignLeft) itemX = GetActualWidth(BORDER_SIZE); else itemX = (defaultFrame.Width() - rect.Width()) / 2; items[x]->SetRelativeFramePos(itemX, itemY); itemY += rect.Height() + GetActualHeight(ITEM_SPACING); } } bool pawsMenu::OnButtonPressed(int button, int keyModifier, pawsWidget* widget) { if ((widget == stickyButton) && !sticky) { sticky = true; stickyButton->SetToggle(false); return true; } else if (widget == closeButton) { DestroyMenu(closeCloseClicked); return true; } return false; } bool pawsMenu::Setup(iDocumentNode * node) { csRef attr; csString colourStr; psImageDescription* imageDesc = PawsManager::GetSingleton().GetTextureManager()->GetImageDescription("Right Arrow"); if (imageDesc == NULL) return false; arrowImage = new pawsImage(PawsManager::GetSingleton().GetObjectRegistry()); arrowImage->Description(imageDesc); stickyButton = new pawsButton(); stickyButton->SetRelativeFrameSize(GetActualWidth(BUTTON_SIZE), GetActualHeight(BUTTON_SIZE)); stickyButton->SetUpImage("stickyoff"); stickyButton->SetDownImage("stickyon"); stickyButton->SetToggle(true); AddChild(stickyButton); closeButton = new pawsButton( ); closeButton->SetToggle(false); closeButton->SetRelativeFrameSize(GetActualWidth(BUTTON_SIZE), GetActualHeight(BUTTON_SIZE)); closeButton->SetUpImage("quit"); AddChild(closeButton); SetButtonPositions(); label = new pawsTextBox(); label->SetRelativeFramePos(GetActualWidth(BORDER_SIZE), GetActualHeight(BORDER_SIZE)); label->SetText(PawsManager::GetSingleton().Translate(node->GetAttributeValue("label"))); label->SetSizeByText(); label->Show(); AddChild(label); attr = node->GetAttribute("align"); if (attr != NULL) { if (strcmp(attr->GetValue(), "center") == 0) align = alignCenter; else align = alignLeft; } else align = alignLeft; attr = node->GetAttribute("autosize"); if (attr != NULL) autosize = (strcmp(attr->GetValue(), "true") == 0); else autosize = true; attr = node->GetAttribute("colour"); if (attr != NULL) colourStr = attr->GetValue(); else colourStr = "00ffff"; label->SetColour(ParseColor(colourStr, PawsManager::GetSingleton().GetGraphics2D())); return true; } bool pawsMenu::PostSetup() { pawsIMenuItem * childAsItem; for ( size_t i = 0; i < pawsWidget::children.Length(); i++ ) { childAsItem = dynamic_cast (pawsWidget::children[i]); if (childAsItem != NULL) items.Push(childAsItem); } if (autosize) Autosize(); SetPositionsOfItems(); return true; } int pawsMenu::GetContentWidth() { int contWidth = 0; for ( size_t x = 0; x < items.Length(); x++ ) { contWidth = MAX(contWidth, items[x]->DefaultFrame().Width()); } return contWidth; } int pawsMenu::GetContentHeight() { int contHeight = 0; for ( size_t x = 0; x < items.Length(); x++ ) { if (x > 0) contHeight += GetActualHeight(ITEM_SPACING); contHeight += items[x]->DefaultFrame().Height(); } return contHeight; } void pawsMenu::Autosize() { SetRelativeFrameSize ( MAX( GetContentWidth() + GetActualWidth(2*BORDER_SIZE), label->DefaultFrame().Width() + GetActualWidth(2*BUTTON_SIZE + 5*BORDER_SIZE) ), GetContentHeight() + GetActualHeight(label->DefaultFrame().Height() + 2*BORDER_SIZE + 2*ITEM_SPACING) ); SetButtonPositions(); } void pawsMenu::SetButtonPositions() { stickyButton -> SetRelativeFramePos(screenFrame.Width() - GetActualWidth(BORDER_SIZE*2 + BUTTON_SIZE*2), GetActualHeight(BORDER_SIZE)); closeButton -> SetRelativeFramePos(screenFrame.Width() - GetActualWidth(BORDER_SIZE + BUTTON_SIZE), GetActualHeight(BORDER_SIZE)); } void pawsMenu::OnParentMenuDestroyed(pawsMenuClose reason) { parentMenu = NULL; if (!sticky) DestroyMenu(closeParentClosed); } bool pawsMenu::HasSubmenus() { return ( submenus.Length() != 0 ); } void pawsMenu::OnChildMenuDestroyed(pawsIMenu * child, pawsMenuClose reason) { for ( size_t x = 0; x < submenus.Length(); x++ ) { if ( submenus[x] == child ) { submenus.Delete(submenus[x]); break; } } if (!sticky) switch (reason) { case closeAction: case closeCloseClicked: case closeChildClosed: DestroyMenu(closeChildClosed); break; } } void pawsMenu::DoAction(pawsIMenuItem * item) { pawsMenuAction action; csRef doc; csRef submenuNode; csString fileName; action = item->GetAction(); if (action.name == SUBMENU_ACTION_NAME) { for ( size_t x = 0; x < submenus.Length(); x++ ) { submenus[x]->OnSiblingOpened(); } if (action.params.Length() == 0) { Error1("Submenu invocation menu item must have one or two parameters"); return; } fileName = PawsManager::GetSingleton().GetLocalization()->FindLocalizedFile(action.params[0]); doc = ParseFile(PawsManager::GetSingleton().GetObjectRegistry(), fileName); if (doc == NULL) { Error2("Parsing of file %s failed", fileName.GetData()); return; } if (action.params.Length() == 1) submenuNode = FindFirstWidget(doc); else if (action.params.Length() == 2) submenuNode = FindSubmenuNode(doc->GetRoot(), action.params[1]); if (submenuNode == NULL) { Error1("Submenu XML node not found"); return; } pawsMenu * newMenu = new pawsMenu(); if ( ! newMenu->Load(submenuNode) ) { Error2("Could not load menu %s", fileName.GetData()); delete newMenu; return; } PawsManager::GetSingleton().GetMainWidget()->AddChild(newMenu); SetSubmenuPos(newMenu, item->ScreenFrame().ymin); newMenu->SetAlwaysOnTop(true); newMenu->Show(); newMenu->SetParentMenu(this); newMenu->SetNotify(notifyTarget); submenus.Push(newMenu); } else { SendOnMenuAction(action); if (!sticky) DestroyMenu(closeAction); } } csRef pawsMenu::FindSubmenuNode(csRef node, const csString & name) { csRef iter; csRef child, foundNode; csString nodeName; if ((node->GetType() != CS_NODE_ELEMENT) && (node->GetType() != CS_NODE_DOCUMENT)) return NULL; nodeName = node->GetAttributeValue("name"); if (nodeName == name) return node; iter = node->GetNodes(); while (iter->HasNext()) { child = iter->Next(); foundNode = FindSubmenuNode(child, name); if (foundNode != NULL) return foundNode; } return NULL; } void pawsMenu::SendDestroyAction() { pawsMenuAction action; action.name = MENU_DESTROY_ACTION_NAME; SendOnMenuAction(action); } void pawsMenu::SetNotify(pawsWidget * _notifyTarget) { notifyTarget = _notifyTarget; } void pawsMenu::SendOnMenuAction(const pawsMenuAction & action) { if (notifyTarget != NULL) notifyTarget->OnMenuAction(this, action); else Error1("Nowhere to send OnMenuAction - menu has no notification target"); } void pawsMenu::SetParentMenu(pawsIMenu * _parentMenu) { parentMenu = _parentMenu; } void pawsMenu::DestroyMenu(pawsMenuClose reason) { if (parentMenu != NULL) parentMenu->OnChildMenuDestroyed(this, reason); for ( size_t x = 0; x < submenus.Length(); x++ ) { submenus[x]->OnParentMenuDestroyed(reason); } SendDestroyAction(); } void pawsMenu::OnSiblingOpened() { if (!sticky) DestroyMenu(closeSiblingOpened); } void pawsMenu::SetSubmenuPos(pawsMenu * submenu, int recommY) { int screenWidth, screenHeight; csRect submenuRect; int x, y; screenWidth = graphics2d->GetWidth(); screenHeight = graphics2d->GetHeight(); submenuRect = submenu->ScreenFrame(); if (recommY < 0) y = 0; else if (recommY + submenuRect.Height() > screenHeight) y = screenHeight - submenuRect.Height(); else y = recommY; if (screenFrame.xmax + 2 + submenuRect.Width() <= screenWidth) x = screenFrame.xmax + 2; else x = screenFrame.xmin - 2 - submenuRect.Width(); submenu->MoveTo(x, y); } void pawsMenu::Draw() { csRect itemFrame, arrowFrame; csRect rect; pawsWidget::Draw(); graphics2D->SetClipRect( 0,0, graphics2D->GetWidth(), graphics2D->GetHeight()); if (arrowImage != NULL) { for ( size_t x = 0; x < items.Length(); x++ ) { if (items[x]->GetAction().name == SUBMENU_ACTION_NAME) { itemFrame = items[x]->ScreenFrame(); arrowFrame.SetPos(screenFrame.xmax - BORDER_SIZE - 4 - SUBMENU_ARROW_SIZE, itemFrame.ymin + itemFrame.Height()/2 - SUBMENU_ARROW_SIZE/2 + 1); arrowFrame.SetSize(SUBMENU_ARROW_SIZE, SUBMENU_ARROW_SIZE); arrowImage->Draw(arrowFrame); } } } rect.xmin = screenFrame.xmin+2; rect.ymin = screenFrame.ymin+2; rect.xmax = screenFrame.xmax-2; rect.ymax = screenFrame.ymin + GetActualHeight(2*BORDER_SIZE + label->DefaultFrame().Height())-2; DrawBumpFrame(graphics2d, this, rect, GetBorderStyle() ); rect.ymin = screenFrame.ymin + GetActualHeight(2*BORDER_SIZE + label->DefaultFrame().Height()); rect.ymax = screenFrame.ymax-2; DrawBumpFrame(graphics2d, this, rect, GetBorderStyle() ); } bool pawsMenu::OnMouseDown(int button, int modifiers, int x, int y) { csRect rowRect; for ( size_t i = 0; i < items.Length(); i++ ) { rowRect = items[i]->ScreenFrame(); rowRect.xmin = screenFrame.xmin + BORDER_SIZE; rowRect.xmax = screenFrame.xmax - BORDER_SIZE; if (rowRect.Contains(x, y)) { items[i]->Invoke(); return true; } } return pawsWidget::OnMouseDown(button, modifiers, x, y); } ////////////////////////////////////////////////////////////////////// // class pawsMenuSeparator ////////////////////////////////////////////////////////////////////// pawsMenuSeparator::pawsMenuSeparator() { SetRelativeFrameSize(10, 10); graphics2d = PawsManager::GetSingleton().GetGraphics2D(); } void pawsMenuSeparator::Draw() { csRect parentFrame, sepFrame; graphics2D->SetClipRect( 0,0, graphics2D->GetWidth(), graphics2D->GetHeight()); parentFrame = parent->ScreenFrame(); sepFrame.xmin = parentFrame.xmin + BORDER_SIZE; sepFrame.xmax = parentFrame.xmax - BORDER_SIZE; sepFrame.ymin = screenFrame.ymin + 6; sepFrame.ymax = screenFrame.ymin + 9; DrawBumpFrame(graphics2d, this, sepFrame, GetBorderStyle() ); }