From 031dc97a316339d19f7a2530f2c90ca67d758528 Mon Sep 17 00:00:00 2001 From: skyjake Date: Wed, 30 Jan 2013 16:07:56 +0200 Subject: [PATCH] libshell: Added MenuWidget --- doomsday/libshell/include/de/shell/MenuWidget | 1 + doomsday/libshell/include/de/shell/action.h | 6 + .../libshell/include/de/shell/menuwidget.h | 83 +++++ doomsday/libshell/libshell.pro | 6 +- doomsday/libshell/src/action.cpp | 15 +- doomsday/libshell/src/menuwidget.cpp | 283 ++++++++++++++++++ 6 files changed, 391 insertions(+), 3 deletions(-) create mode 100644 doomsday/libshell/include/de/shell/MenuWidget create mode 100644 doomsday/libshell/include/de/shell/menuwidget.h create mode 100644 doomsday/libshell/src/menuwidget.cpp diff --git a/doomsday/libshell/include/de/shell/MenuWidget b/doomsday/libshell/include/de/shell/MenuWidget new file mode 100644 index 0000000000..c7fa98fb5e --- /dev/null +++ b/doomsday/libshell/include/de/shell/MenuWidget @@ -0,0 +1 @@ +#include "menuwidget.h" diff --git a/doomsday/libshell/include/de/shell/action.h b/doomsday/libshell/include/de/shell/action.h index 414513e7b8..38d1a83fb9 100644 --- a/doomsday/libshell/include/de/shell/action.h +++ b/doomsday/libshell/include/de/shell/action.h @@ -34,8 +34,13 @@ class Action : public QObject public: Action(KeyEvent const &event, QObject *target = 0, char const *slot = 0); + + Action(String const &label, KeyEvent const &event, QObject *target = 0, char const *slot = 0); + ~Action(); + String label() const; + /** * Triggers the action if the event matches the action's condition. * @@ -50,6 +55,7 @@ class Action : public QObject private: KeyEvent _event; + String _label; }; } // namespace shell diff --git a/doomsday/libshell/include/de/shell/menuwidget.h b/doomsday/libshell/include/de/shell/menuwidget.h new file mode 100644 index 0000000000..fc321cd580 --- /dev/null +++ b/doomsday/libshell/include/de/shell/menuwidget.h @@ -0,0 +1,83 @@ +/** @file menuwidget.h Menu with shortcuts. + * + * @authors Copyright © 2013 Jaakko Keränen + * + * @par License + * GPL: http://www.gnu.org/licenses/gpl.html + * + * 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; either version 2 of the License, or (at your + * option) any later version. 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, see: + * http://www.gnu.org/licenses + */ + +#ifndef LIBSHELL_MENUWIDGET_H +#define LIBSHELL_MENUWIDGET_H + +#include "TextWidget" +#include "Action" +#include "TextCanvas" + +namespace de { +namespace shell { + +/** + * Menu with Action instances as items. + * + * The width of the widget is automatically determined based on how much space + * is needed for the items and their possible shortcut labels. The height of + * the widget depends on the number of items in the menu. + */ +class MenuWidget : public TextWidget +{ +public: + MenuWidget(String const &name = ""); + + ~MenuWidget(); + + int itemCount() const; + + void appendItem(Action *action, String const &shortcutLabel = ""); + + void appendSeparator(); + + void insertItem(int pos, Action *action, String const &shortcutLabel = ""); + + void insertSeparator(int pos); + + void clear(); + + void removeItem(int pos); + + Action &itemAction(int pos) const; + + void setCursor(int pos); + + int cursor() const; + + void setSelectionAttribs(TextCanvas::Char::Attribs const &attribs); + + void setBackgroundAttribs(TextCanvas::Char::Attribs const &attribs); + + void setBorderAttribs(TextCanvas::Char::Attribs const &attribs); + + Vector2i cursorPosition() const; + + // Events. + void draw(); + bool handleEvent(Event const *); + +private: + struct Instance; + Instance *d; +}; + +} // namespace shell +} // namespace de + +#endif // LIBSHELL_MENUWIDGET_H diff --git a/doomsday/libshell/libshell.pro b/doomsday/libshell/libshell.pro index 0d6e53a3e6..b0166b199e 100644 --- a/doomsday/libshell/libshell.pro +++ b/doomsday/libshell/libshell.pro @@ -43,7 +43,8 @@ HEADERS += \ include/de/shell/protocol.h \ include/de/shell/textcanvas.h \ include/de/shell/textrootwidget.h \ - include/de/shell/textwidget.h + include/de/shell/textwidget.h \ + include/de/shell/menuwidget.h # Sources and private headers. SOURCES += \ @@ -55,7 +56,8 @@ SOURCES += \ src/protocol.cpp \ src/textcanvas.cpp \ src/textrootwidget.cpp \ - src/textwidget.cpp + src/textwidget.cpp \ + src/menuwidget.cpp # Installation --------------------------------------------------------------- diff --git a/doomsday/libshell/src/action.cpp b/doomsday/libshell/src/action.cpp index dd4677aa39..748c64c07b 100644 --- a/doomsday/libshell/src/action.cpp +++ b/doomsday/libshell/src/action.cpp @@ -21,7 +21,7 @@ namespace de { namespace shell { -Action::Action(KeyEvent const &event, QObject *target, const char *slot) +Action::Action(KeyEvent const &event, QObject *target, char const *slot) : _event(event) { if(target && slot) @@ -30,8 +30,21 @@ Action::Action(KeyEvent const &event, QObject *target, const char *slot) } } +Action::Action(String const &label, KeyEvent const &event, QObject *target, char const *slot) + : _event(event), _label(label) +{ + if(target && slot) + { + connect(this, SIGNAL(triggered()), target, slot); + } +} + Action::~Action() +{} + +String Action::label() const { + return _label; } bool Action::tryTrigger(KeyEvent const &ev) diff --git a/doomsday/libshell/src/menuwidget.cpp b/doomsday/libshell/src/menuwidget.cpp new file mode 100644 index 0000000000..17960bc8de --- /dev/null +++ b/doomsday/libshell/src/menuwidget.cpp @@ -0,0 +1,283 @@ +/** @file menuwidget.cpp Menu with shortcuts. + * + * @authors Copyright © 2013 Jaakko Keränen + * + * @par License + * GPL: http://www.gnu.org/licenses/gpl.html + * + * 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; either version 2 of the License, or (at your + * option) any later version. 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, see: + * http://www.gnu.org/licenses + */ + +#include "de/shell/MenuWidget" +#include "de/shell/TextRootWidget" +#include +#include + +namespace de { +namespace shell { + +struct MenuWidget::Instance +{ + MenuWidget &self; + ConstantRule *width; + ConstantRule *height; + TextCanvas::Char::Attribs borderAttr; + TextCanvas::Char::Attribs backgroundAttr; + TextCanvas::Char::Attribs selectionAttr; + Vector2i cursorPos; ///< Visual position. + + struct Item + { + Action *action; + String shortcutLabel; + bool separatorBefore; + + Item() : action(0), separatorBefore(false) + {} + }; + + QList items; + int cursor; + + Instance(MenuWidget &inst) + : self(inst), + borderAttr(TextCanvas::Char::Reverse), + backgroundAttr(TextCanvas::Char::Reverse), + cursor(0) + { + width = new ConstantRule(1); + height = new ConstantRule(1); + } + + ~Instance() + { + clear(); + releaseRef(width); + releaseRef(height); + } + + void clear() + { + foreach(Item i, items) + { + self.removeAction(*i.action); + delete i.action; + } + items.clear(); + updateSize(); + } + + void updateSize() + { + int cols = 0; + int lines = 2; // borders + foreach(Item const &item, items) + { + lines++; + if(item.separatorBefore) lines++; + + int w = item.action->label().size(); + if(!item.shortcutLabel.isEmpty()) + { + w += 1 + item.shortcutLabel.size(); + } + cols = de::max(w, cols); + } + height->set(lines); + width->set(6 + cols); // cursor and borders + } + + void removeItem(int pos) + { + self.removeAction(*items[pos].action); + delete items[pos].action; + items.removeAt(pos); + updateSize(); + } +}; + +MenuWidget::MenuWidget(const String &name) + : TextWidget(name), d(new Instance(*this)) +{ + rule().setInput(RectangleRule::Width, d->width) + .setInput(RectangleRule::Height, d->height); +} + +MenuWidget::~MenuWidget() +{ + delete d; +} + +int MenuWidget::itemCount() const +{ + return d->items.size(); +} + +void MenuWidget::appendItem(Action *action, String const &shortcutLabel) +{ + Instance::Item item; + item.action = action; + item.shortcutLabel = shortcutLabel; + d->items.append(item); + d->updateSize(); + redraw(); + + addAction(action); +} + +void MenuWidget::appendSeparator() +{ + if(d->items.isEmpty()) return; + + d->items.last().separatorBefore = true; + d->updateSize(); + redraw(); +} + +void MenuWidget::insertItem(int pos, Action *action, String const &shortcutLabel) +{ + Instance::Item item; + item.action = action; + item.shortcutLabel = shortcutLabel; + d->items.insert(pos, item); + d->updateSize(); + redraw(); + + addAction(action); +} + +void MenuWidget::insertSeparator(int pos) +{ + if(pos < 0 || pos >= d->items.size()) return; + + d->items[pos].separatorBefore = true; + d->updateSize(); + redraw(); +} + +void MenuWidget::clear() +{ + d->clear(); + redraw(); +} + +void MenuWidget::removeItem(int pos) +{ + d->removeItem(pos); + redraw(); +} + +Action &MenuWidget::itemAction(int pos) const +{ + return *d->items[pos].action; +} + +void MenuWidget::setCursor(int pos) +{ + d->cursor = pos; + redraw(); +} + +int MenuWidget::cursor() const +{ + return d->cursor; +} + +void MenuWidget::setSelectionAttribs(TextCanvas::Char::Attribs const &attribs) +{ + d->selectionAttr = attribs; + redraw(); +} + +void MenuWidget::setBackgroundAttribs(TextCanvas::Char::Attribs const &attribs) +{ + d->backgroundAttr = attribs; + redraw(); +} + +void MenuWidget::setBorderAttribs(TextCanvas::Char::Attribs const &attribs) +{ + d->borderAttr = attribs; + redraw(); +} + +Vector2i MenuWidget::cursorPosition() const +{ + return d->cursorPos; +} + +void MenuWidget::draw() +{ + Rectanglei pos = rule().recti(); + TextCanvas buf(pos.size()); + buf.clear(TextCanvas::Char(' ', d->backgroundAttr)); + + int y = 1; + for(int i = 0; i < d->items.size(); ++i) + { + Instance::Item const &item = d->items[i]; + + // Draw a separator. + if(item.separatorBefore) + { + buf.fill(Rectanglei(Vector2i(1, y), Vector2i(pos.width() - 1, 2)), + TextCanvas::Char('-', d->borderAttr)); + y++; + } + + // Determine style. + TextCanvas::Char::Attribs itemAttr = (d->cursor == i && hasFocus()? + d->selectionAttr : d->backgroundAttr); + + // Cursor. + if(d->cursor == i) + { + buf.fill(Rectanglei(Vector2i(1, y), Vector2i(pos.width() - 1, 2)), + TextCanvas::Char(' ', itemAttr)); + + d->cursorPos = Vector2i(2, y); + buf.put(d->cursorPos, TextCanvas::Char('*', itemAttr)); + d->cursorPos += pos.topLeft; + } + + buf.drawText(Vector2i(4, y), item.action->label(), + itemAttr | (d->cursor == i? TextCanvas::Char::Bold : TextCanvas::Char::DefaultAttributes)); + + if(item.shortcutLabel) + { + buf.drawText(Vector2i(buf.width() - 2 - item.shortcutLabel.size(), y), + item.shortcutLabel, itemAttr); + } + + y++; + } + + // Draw a frame. + buf.drawLineRect(buf.rect(), d->borderAttr); + + targetCanvas().draw(buf, pos.topLeft); +} + +bool MenuWidget::handleEvent(Event const *event) +{ + if(event->type() != Event::KeyPress) return false; + + KeyEvent const *ev = static_cast(event); + + // Fall back to default handling. + TextWidget::handleEvent(event); + + // When open, a menu eats all key events. + return true; +} + +} // namespace shell +} // namespace de