summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJoel Klinghed <the_jk@yahoo.com>2017-07-30 00:36:18 +0200
committerJoel Klinghed <the_jk@yahoo.com>2017-07-30 00:36:18 +0200
commit124429985be8033a398b891f400f70a058330c87 (patch)
tree6f2e8ba3bd4044a1c8252d17e837fe83c44125d8
parente7a0bf680f3d805b190b89bfeb1f1f8a4c3ec982 (diff)
Add menu shortcuts
-rw-r--r--src/gui_gtk.cc190
-rw-r--r--src/gui_menu.hh31
-rw-r--r--src/gui_qt.cc120
-rw-r--r--src/monitor-gui.cc33
4 files changed, 321 insertions, 53 deletions
diff --git a/src/gui_gtk.cc b/src/gui_gtk.cc
index 7dd2d42..2056034 100644
--- a/src/gui_gtk.cc
+++ b/src/gui_gtk.cc
@@ -822,10 +822,11 @@ public:
return G_MENU_MODEL(menu_.get());
}
- void set_map(GActionMap* map);
+ void set_app(GtkApplication* app);
void activate(GSimpleAction* action);
- void add_item(std::string const& id, std::string const& label) override;
+ void add_item(std::string const& id, std::string const& label,
+ Shortcut const& shortcut) override;
GuiMenu* add_menu(std::string const& label) override;
void add_separator() override;
bool enable_item(std::string const& id, bool enable) override;
@@ -833,7 +834,7 @@ public:
private:
explicit GtkGuiMenu(GtkGuiMenu* parent)
: parent_(parent), menu_(g_menu_new()), section_(g_menu_new()),
- map_(nullptr) {
+ app_(nullptr) {
g_menu_append_section(menu_.get(), nullptr, G_MENU_MODEL(section_.get()));
}
@@ -847,14 +848,16 @@ private:
std::string add_action(std::string const& id);
std::string escape(std::string const& id);
std::string unescape(std::string const& action);
+ void add_shortcut(std::string const& action, Shortcut const& shortcut);
GtkGuiMenu* const parent_;
shared_gobject<GMenu> menu_;
shared_gobject<GMenu> section_;
std::unordered_map<std::string, shared_gobject<GSimpleAction>> action_;
- GActionMap* map_;
+ GtkApplication* app_;
std::vector<std::unique_ptr<GtkGuiMenu>> submenus_;
Observers<Listener*> observers_;
+ std::unordered_map<std::string, std::string> shortcut_;
};
class GtkGuiStatusBar : public GuiStatusBar {
@@ -1852,13 +1855,17 @@ MainAppWindow* main_app_window_new(MainApp *app) {
return ret;
}
-void main_app_activate(GApplication* g_app) {
- auto app = MAIN_APP(g_app);
+void setup_menubar(MainApp* app) {
auto menu = static_cast<GtkGuiMenu*>(app->main_->menu());
if (menu) {
- menu->set_map(G_ACTION_MAP(app));
+ menu->set_app(GTK_APPLICATION(app));
gtk_application_set_menubar(GTK_APPLICATION(app), menu->model());
}
+}
+
+void main_app_activate(GApplication* g_app) {
+ auto app = MAIN_APP(g_app);
+ setup_menubar(app);
auto win = main_app_window_new(app);
gtk_window_set_title(GTK_WINDOW(win), app->main_->title().c_str());
gtk_window_present(GTK_WINDOW(win));
@@ -1876,11 +1883,7 @@ bool main_app_window_open(MainApp* app, MainAppWindow*, GFile* file) {
void main_app_open(GApplication* app, GFile** files, gint n_files,
gchar const*) {
- auto menu = static_cast<GtkGuiMenu*>(MAIN_APP(app)->main_->menu());
- if (menu) {
- menu->set_map(G_ACTION_MAP(app));
- gtk_application_set_menubar(GTK_APPLICATION(app), menu->model());
- }
+ setup_menubar(MAIN_APP(app));
auto windows = gtk_application_get_windows(GTK_APPLICATION(app));
auto win = windows ? MAIN_APP_WINDOW(windows->data)
: main_app_window_new(MAIN_APP(app));
@@ -2010,8 +2013,12 @@ double GtkGuiMain::split() const {
return split_;
}
-void GtkGuiMenu::add_item(std::string const& id, std::string const& label) {
+void GtkGuiMenu::add_item(std::string const& id, std::string const& label,
+ Shortcut const& shortcut) {
auto action = add_action(id);
+ if (shortcut.key) {
+ add_shortcut(action, shortcut);
+ }
g_menu_append(section_.get(), label.c_str(), action.c_str());
}
@@ -2027,23 +2034,148 @@ void GtkGuiMenu::add_separator() {
g_menu_append_section(menu_.get(), nullptr, G_MENU_MODEL(section_.get()));
}
-void GtkGuiMenu::set_map(GActionMap* map) {
+void GtkGuiMenu::set_app(GtkApplication* app) {
assert(!parent_);
- assert(!map_);
- assert(map);
- map_ = map;
+ assert(!app_);
+ assert(app);
+ app_ = app;
for (auto const& pair : action_) {
- g_action_map_add_action(map_, G_ACTION(pair.second.get()));
+ g_action_map_add_action(G_ACTION_MAP(app_), G_ACTION(pair.second.get()));
+ }
+ for (auto const& pair : shortcut_) {
+ gchar const* accels[2];
+ accels[0] = pair.second.c_str();
+ accels[1] = nullptr;
+ gtk_application_set_accels_for_action(app_, pair.first.c_str(), accels);
}
}
+void GtkGuiMenu::add_shortcut(std::string const& action,
+ Shortcut const& shortcut) {
+ if (parent_) return parent_->add_shortcut(action, shortcut);
+ assert(shortcut.key);
+ std::string accel;
+ if (shortcut.mask & CTRL) accel.append("<Ctrl>");
+ if (shortcut.mask & ALT) accel.append("<Alt>");
+ if (shortcut.mask & SHIFT) accel.append("<Shift>");
+ if (shortcut.mask & META) accel.append("<Meta>");
+ guint key;
+ if (shortcut.function) {
+ if (shortcut.key >= 1 && shortcut.key <= 35) {
+ key = GDK_KEY_F1 + (shortcut.key - 1);
+ } else {
+ assert(false);
+ return;
+ }
+ } else {
+ if (shortcut.key == ESC) {
+ key = GDK_KEY_Escape;
+ } else if (shortcut.key == RETURN) {
+ key = GDK_KEY_Return;
+ } else if (shortcut.key == UP) {
+ key = GDK_KEY_Up;
+ } else if (shortcut.key == LEFT) {
+ key = GDK_KEY_Left;
+ } else if (shortcut.key == RIGHT) {
+ key = GDK_KEY_Right;
+ } else if (shortcut.key == DOWN) {
+ key = GDK_KEY_Down;
+ } else if (shortcut.key == '\t') {
+ key = GDK_KEY_Tab;
+ } else if (shortcut.key == 0x08) {
+ key = GDK_KEY_BackSpace;
+ } else if (shortcut.key >= 0x20 && shortcut.key < 0x7f) {
+ key = GDK_KEY_space + (shortcut.key - ' ');
+ } else {
+ assert(false);
+ return;
+ }
+ }
+ auto name = gdk_keyval_name(key);
+ if (!name) {
+ assert(false);
+ return;
+ }
+ accel.append(name);
+ shortcut_.emplace(action, accel);
+ if (app_) {
+ gchar const* accels[2];
+ accels[0] = accel.c_str();
+ accels[1] = nullptr;
+ gtk_application_set_accels_for_action(app_, action.c_str(), accels);
+ }
+}
+
+static char hex[] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
+ 'a', 'b', 'c', 'd', 'e', 'f' };
+
+static int8_t unhex(char c) {
+ if (c >= '0' && c <= '9') return (c - '0');
+ if (c >= 'a' && c <= 'f') return 10 + (c - 'a');
+ return -1;
+}
+
std::string GtkGuiMenu::escape(std::string const& action) {
- // TODO
- return action;
+ size_t last = 0;
+ std::string ret;
+ for (size_t i = 0; i < action.size(); ++i) {
+ auto c = action[i];
+ if ((c >= '0' && c <= '9')
+ || (c >= 'a' && c <= 'z')
+ || (c >= 'A' && c <= 'Z')
+ || c == '-') {
+ continue;
+ }
+ ret.append(action, last, i - last);
+ if (c == '.') {
+ ret.append("..");
+ } else {
+ char tmp[3];
+ tmp[0] = '.';
+ tmp[1] = hex[c >> 4];
+ tmp[2] = hex[c & 0xf];
+ ret.append(tmp, 3);
+ }
+ last = i + 1;
+ }
+ if (last == 0) return action;
+ ret.append(action, last, action.size() - last);
+ return ret;
}
std::string GtkGuiMenu::unescape(std::string const& action) {
- return action;
+ size_t last = 0;
+ std::string ret;
+ for (size_t i = 0; i < action.size(); ++i) {
+ if (action[i] != '.') continue;
+ ++i;
+ if (i == action.size()) {
+ assert(false);
+ return "";
+ }
+ ret.append(action, last, i - last);
+ if (action[i] == '.') {
+ last = i + 1;
+ ret.push_back('.');
+ continue;
+ }
+ auto a = unhex(action[i]);
+ ++i;
+ if (a < 0 || i == action.size()) {
+ assert(false);
+ return "";
+ }
+ auto b = unhex(action[i]);
+ if (b < 0) {
+ assert(false);
+ return "";
+ }
+ ret.push_back((a << 4) | b);
+ last = i + 1;
+ }
+ if (last == 0) return action;
+ ret.append(action, last, action.size() - last);
+ return ret;
}
void menu_item_activate(GSimpleAction* action, GVariant*, gpointer data) {
@@ -2061,7 +2193,7 @@ std::string GtkGuiMenu::add_action(std::string const& id) {
auto action = g_simple_action_new(name.c_str(), nullptr);
action_[id].reset(action);
g_signal_connect(action, "activate", G_CALLBACK(menu_item_activate), this);
- if (map_) g_action_map_add_action(map_, G_ACTION(action));
+ if (app_) g_action_map_add_action(G_ACTION_MAP(app_), G_ACTION(action));
return "app." + name;
}
@@ -2235,6 +2367,20 @@ GuiMenu* GuiMenu::create() {
}
// static
+const uint32_t GuiMenu::CTRL = 1 << 0;
+const uint32_t GuiMenu::ALT = 1 << 1;
+const uint32_t GuiMenu::SHIFT = 1 << 2;
+const uint32_t GuiMenu::META = 1 << 3;
+
+// static
+const char GuiMenu::ESC = 0x1b;
+const char GuiMenu::UP = 1;
+const char GuiMenu::LEFT = 2;
+const char GuiMenu::RIGHT = 3;
+const char GuiMenu::DOWN = 4;
+const char GuiMenu::RETURN = '\n';
+
+// static
GuiStatusBar* GuiStatusBar::create() {
return new GtkGuiStatusBar();
}
diff --git a/src/gui_menu.hh b/src/gui_menu.hh
index e2af274..a6c6b54 100644
--- a/src/gui_menu.hh
+++ b/src/gui_menu.hh
@@ -17,11 +17,40 @@ public:
Listener() {}
};
+ static const uint32_t CTRL;
+ static const uint32_t ALT;
+ static const uint32_t SHIFT;
+ static const uint32_t META;
+
+ static const char ESC;
+ static const char UP;
+ static const char LEFT;
+ static const char RIGHT;
+ static const char DOWN;
+ static const char RETURN;
+
+ struct Shortcut {
+ uint32_t const mask;
+ char const key;
+ bool const function;
+
+ Shortcut()
+ : mask(0), key(0), function(false) {
+ }
+ Shortcut(uint32_t mask, char key)
+ : mask(mask), key(key), function(false) {
+ }
+ Shortcut(uint32_t mask, int function)
+ : mask(mask), key(function), function(true) {
+ }
+ };
+
virtual ~GuiMenu() {}
static GuiMenu* create();
- virtual void add_item(std::string const& id, std::string const& label) = 0;
+ virtual void add_item(std::string const& id, std::string const& label,
+ Shortcut const& shortcut = Shortcut()) = 0;
// The returned menu lives as long as the root-menu, do not free
// the pointer yourself
virtual GuiMenu* add_menu(std::string const& label) = 0;
diff --git a/src/gui_qt.cc b/src/gui_qt.cc
index d4bc3d8..8607e16 100644
--- a/src/gui_qt.cc
+++ b/src/gui_qt.cc
@@ -173,12 +173,13 @@ public:
: parent_(nullptr) {
}
- void add_item(std::string const& id, std::string const& label) override {
- item_.emplace_back(id, label);
+ void add_item(std::string const& id, std::string const& label,
+ Shortcut const& shortcut) override {
+ item_.emplace_back(id, label, shortcut);
}
void add_separator() override {
- item_.emplace_back("", "");
+ item_.emplace_back("", "", Shortcut());
}
GuiMenu* add_menu(std::string const& label) override;
@@ -202,16 +203,68 @@ public:
}
protected:
+ struct Entry {
+ std::string const id;
+ std::string const label;
+ Shortcut const shortcut;
+
+ Entry(std::string const& id, std::string const& label,
+ Shortcut const& shortcut)
+ : id(id), label(label), shortcut(shortcut) {
+ }
+ };
+
bool find(const std::string& id) const {
- for (auto const& pair : item_) {
- if (pair.first == id) return true;
+ for (auto const& entry : item_) {
+ if (entry.id == id) return true;
}
return false;
}
+ static QKeySequence make_key_sequence(Shortcut const& shortcut) {
+ assert(shortcut.key);
+ int mod = 0;
+ if (shortcut.mask & CTRL) mod |= Qt::CTRL;
+ if (shortcut.mask & ALT) mod |= Qt::ALT;
+ if (shortcut.mask & SHIFT) mod |= Qt::SHIFT;
+ if (shortcut.mask & META) mod |= Qt::META;
+ int key = 0;
+ if (shortcut.function) {
+ key = Qt::Key_F1 + (shortcut.key - 1);
+ if (key > Qt::Key_F35) {
+ return QKeySequence();
+ }
+ } else {
+ if (shortcut.key == ESC) {
+ key = Qt::Key_Escape;
+ } else if (shortcut.key == RETURN) {
+ key = Qt::Key_Return;
+ } else if (shortcut.key == UP) {
+ key = Qt::Key_Up;
+ } else if (shortcut.key == LEFT) {
+ key = Qt::Key_Left;
+ } else if (shortcut.key == RIGHT) {
+ key = Qt::Key_Right;
+ } else if (shortcut.key == DOWN) {
+ key = Qt::Key_Down;
+ } else if (shortcut.key >= 0x20 && shortcut.key <= 0x60) {
+ key = shortcut.key;
+ } else if (shortcut.key > 0x60 && shortcut.key < 0x7b) {
+ key = shortcut.key - 32;
+ } else if (shortcut.key == '\t') {
+ key = Qt::Key_Tab;
+ } else if (shortcut.key == 0x08) {
+ key = Qt::Key_Backspace;
+ } else {
+ return QKeySequence();
+ }
+ }
+ return QKeySequence(mod + key);
+ }
+
QtCommonMenu* const parent_;
std::string const label_;
- std::vector<std::pair<std::string, std::string>> item_;
+ std::vector<Entry> item_;
std::unordered_set<std::string> disabled_;
std::vector<std::unique_ptr<QtChildGuiMenu>> children_;
};
@@ -220,7 +273,8 @@ class QtGuiMenu : public QtCommonMenu {
public:
QtGuiMenu();
- void add_item(std::string const& id, std::string const& label) override;
+ void add_item(std::string const& id, std::string const& label,
+ Shortcut const& shotcut) override;
void add_separator() override;
GuiMenu* add_menu(std::string const& label) override;
bool enable_item(const std::string& id, bool enable) override;
@@ -599,12 +653,16 @@ public:
: QtCommonMenu(parent, label), menu_(nullptr) {
}
- void add_item(std::string const& id, std::string const& label) override {
+ void add_item(std::string const& id, std::string const& label,
+ Shortcut const& shortcut) override {
if (!menu_) {
- QtCommonMenu::add_item(id, label);
+ QtCommonMenu::add_item(id, label, shortcut);
return;
}
auto action = menu_->addAction(QString::fromStdString(label));
+ if (shortcut.key != 0) {
+ action->setShortcut(make_key_sequence(shortcut));
+ }
parent_->add_action(id, action);
}
@@ -637,13 +695,16 @@ public:
assert(menu);
menu_ = menu;
- for (auto const& pair : item_) {
- if (pair.first.empty() && pair.second.empty()) {
+ for (auto const& entry : item_) {
+ if (entry.id.empty() && entry.label.empty()) {
menu_->addSeparator();
continue;
}
- auto action = menu_->addAction(QString::fromStdString(pair.second));
- parent_->add_action(pair.first, action);
+ auto action = menu_->addAction(QString::fromStdString(entry.label));
+ if (entry.shortcut.key) {
+ action->setShortcut(make_key_sequence(entry.shortcut));
+ }
+ parent_->add_action(entry.id, action);
}
item_.clear();
@@ -666,12 +727,17 @@ QtGuiMenu::QtGuiMenu()
: QtCommonMenu(), menubar_(nullptr) {
}
-void QtGuiMenu::add_item(std::string const& id, std::string const& label) {
+void QtGuiMenu::add_item(std::string const& id, std::string const& label,
+ Shortcut const& shortcut) {
if (!menubar_) {
- QtCommonMenu::add_item(id, label);
+ QtCommonMenu::add_item(id, label, shortcut);
return;
}
- add_action(id, menubar_->addAction(QString::fromStdString(label)));
+ auto action = menubar_->addAction(QString::fromStdString(label));
+ if (shortcut.key != 0) {
+ action->setShortcut(make_key_sequence(shortcut));
+ }
+ add_action(id, action);
}
void QtGuiMenu::add_separator() {
@@ -725,9 +791,11 @@ void QtGuiMenu::setup(QMenuBar* menubar) {
assert(menubar);
assert(!menubar_);
menubar_ = menubar;
- for (auto const& pair : item_) {
- auto action = menubar_->addAction(QString::fromStdString(pair.second));
- add_action(pair.first, action);
+ for (auto const& entry : item_) {
+ auto action = menubar_->addAction(QString::fromStdString(entry.label));
+ if (entry.shortcut.key) {
+ }
+ add_action(entry.id, action);
}
item_.clear();
@@ -1779,6 +1847,20 @@ GuiMenu* GuiMenu::create() {
}
// static
+const uint32_t GuiMenu::CTRL = 1 << 0;
+const uint32_t GuiMenu::ALT = 1 << 1;
+const uint32_t GuiMenu::SHIFT = 1 << 2;
+const uint32_t GuiMenu::META = 1 << 3;
+
+// static
+const char GuiMenu::ESC = 0x1b;
+const char GuiMenu::UP = 1;
+const char GuiMenu::LEFT = 2;
+const char GuiMenu::RIGHT = 3;
+const char GuiMenu::DOWN = 4;
+const char GuiMenu::RETURN = '\n';
+
+// static
GuiStatusBar* GuiStatusBar::create() {
return new QtGuiStatusBar();
}
diff --git a/src/monitor-gui.cc b/src/monitor-gui.cc
index 2b8cf07..0670b80 100644
--- a/src/monitor-gui.cc
+++ b/src/monitor-gui.cc
@@ -693,12 +693,17 @@ public:
crt_filter_.back().masks.emplace_back("*.*");
auto file = menu_->add_menu("File");
- file->add_item(ACTION_NEW, "New");
- file->add_item(ACTION_OPEN, "Open...");
- file->add_item(ACTION_SAVE, "Save");
- file->add_item(ACTION_SAVE_AS, "Save As...");
+ file->add_item(ACTION_NEW, "New",
+ GuiMenu::Shortcut(GuiMenu::CTRL, 'N'));
+ file->add_item(ACTION_OPEN, "Open...",
+ GuiMenu::Shortcut(GuiMenu::CTRL, 'O'));
+ file->add_item(ACTION_SAVE, "Save",
+ GuiMenu::Shortcut(GuiMenu::CTRL, 'S'));
+ file->add_item(ACTION_SAVE_AS, "Save As...",
+ GuiMenu::Shortcut(GuiMenu::CTRL | GuiMenu::SHIFT, 'S'));
file->add_separator();
- file->add_item(ACTION_EXIT, "Exit");
+ file->add_item(ACTION_EXIT, "Quit",
+ GuiMenu::Shortcut(GuiMenu::CTRL, 'Q'));
menu_->enable_item(ACTION_SAVE, false);
@@ -716,14 +721,19 @@ public:
auto proxy = menu_->add_menu("Proxy");
proxy->add_item(ACTION_SETUP, "Setup...");
- proxy->add_item(ACTION_CONNECT, "Connect...");
- proxy->add_item(ACTION_DISCONNECT, "Disconnect");
+ proxy->add_item(ACTION_CONNECT, "Connect...",
+ GuiMenu::Shortcut(GuiMenu::CTRL, 'C'));
+ proxy->add_item(ACTION_DISCONNECT, "Disconnect",
+ GuiMenu::Shortcut(GuiMenu::CTRL, 'D'));
auto edit = menu_->add_menu("Edit");
- edit->add_item(ACTION_COPY_TEXT, "Copy");
- edit->add_item(ACTION_COPY_RAW, "Copy binary");
+ edit->add_item(ACTION_COPY_TEXT, "Copy",
+ GuiMenu::Shortcut(GuiMenu::CTRL, 'C'));
+ edit->add_item(ACTION_COPY_RAW, "Copy binary",
+ GuiMenu::Shortcut(GuiMenu::CTRL | GuiMenu::SHIFT, 'C'));
edit->add_separator();
- edit->add_item(ACTION_JUMP, "Jump to related");
+ edit->add_item(ACTION_JUMP, "Jump to related",
+ GuiMenu::Shortcut(GuiMenu::CTRL, ' '));
edit->add_separator();
edit->add_item(ACTION_CLEAR, "Clear");
#if HAVE_SSL
@@ -731,7 +741,8 @@ public:
tools->add_item(ACTION_GENERATE_CA, "Generate CA...");
#endif // HAVE_SSL
auto help = menu_->add_menu("Help");
- help->add_item(ACTION_ABOUT, "About...");
+ help->add_item(ACTION_ABOUT, "About...",
+ GuiMenu::Shortcut(0, 1));
help->add_item(ACTION_PROXY_LOG, "Proxy log...");
main_->set_menu(menu_.get());
main_->set_statusbar(statusbar_.get());