diff --git a/doomsday/libdeng2/src/widgets/rootwidget.cpp b/doomsday/libdeng2/src/widgets/rootwidget.cpp index 6b37548f90..5af7e99b69 100644 --- a/doomsday/libdeng2/src/widgets/rootwidget.cpp +++ b/doomsday/libdeng2/src/widgets/rootwidget.cpp @@ -93,7 +93,7 @@ void RootWidget::setViewSize(Vector2i const &size) void RootWidget::setFocus(Widget *widget) { - d->focus = widget; + d->focus = widget; } Widget *RootWidget::focus() const diff --git a/doomsday/libdeng2/src/widgets/widget.cpp b/doomsday/libdeng2/src/widgets/widget.cpp index e736663a8e..18a04c8ed7 100644 --- a/doomsday/libdeng2/src/widgets/widget.cpp +++ b/doomsday/libdeng2/src/widgets/widget.cpp @@ -130,7 +130,7 @@ bool Widget::isHidden() const void Widget::show(bool doShow) { - d->hidden = doShow; + d->hidden = !doShow; } void Widget::clear() diff --git a/doomsday/libshell/include/de/shell/action.h b/doomsday/libshell/include/de/shell/action.h index 38d1a83fb9..67b2417a50 100644 --- a/doomsday/libshell/include/de/shell/action.h +++ b/doomsday/libshell/include/de/shell/action.h @@ -37,6 +37,8 @@ class Action : public QObject Action(String const &label, KeyEvent const &event, QObject *target = 0, char const *slot = 0); + Action(String const &label); + ~Action(); String label() const; @@ -50,6 +52,8 @@ class Action : public QObject */ bool tryTrigger(KeyEvent const &ev); + void trigger(); + signals: void triggered(); diff --git a/doomsday/libshell/include/de/shell/menuwidget.h b/doomsday/libshell/include/de/shell/menuwidget.h index fc321cd580..fa310ef1e4 100644 --- a/doomsday/libshell/include/de/shell/menuwidget.h +++ b/doomsday/libshell/include/de/shell/menuwidget.h @@ -35,6 +35,8 @@ namespace shell { */ class MenuWidget : public TextWidget { + Q_OBJECT + public: MenuWidget(String const &name = ""); @@ -72,6 +74,13 @@ class MenuWidget : public TextWidget void draw(); bool handleEvent(Event const *); +public slots: + void open(); + void close(); + +signals: + void closed(); + private: struct Instance; Instance *d; diff --git a/doomsday/libshell/src/action.cpp b/doomsday/libshell/src/action.cpp index 748c64c07b..ba114a4b4f 100644 --- a/doomsday/libshell/src/action.cpp +++ b/doomsday/libshell/src/action.cpp @@ -39,6 +39,9 @@ Action::Action(String const &label, KeyEvent const &event, QObject *target, char } } +Action::Action(String const &label) : _event(KeyEvent("")), _label(label) +{} + Action::~Action() {} @@ -51,11 +54,16 @@ bool Action::tryTrigger(KeyEvent const &ev) { if(ev == _event) { - emit triggered(); + trigger(); return true; } return false; } +void Action::trigger() +{ + emit triggered(); +} + } // namespace shell } // namespace de diff --git a/doomsday/libshell/src/menuwidget.cpp b/doomsday/libshell/src/menuwidget.cpp index 5a49305ead..ae27d05619 100644 --- a/doomsday/libshell/src/menuwidget.cpp +++ b/doomsday/libshell/src/menuwidget.cpp @@ -38,9 +38,9 @@ struct MenuWidget::Instance { Action *action; String shortcutLabel; - bool separatorBefore; + bool separatorAfter; - Item() : action(0), separatorBefore(false) + Item() : action(0), separatorAfter(false) {} }; @@ -82,7 +82,7 @@ struct MenuWidget::Instance foreach(Item const &item, items) { lines++; - if(item.separatorBefore) lines++; + if(item.separatorAfter) lines++; int w = item.action->label().size(); if(!item.shortcutLabel.isEmpty()) @@ -137,7 +137,7 @@ void MenuWidget::appendSeparator() { if(d->items.isEmpty()) return; - d->items.last().separatorBefore = true; + d->items.last().separatorAfter = true; d->updateSize(); redraw(); } @@ -158,7 +158,7 @@ void MenuWidget::insertSeparator(int pos) { if(pos < 0 || pos >= d->items.size()) return; - d->items[pos].separatorBefore = true; + d->items[pos].separatorAfter = true; d->updateSize(); redraw(); } @@ -214,6 +214,19 @@ Vector2i MenuWidget::cursorPosition() const return d->cursorPos; } +void MenuWidget::open() +{ + show(); + redraw(); +} + +void MenuWidget::close() +{ + emit closed(); + hide(); + redraw(); +} + void MenuWidget::draw() { Rectanglei pos = rule().recti(); @@ -225,14 +238,6 @@ void MenuWidget::draw() { 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); @@ -240,7 +245,7 @@ void MenuWidget::draw() // Cursor. if(d->cursor == i) { - buf.fill(Rectanglei(Vector2i(1, y), Vector2i(pos.width() - 1, 2)), + buf.fill(Rectanglei(Vector2i(1, y), Vector2i(pos.width() - 1, y + 1)), TextCanvas::Char(' ', itemAttr)); d->cursorPos = Vector2i(2, y); @@ -258,6 +263,14 @@ void MenuWidget::draw() } y++; + + // Draw a separator. + if(item.separatorAfter) + { + buf.fill(Rectanglei(Vector2i(1, y), Vector2i(pos.width() - 1, y + 1)), + TextCanvas::Char('-', d->borderAttr)); + y++; + } } // Draw a frame. @@ -270,10 +283,62 @@ bool MenuWidget::handleEvent(Event const *event) { if(event->type() != Event::KeyPress) return false; + // Check registered actions. + if(TextWidget::handleEvent(event)) + return true; + KeyEvent const *ev = static_cast(event); - // Fall back to default handling. - TextWidget::handleEvent(event); + if(ev->text() == " ") + { + itemAction(d->cursor).trigger(); + close(); + return true; + } + + if(ev->text().isEmpty()) + { + switch(ev->key()) + { + case Qt::Key_Up: + if(d->cursor > 0) + { + d->cursor--; + redraw(); + } + return true; + + case Qt::Key_Down: + if(d->cursor < itemCount() - 1) + { + d->cursor++; + redraw(); + } + return true; + + case Qt::Key_Home: + case Qt::Key_PageUp: + d->cursor = 0; + redraw(); + return true; + + case Qt::Key_End: + case Qt::Key_PageDown: + d->cursor = itemCount() - 1; + redraw(); + return true; + + case Qt::Key_Enter: + itemAction(d->cursor).trigger(); + close(); + return true; + + default: + // Any other control key closes the menu. + close(); + return true; + } + } // When open, a menu eats all key events. return true; diff --git a/doomsday/tools/shell/shell-text/src/cursesapp.cpp b/doomsday/tools/shell/shell-text/src/cursesapp.cpp index 816d8c1cee..7465103eb6 100644 --- a/doomsday/tools/shell/shell-text/src/cursesapp.cpp +++ b/doomsday/tools/shell/shell-text/src/cursesapp.cpp @@ -285,6 +285,10 @@ struct CursesApp::Instance mods = KeyEvent::Control; break; + case 0x1b: + code = Qt::Key_Escape; + break; + default: if(key & KEY_CODE_YES) qDebug() << "CURSES" << QString("0%1").arg(key, 0, 8).toAscii().constData(); diff --git a/doomsday/tools/shell/shell-text/src/shellapp.cpp b/doomsday/tools/shell/shell-text/src/shellapp.cpp index beee23ead3..576d49786c 100644 --- a/doomsday/tools/shell/shell-text/src/shellapp.cpp +++ b/doomsday/tools/shell/shell-text/src/shellapp.cpp @@ -20,52 +20,99 @@ #include "logwidget.h" #include "commandlinewidget.h" #include "statuswidget.h" +#include +#include #include +#include #include #include using namespace de; +using namespace shell; struct ShellApp::Instance { ShellApp &self; + MenuWidget *menu; LogWidget *log; CommandLineWidget *cli; + LabelWidget *menuLabel; StatusWidget *status; - shell::Link *link; + Link *link; Instance(ShellApp &a) : self(a), link(0) { RootWidget &root = self.rootWidget(); + // Status bar in the bottom of the view. status = new StatusWidget; status->rule() - .setInput(RectangleRule::Height, refless(new ConstantRule(1))) - .setInput(RectangleRule::Bottom, root.viewBottom()) - .setInput(RectangleRule::Width, root.viewWidth()) - .setInput(RectangleRule::Left, root.viewLeft()); - + .setInput(RuleRectangle::Height, refless(new ConstantRule(1))) + .setInput(RuleRectangle::Bottom, root.viewBottom()) + .setInput(RuleRectangle::Width, root.viewWidth()) + .setInput(RuleRectangle::Left, root.viewLeft()); + + // Menu button at the left edge. + menuLabel = new LabelWidget; + menuLabel->setAlignment(AlignTop); + menuLabel->setLabel(" F9:Menu "); + menuLabel->setAttribs(TextCanvas::Char::Bold); + menuLabel->rule() + .setInput(RuleRectangle::Left, root.viewLeft()) + .setInput(RuleRectangle::Width, refless(new ConstantRule(menuLabel->label().size()))) + .setInput(RuleRectangle::Bottom, status->rule().top()); + + menuLabel->addAction(new Action(KeyEvent(Qt::Key_F9), &self, SLOT(openMenu()))); + menuLabel->addAction(new Action(KeyEvent(Qt::Key_Z, KeyEvent::Control), &self, SLOT(openMenu()))); + menuLabel->addAction(new Action(KeyEvent(Qt::Key_C, KeyEvent::Control), &self, SLOT(openMenu()))); + + // Expanding command line widget. cli = new CommandLineWidget; cli->rule() - .setInput(RectangleRule::Left, root.viewLeft()) - .setInput(RectangleRule::Width, root.viewWidth()) - .setInput(RectangleRule::Bottom, status->rule().top()); + .setInput(RuleRectangle::Left, menuLabel->rule().right()) + .setInput(RuleRectangle::Right, root.viewRight()) + .setInput(RuleRectangle::Bottom, status->rule().top()); + + menuLabel->rule().setInput(RuleRectangle::Top, cli->rule().top()); + // Log history covers the rest of the view. log = new LogWidget; log->rule() - .setInput(RectangleRule::Left, root.viewLeft()) - .setInput(RectangleRule::Width, root.viewWidth()) - .setInput(RectangleRule::Top, root.viewTop()) - .setInput(RectangleRule::Bottom, cli->rule().top()); - - // Ownership also given to the root widget: + .setInput(RuleRectangle::Left, root.viewLeft()) + .setInput(RuleRectangle::Width, root.viewWidth()) + .setInput(RuleRectangle::Top, root.viewTop()) + .setInput(RuleRectangle::Bottom, cli->rule().top()); + + // Main menu. + menu = new MenuWidget; + menu->hide(); // closed initially + menu->appendItem(new Action("Open connection...", + KeyEvent(Qt::Key_O), + &self, SLOT(openConnection())), "O"); + menu->appendItem(new Action("Disconnect")); + menu->appendSeparator(); + menu->appendItem(new Action("Start new server")); + menu->appendSeparator(); + menu->appendItem(new Action("About")); + menu->appendItem(new Action("Quit Shell", + KeyEvent(Qt::Key_X, KeyEvent::Control), + &self, SLOT(quit())), "Ctrl-X"); + menu->rule() + .setInput(RuleRectangle::Bottom, menuLabel->rule().top()) + .setInput(RuleRectangle::Left, menuLabel->rule().left()); + + // Compose the UI. root.add(status); root.add(cli); root.add(log); + root.add(menuLabel); + root.add(menu); root.setFocus(cli); + // Signals. QObject::connect(cli, SIGNAL(commandEntered(de::String)), &self, SLOT(sendCommandToServer(de::String))); + QObject::connect(menu, SIGNAL(closed()), &self, SLOT(menuClosed())); } ~Instance() @@ -85,7 +132,7 @@ ShellApp::ShellApp(int &argc, char **argv) if(args.size() > 1) { // Open a connection. - d->link = new shell::Link(Address(args[1])); + d->link = new Link(Address(args[1])); d->status->setShellLink(d->link); connect(d->link, SIGNAL(packetsReady()), this, SLOT(handleIncomingPackets())); @@ -100,6 +147,10 @@ ShellApp::~ShellApp() delete d; } +void ShellApp::openConnection() +{ +} + void ShellApp::sendCommandToServer(String command) { if(d->link) @@ -134,3 +185,16 @@ void ShellApp::disconnected() d->link = 0; d->status->setShellLink(0); } + +void ShellApp::openMenu() +{ + d->menuLabel->setAttribs(TextCanvas::Char::Reverse); + rootWidget().setFocus(d->menu); + d->menu->open(); +} + +void ShellApp::menuClosed() +{ + d->menuLabel->setAttribs(TextCanvas::Char::Bold); + rootWidget().setFocus(d->cli); +} diff --git a/doomsday/tools/shell/shell-text/src/shellapp.h b/doomsday/tools/shell/shell-text/src/shellapp.h index 216ab1ceed..b0191d4b09 100644 --- a/doomsday/tools/shell/shell-text/src/shellapp.h +++ b/doomsday/tools/shell/shell-text/src/shellapp.h @@ -31,9 +31,12 @@ class ShellApp : public CursesApp ~ShellApp(); public slots: + void openConnection(); void sendCommandToServer(de::String command); void handleIncomingPackets(); void disconnected(); + void openMenu(); + void menuClosed(); private: struct Instance;