-
Notifications
You must be signed in to change notification settings - Fork 619
Added support of Native Menus in Linux #602
Changes from 8 commits
221b330
2216c01
83f3748
c47819f
04f56c7
6c09c32
354c10e
2f15dfd
ff6abd4
0f67000
98ccf5b
1524d7b
0ecc5c8
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -26,7 +26,7 @@ | |
#include <glib/gstdio.h> | ||
#include <gtk/gtk.h> | ||
#include "appshell_extensions.h" | ||
#include "appshell_extensions_platform.h" | ||
#include "native_menu_model.h" | ||
#include "client_handler.h" | ||
|
||
#include <errno.h> | ||
|
@@ -36,8 +36,12 @@ | |
#include <sys/stat.h> | ||
#include <unistd.h> | ||
#include <X11/Xlib.h> | ||
#include <gdk/gdkkeysyms.h> | ||
#include <algorithm> | ||
#include <sstream> | ||
#include <vector> | ||
|
||
GtkWidget* _menuWidget; | ||
extern CefRefPtr<ClientHandler> g_handler; | ||
|
||
// Supported browsers (order matters): | ||
// - google-chorme | ||
|
@@ -652,69 +656,253 @@ int32 GetPendingFilesToOpen(ExtensionString& files) | |
{ | ||
} | ||
|
||
GtkWidget* GetMenuBar(CefRefPtr<CefBrowser> browser) | ||
static GtkWidget* GetMenuBar(CefRefPtr<CefBrowser> browser) | ||
{ | ||
GtkWidget* window = (GtkWidget*)getMenuParent(browser); | ||
GtkWidget* widget; | ||
GList *children, *iter; | ||
GtkWidget* menuBar = NULL; | ||
|
||
children = gtk_container_get_children(GTK_CONTAINER(window)); | ||
for(iter = children; iter != NULL; iter = g_list_next(iter)) { | ||
widget = (GtkWidget*)iter->data; | ||
|
||
if (GTK_IS_MENU_BAR(widget)) | ||
return widget; | ||
if (GTK_IS_MENU_BAR(widget)) { | ||
menuBar = widget; | ||
break; | ||
} | ||
} | ||
|
||
return NULL; | ||
g_list_free(children); | ||
|
||
return menuBar; | ||
} | ||
|
||
static int GetPosition(const ExtensionString& positionString, const ExtensionString& relativeId, | ||
GtkWidget* container, NativeMenuModel& model) | ||
{ | ||
if (positionString == "" || positionString == "last") | ||
return -1; | ||
else if (positionString == "first") | ||
return 0; | ||
else if (relativeId.size() > 0) { | ||
int relativeTag = model.getTag(relativeId); | ||
GtkWidget* relativeMenuItem = (GtkWidget*) model.getOsItem(relativeTag); | ||
int position = 0; | ||
GList* children = gtk_container_get_children(GTK_CONTAINER(container)); | ||
GList* iter; | ||
for (iter = children; iter != NULL; iter = g_list_next(iter)) { | ||
if (iter->data == relativeMenuItem) | ||
break; | ||
position++; | ||
} | ||
if (iter == NULL) | ||
position = -1; | ||
else if (positionString == "before") | ||
; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. can't you instead set position as 0 here. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We can't set position to 0 here, as in https://github.com/adobe/brackets-shell/pull/602/files#diff-10aa13d798996c2d964d38b1f328ae62R698 we are incrementing value of position, so setting it to 0 will always add the menu in the first index, which is not desired. |
||
else if (positionString == "after") | ||
position++; | ||
else if (positionString == "firstInSection") { | ||
for (; iter != NULL; iter = g_list_previous(iter)) { | ||
if (GTK_IS_SEPARATOR_MENU_ITEM(iter->data)) | ||
break; | ||
position--; | ||
} | ||
position++; | ||
} | ||
else if (positionString == "lastInSection") { | ||
for (; iter != NULL; iter = g_list_next(iter)) { | ||
if (GTK_IS_SEPARATOR_MENU_ITEM(iter->data)) | ||
break; | ||
position++; | ||
} | ||
} | ||
else | ||
position = -1; | ||
g_list_free(children); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. can you point to the documentation in GTK where they specify that you need to explicitly free it? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. also https://developer.gnome.org/gtk3/stable/GtkContainer.html#gtk-container-get-children says:
and the tooltip of There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Just checked the tooltip indeed conveys that we need to free. |
||
return position; | ||
} | ||
else | ||
return -1; | ||
} | ||
|
||
int32 AddMenu(CefRefPtr<CefBrowser> browser, ExtensionString title, ExtensionString command, | ||
ExtensionString position, ExtensionString relativeId) | ||
ExtensionString positionString, ExtensionString relativeId) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. why can't these parameters be passed by const ref? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. These are actually implementation of interface which is used by all platforms, so changing this would mean changing in all the platforms implementation. |
||
{ | ||
// if (tag == kTagNotFound) { | ||
// tag = NativeMenuModel::getInstance(getMenuParent(browser)).getOrCreateTag(command, ExtensionString()); | ||
// } else { | ||
// // menu already there | ||
// return NO_ERROR; | ||
// } | ||
NativeMenuModel& model = NativeMenuModel::getInstance(getMenuParent(browser)); | ||
int tag = model.getTag(command); | ||
if (tag == kTagNotFound) { | ||
tag = model.getOrCreateTag(command, ExtensionString()); | ||
} else { | ||
// menu is already there | ||
return NO_ERROR; | ||
} | ||
|
||
GtkWidget* menuBar = GetMenuBar(browser); | ||
GtkWidget* menuWidget = gtk_menu_new(); | ||
GtkWidget* menuHeader = gtk_menu_item_new_with_label(title.c_str()); | ||
gtk_menu_item_set_submenu(GTK_MENU_ITEM(menuHeader), menuWidget); | ||
gtk_menu_shell_append(GTK_MENU_SHELL(menuBar), menuHeader); | ||
model.setOsItem(tag, menuHeader); | ||
int position = GetPosition(positionString, relativeId, menuBar, model); | ||
if (position >= 0) | ||
gtk_menu_shell_insert(GTK_MENU_SHELL(menuBar), menuHeader, position); | ||
else | ||
gtk_menu_shell_append(GTK_MENU_SHELL(menuBar), menuHeader); | ||
gtk_widget_show(menuHeader); | ||
|
||
// FIXME add lookup for menu widgets | ||
_menuWidget = menuWidget; | ||
|
||
return NO_ERROR; | ||
} | ||
|
||
void FakeCallback() { | ||
static void Callback(GtkMenuItem* menuItem, gpointer userData) { | ||
if (g_handler.get() && g_handler->GetBrowserId()) { | ||
int tag = GPOINTER_TO_INT(userData); | ||
ExtensionString commandId = NativeMenuModel::getInstance(getMenuParent(g_handler->GetBrowser())).getCommandId(tag); | ||
CefRefPtr<CommandCallback> callback = new EditCommandCallback(g_handler->GetBrowser(), commandId); | ||
g_handler->SendJSCommand(g_handler->GetBrowser(), commandId, callback); | ||
} | ||
} | ||
|
||
|
||
ExtensionString GetDisplayKeyString(const ExtensionString& keyStr) | ||
{ | ||
ExtensionString result = keyStr; | ||
|
||
// We get a keyStr that looks like "Ctrl-O", which is the format we | ||
// need for the accelerator table. For displaying in the menu, though, | ||
// we have to change it to "Ctrl+O". Careful not to change the final | ||
// character, though, so "Ctrl--" ends up as "Ctrl+-". | ||
if (result.size() > 0) { | ||
replace(result.begin(), result.end() - 1, '-', '+'); | ||
} | ||
return result; | ||
} | ||
|
||
|
||
int32 ParseShortcut(CefRefPtr<CefBrowser> browser, GtkWidget* entry, ExtensionString& key, GdkModifierType* modifier, guint* keyVal, ExtensionString& commandId) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This function could probably be static if it is not used outside this file. |
||
if (key.size()) { | ||
GtkAccelGroup *accel_group = NULL; | ||
accel_group = gtk_accel_group_new(); | ||
|
||
std::vector<std::string> tokens; | ||
std::istringstream f(key.c_str()); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is the |
||
std::string s; | ||
while (getline(f, s, '-')) { | ||
tokens.push_back(s); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm wondering whether the tokens could be parsed here directly instead of copying them to a vector first and the parsing the vector. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Actually I have used tokens in creating shortcut of type A and also in the fallback, so I won't be able to have fallback if there is no tokens vector. |
||
} | ||
std::string shortcut = ""; | ||
// convert shortcut format | ||
// e.g. Ctrl+A converts to <Ctrl>A whose entry is stored in accelerator table | ||
for (int i=0;i<tokens.size();i++) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. isn't it better to take the size() in a local and use that instead? |
||
if (i != tokens.size() - 1) { | ||
shortcut += "<"; | ||
} | ||
shortcut += tokens[i]; | ||
if (i != tokens.size() - 1) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. why is the last token excluded? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Actually in this we are changing the token string from Ctrl+A to A whose entry is stored in accelerator table, so for the last token there is no need to enclose it in <> |
||
shortcut += ">"; | ||
} | ||
} | ||
gchar* val = (gchar*)shortcut.c_str(); | ||
gtk_accelerator_parse (val, keyVal, modifier); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: everywhere else there is no space between function name and parameters. |
||
|
||
// Fallback | ||
if (!(*keyVal)) { | ||
for (int i=0;i<tokens.size();i++) { | ||
if (tokens[i] == "Ctrl") { | ||
*modifier = GdkModifierType(*modifier | GDK_CONTROL_MASK); | ||
} else if (tokens[i] == "Alt") { | ||
*modifier = GdkModifierType(*modifier | GDK_MOD1_MASK); | ||
} else if (tokens[i] == "Shift") { | ||
*modifier = GdkModifierType(*modifier | GDK_SHIFT_MASK); | ||
} else if (tokens[i] == "Enter") { | ||
*keyVal = GDK_KEY_KP_Enter; | ||
} else if (tokens[i] == "Up" || tokens[i] == "\u2191") { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It is better to use macros or constants instead of these string literals |
||
*keyVal = GDK_KEY_uparrow; | ||
} else if (tokens[i] == "Down" || tokens[i] == "\u2193") { | ||
*keyVal = GDK_KEY_downarrow; | ||
} else if (tokens[i] == "Right") { | ||
*keyVal = GDK_KEY_rightarrow; | ||
} else if (tokens[i] == "Left") { | ||
*keyVal = GDK_KEY_leftarrow; | ||
} else if (tokens[i] == "Space") { | ||
*keyVal = GDK_KEY_KP_Space; | ||
} else if (tokens[i] == "PageUp") { | ||
*keyVal = GDK_KEY_Page_Up; | ||
} else if (tokens[i] == "PageDown") { | ||
*keyVal = GDK_KEY_Page_Down; | ||
} else if (tokens[i] == "[" || tokens[i] == "]" || tokens[i] == "+" || tokens[i] == "." || tokens[i] == "," || tokens[i] == "\\") { | ||
*keyVal = tokens[i][0]; | ||
} else if (tokens[i] == "−") { | ||
*keyVal = GDK_KEY_minus; | ||
} | ||
} | ||
} | ||
if (*keyVal) { | ||
gtk_widget_add_accelerator(entry, "activate", accel_group, *keyVal, *modifier, GTK_ACCEL_VISIBLE); | ||
} else if (shortcut.size()) { | ||
ExtensionString menuTitle; | ||
GetMenuTitle(browser, commandId, menuTitle); | ||
menuTitle += "\t( " + shortcut + " )"; | ||
gtk_menu_item_set_label(GTK_MENU_ITEM(entry), menuTitle.c_str()); | ||
} | ||
} | ||
return NO_ERROR; | ||
} | ||
|
||
int32 AddMenuItem(CefRefPtr<CefBrowser> browser, ExtensionString parentCommand, ExtensionString itemTitle, | ||
ExtensionString command, ExtensionString key, ExtensionString displayStr, | ||
ExtensionString position, ExtensionString relativeId) | ||
ExtensionString positionString, ExtensionString relativeId) | ||
{ | ||
GtkWidget* entry = gtk_menu_item_new_with_label(itemTitle.c_str()); | ||
g_signal_connect(entry, "activate", FakeCallback, NULL); | ||
// FIXME add lookup for menu widgets | ||
gtk_menu_shell_append(GTK_MENU_SHELL(_menuWidget), entry); | ||
NativeMenuModel& model = NativeMenuModel::getInstance(getMenuParent(browser)); | ||
int parentTag = model.getTag(parentCommand); | ||
if (parentTag == kTagNotFound) { | ||
return ERR_NOT_FOUND; | ||
} | ||
|
||
int tag = model.getTag(command); | ||
if (tag == kTagNotFound) { | ||
tag = model.getOrCreateTag(command, parentCommand); | ||
} else { | ||
return NO_ERROR; | ||
} | ||
|
||
GtkWidget* entry; | ||
if (itemTitle == "---") | ||
entry = gtk_separator_menu_item_new(); | ||
else | ||
entry = gtk_menu_item_new_with_label(itemTitle.c_str()); | ||
g_signal_connect(entry, "activate", G_CALLBACK(Callback), GINT_TO_POINTER(tag)); | ||
GdkModifierType modifier = GdkModifierType(0); | ||
guint keyVal = 0; | ||
ExtensionString commandId = model.getCommandId(tag); | ||
model.setOsItem(tag, entry); | ||
ParseShortcut(browser, entry, key, &modifier, &keyVal, commandId); | ||
GtkWidget* menuHeader = (GtkWidget*) model.getOsItem(parentTag); | ||
GtkWidget* menuWidget = gtk_menu_item_get_submenu(GTK_MENU_ITEM(menuHeader)); | ||
int position = GetPosition(positionString, relativeId, menuWidget, model); | ||
if (position >= 0) | ||
gtk_menu_shell_insert(GTK_MENU_SHELL(menuWidget), entry, position); | ||
else | ||
gtk_menu_shell_append(GTK_MENU_SHELL(menuWidget), entry); | ||
gtk_widget_show(entry); | ||
|
||
return NO_ERROR; | ||
} | ||
|
||
int32 RemoveMenu(CefRefPtr<CefBrowser> browser, const ExtensionString& commandId) | ||
{ | ||
return NO_ERROR; | ||
// works for menu and menu item | ||
return RemoveMenuItem(browser, commandId); | ||
} | ||
|
||
int32 RemoveMenuItem(CefRefPtr<CefBrowser> browser, const ExtensionString& commandId) | ||
{ | ||
NativeMenuModel& model = NativeMenuModel::getInstance(getMenuParent(browser)); | ||
int tag = model.getTag(commandId); | ||
if (tag == kTagNotFound) | ||
return ERR_NOT_FOUND; | ||
GtkWidget* menuItem = (GtkWidget*) model.getOsItem(tag); | ||
model.removeMenuItem(commandId); | ||
gtk_widget_destroy(menuItem); | ||
return NO_ERROR; | ||
} | ||
|
||
|
@@ -725,16 +913,51 @@ int32 GetMenuItemState(CefRefPtr<CefBrowser> browser, ExtensionString commandId, | |
|
||
int32 SetMenuTitle(CefRefPtr<CefBrowser> browser, ExtensionString commandId, ExtensionString menuTitle) | ||
{ | ||
NativeMenuModel& model = NativeMenuModel::getInstance(getMenuParent(browser)); | ||
int tag = model.getTag(commandId); | ||
if (tag == kTagNotFound) | ||
return ERR_NOT_FOUND; | ||
GtkWidget* menuItem = (GtkWidget*) model.getOsItem(tag); | ||
ExtensionString shortcut; | ||
GetMenuTitle(browser, commandId, shortcut); | ||
size_t pos = shortcut.find("\t"); | ||
if (pos != -1) { | ||
shortcut = shortcut.substr(pos); | ||
} else { | ||
shortcut = ""; | ||
} | ||
|
||
ExtensionString newTitle = menuTitle; | ||
if (shortcut.length() > 0) { | ||
newTitle += shortcut; | ||
} | ||
gtk_menu_item_set_label(GTK_MENU_ITEM(menuItem), menuTitle.c_str()); | ||
return NO_ERROR; | ||
} | ||
|
||
int32 GetMenuTitle(CefRefPtr<CefBrowser> browser, ExtensionString commandId, ExtensionString& menuTitle) | ||
{ | ||
NativeMenuModel& model = NativeMenuModel::getInstance(getMenuParent(browser)); | ||
int tag = model.getTag(commandId); | ||
if (tag == kTagNotFound) | ||
return ERR_NOT_FOUND; | ||
GtkWidget* menuItem = (GtkWidget*) model.getOsItem(tag); | ||
menuTitle = gtk_menu_item_get_label(GTK_MENU_ITEM(menuItem)); | ||
return NO_ERROR; | ||
} | ||
|
||
int32 SetMenuItemShortcut(CefRefPtr<CefBrowser> browser, ExtensionString commandId, ExtensionString shortcut, ExtensionString displayStr) | ||
{ | ||
NativeMenuModel model = NativeMenuModel::getInstance(getMenuParent(browser)); | ||
int32 tag = model.getTag(commandId); | ||
if (tag == kTagNotFound) { | ||
return ERR_NOT_FOUND; | ||
} | ||
GtkWidget* entry = (GtkWidget*) model.getOsItem(tag); | ||
GdkModifierType modifier = GdkModifierType(0); | ||
guint keyVal = 0; | ||
ParseShortcut(browser, entry, shortcut, &modifier, &keyVal, commandId); | ||
|
||
return NO_ERROR; | ||
} | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
use empty() instead