/* -*- mode: C++; tab-width: 4; c-basic-offset: 4; -*- */ /* AbiSource Program Utilities * Copyright (C) 1998-2000 AbiSource, Inc. * * 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, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA * 02111-1307, USA. */ #include #include #include #include #include #include #include #include "ut_types.h" #include "ut_stack.h" #include "ut_string.h" #include "ut_string_class.h" #include "ut_debugmsg.h" #include "xap_Types.h" #include "ev_UnixMenu.h" #include "ev_UnixMenuBar.h" #include "ev_UnixMenuPopup.h" #include "xap_UnixApp.h" #include "xap_UnixFrame.h" #include "ev_UnixKeyboard.h" #include "ev_Menu_Layouts.h" #include "ev_Menu_Actions.h" #include "ev_Menu_Labels.h" #include "ev_EditEventMapper.h" #include "ut_string_class.h" /*****************************************************************/ class _wd // a private little class to help { // us remember all the widgets that public: // we create... _wd(EV_UnixMenu * pUnixMenu, XAP_Menu_Id id) { m_pUnixMenu = pUnixMenu; m_id = id; m_accelGroup = NULL; }; ~_wd(void) { }; static void s_onActivate(GtkWidget * /* widget */, gpointer callback_data) { // this is a static callback method and does not have a 'this' pointer. // map the user_data into an object and dispatch the event. _wd * wd = (_wd *) callback_data; UT_ASSERT(wd); wd->m_pUnixMenu->menuEvent(wd->m_id); }; static void s_onMenuItemSelect(GtkWidget * widget, gpointer data) { UT_ASSERT(widget && data); _wd * wd = (_wd *) data; UT_ASSERT(wd && wd->m_pUnixMenu); XAP_UnixFrame * pFrame = wd->m_pUnixMenu->getFrame(); UT_ASSERT(pFrame); EV_Menu_Label * pLabel = wd->m_pUnixMenu->getLabelSet()->getLabel(wd->m_id); if (!pLabel) { pFrame->setStatusMessage(NULL); return; } const char * szMsg = pLabel->getMenuStatusMessage(); if (!szMsg || !*szMsg) szMsg = "TODO This menu item doesn't have a StatusMessage defined."; pFrame->setStatusMessage(szMsg); }; static void s_onMenuItemDeselect(GtkWidget * widget, gpointer data) { UT_ASSERT(widget && data); _wd * wd = (_wd *) data; UT_ASSERT(wd && wd->m_pUnixMenu); XAP_UnixFrame * pFrame = wd->m_pUnixMenu->getFrame(); UT_ASSERT(pFrame); pFrame->setStatusMessage(NULL); }; static void s_onInitMenu(GtkMenuItem * menuItem, gpointer callback_data) { _wd * wd = (_wd *) callback_data; UT_ASSERT(wd); wd->m_pUnixMenu->refreshMenu(wd->m_pUnixMenu->getFrame()->getCurrentView()); // attach this new menu's accel group to be triggered off itself gtk_accel_group_attach(wd->m_accelGroup, GTK_OBJECT(menuItem)); gtk_accel_group_lock(wd->m_accelGroup); }; static void s_onDestroyMenu(GtkMenuItem * menuItem, gpointer callback_data) { _wd * wd = (_wd *) callback_data; UT_ASSERT(wd); // we always clear the status bar when a menu goes away, so we don't // leave a message behind XAP_UnixFrame * pFrame = wd->m_pUnixMenu->getFrame(); UT_ASSERT(pFrame); pFrame->setStatusMessage(NULL); // bind this menuitem to its parent menu gtk_accel_group_detach(wd->m_accelGroup, GTK_OBJECT(menuItem)); gtk_accel_group_unlock(wd->m_accelGroup); }; // GTK wants to run popup menus asynchronously, but we want synchronous, // so we need to do a gtk_main_quit() on our own to show we're done // with our modal work. static void s_onDestroyPopupMenu(GtkMenuItem * menuItem, gpointer callback_data) { // do the grunt work s_onDestroyMenu(menuItem, callback_data); gtk_main_quit(); }; GtkAccelGroup * m_accelGroup; EV_UnixMenu * m_pUnixMenu; XAP_Menu_Id m_id; }; /*****************************************************************/ /* Unlike the Win32 version, which uses a \t (tab) to seperate the feature from the mnemonic in a single label string, this function returns two strings, to be put into the two seperate labels in a Gtk menu item. Oh, and these are static buffers, don't call this function twice and expect previous return pointers to have the same values at their ends. */ static const char ** _ev_GetLabelName(XAP_UnixApp * pUnixApp, XAP_UnixFrame * pUnixFrame, const EV_Menu_Action * pAction, const EV_Menu_Label * pLabel) { static const char * data[2] = {NULL, NULL}; // hit the static pointers back to null each time around data[0] = NULL; data[1] = NULL; const char * szLabelName; if (pAction->hasDynamicLabel()) szLabelName = pAction->getDynamicLabel(pUnixFrame,pLabel); else szLabelName = pLabel->getMenuLabel(); if (!szLabelName || !*szLabelName) return data; // which will be two nulls now static UT_String accelbuf; { // see if this has an associated keybinding const char * szMethodName = pAction->getMethodName(); if (szMethodName) { const EV_EditMethodContainer * pEMC = pUnixApp->getEditMethodContainer(); UT_ASSERT(pEMC); EV_EditMethod * pEM = pEMC->findEditMethodByName(szMethodName); UT_ASSERT(pEM); // make sure it's bound to something const EV_EditEventMapper * pEEM = pUnixFrame->getEditEventMapper(); UT_ASSERT(pEEM); const char * string = pEEM->getShortcutFor(pEM); if (string && *string) accelbuf = string; else // zero it out for this round accelbuf = ""; } } // set shortcut mnemonic, if any if (!accelbuf.empty()) data[1] = accelbuf.c_str(); if (!pAction->raisesDialog()) { data[0] = szLabelName; return data; } // append "..." to menu item if it raises a dialog static char buf[128]; memset(buf,0,NrElements(buf)); strncpy(buf,szLabelName,NrElements(buf)-4); strcat(buf,"..."); data[0] = buf; return data; } /*****************************************************************/ EV_UnixMenu::EV_UnixMenu(XAP_UnixApp * pUnixApp, XAP_UnixFrame * pUnixFrame, const char * szMenuLayoutName, const char * szMenuLabelSetName) : EV_Menu(pUnixApp, pUnixApp->getEditMethodContainer(), szMenuLayoutName, szMenuLabelSetName), m_pUnixApp(pUnixApp), m_pUnixFrame(pUnixFrame) { m_accelGroup = gtk_accel_group_new(); } EV_UnixMenu::~EV_UnixMenu() { m_vecMenuWidgets.clear(); } XAP_UnixFrame * EV_UnixMenu::getFrame() { return m_pUnixFrame; } bool EV_UnixMenu::menuEvent(XAP_Menu_Id id) { // user selected something from the menu. // invoke the appropriate function. // return true if handled. const EV_Menu_ActionSet * pMenuActionSet = m_pUnixApp->getMenuActionSet(); UT_ASSERT(pMenuActionSet); const EV_Menu_Action * pAction = pMenuActionSet->getAction(id); UT_ASSERT(pAction); const char * szMethodName = pAction->getMethodName(); if (!szMethodName) return false; const EV_EditMethodContainer * pEMC = m_pUnixApp->getEditMethodContainer(); UT_ASSERT(pEMC); EV_EditMethod * pEM = pEMC->findEditMethodByName(szMethodName); UT_ASSERT(pEM); // make sure it's bound to something UT_String script_name(pAction->getScriptName()); invokeMenuMethod(m_pUnixFrame->getCurrentView(), pEM, script_name); return true; } static const char * _ev_FakeName(const char * sz, UT_uint32 k) { // construct a temporary string static char buf[128]; UT_ASSERT(strlen(sz)<120); sprintf(buf,"%s%d",sz,k); return buf; } /* This function is quirky because GTK (up to 1.2.0) is. When creating accelerators for menu items, one calls "guint gtk_label_parse_uline(string)", which looks for underscores in the string, and makes each character after an underscore underlined, throws out the underscore, and returns the character the FIRST underscore precedes. I think it should quit at the first underscore, since it can only return one char, but it doesn't. It does know that underscores can be escaped by preceding them with another underscore. This function skips the first underscore, but then pads out all the others, so that feeding it to the gtk_label_parse_uline() will do the "right" thing. You get to free the string this returns. */ static char * _ev_skip_first_underscore_pad_rest(const char * szString) { UT_ASSERT(szString); // create a GString to operate on, using insert operations GString * string = g_string_new(szString); gint underscoreCount = 0; for (gint a = 0; a < string->len; a++) { if (string->str[a] == '_') { if (underscoreCount > 0) { // insert character and increment index (a) g_string_insert_c(string, a, '_'); a++; } underscoreCount++; } } char * newstring = NULL; UT_cloneString(newstring, string->str); // go ahead and free segment; we have a copy g_string_free(string, TRUE); return newstring; } static char _ev_get_underlined_char(const char * szString) { UT_ASSERT(szString); // return the char right after the underline const char * p = szString; while (*p && *(p+1)) { if (*p == '_') return *++p; else p++; } return 0; } static void _ev_strip_underline(char * bufResult, const char * szString) { UT_ASSERT(szString && bufResult); const char * pl = szString; char * b = bufResult; while (*pl) { if (*pl == '_') pl++; else *b++ = *pl++; } *b = 0; } static void _ev_convert(char * bufResult, const char * szString) { UT_ASSERT(szString && bufResult); strcpy(bufResult, szString); char * pl = bufResult; while (*pl) { if (*pl == '&') *pl = '_'; pl++; } } bool EV_UnixMenu::synthesizeMenu(GtkWidget * wMenuRoot) { UT_DEBUGMSG(("EV_UnixMenu::synthesizeMenu\n")); // create a GTK menu from the info provided. const EV_Menu_ActionSet * pMenuActionSet = m_pUnixApp->getMenuActionSet(); UT_ASSERT(pMenuActionSet); UT_uint32 nrLabelItemsInLayout = m_pMenuLayout->getLayoutItemCount(); UT_ASSERT(nrLabelItemsInLayout > 0); // we keep a stack of the widgets so that we can properly // parent the menu items and deal with nested pull-rights. UT_uint32 tmp = 0; bool bResult; UT_Stack stack; stack.push(wMenuRoot); for (UT_uint32 k = 0; (k < nrLabelItemsInLayout); k++) { EV_Menu_LayoutItem * pLayoutItem = m_pMenuLayout->getLayoutItem(k); UT_ASSERT(pLayoutItem); XAP_Menu_Id id = pLayoutItem->getMenuId(); // VERY BAD HACK! It will be here until I fix the const correctness of all the functions // using EV_Menu_Action const EV_Menu_Action * pAction = pMenuActionSet->getAction(id); UT_ASSERT(pAction); const EV_Menu_Label * pLabel = m_pMenuLabelSet->getLabel(id); UT_ASSERT(pLabel); // get the name for the menu item const char * szLabelName; const char * szMnemonicName; switch (pLayoutItem->getMenuLayoutFlags()) { case EV_MLF_Normal: { const char ** data = _ev_GetLabelName(m_pUnixApp, m_pUnixFrame, pAction, pLabel); szLabelName = data[0]; szMnemonicName = data[1]; if (szLabelName && *szLabelName) { char buf[1024]; // convert label into underscored version _ev_convert(buf, szLabelName); // create the item with the underscored label GtkWidget * w; if ( !pAction->isCheckable() ) w = gtk_menu_item_new(); else { w = gtk_check_menu_item_new(); // Make the checkbox visible at all times. Much more informative to the user // and less confusing. gtk_check_menu_item_set_show_toggle (GTK_CHECK_MENU_ITEM(w), TRUE); } GtkWidget * hbox = gtk_hbox_new(FALSE, 20); gtk_widget_show(hbox); gtk_container_add(GTK_CONTAINER(w), hbox); // label widget GtkWidget * label; label = gtk_accel_label_new("SHOULD NOT APPEAR"); // get a newly padded underscore version char * padString = _ev_skip_first_underscore_pad_rest(buf); UT_ASSERT(padString); guint keyCode = gtk_label_parse_uline(GTK_LABEL(label), padString); FREEP(padString); // either path above filled out the label pointer, so pack it // in the hbox gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.5); gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0); if (szMnemonicName && *szMnemonicName) { GtkWidget * mlabel = gtk_accel_label_new(szMnemonicName); UT_ASSERT(mlabel); gtk_misc_set_alignment(GTK_MISC(mlabel), 0.0, 0.5); gtk_box_pack_end(GTK_BOX(hbox), mlabel, FALSE, FALSE, 0); gtk_widget_show(mlabel); } gtk_widget_show(GTK_WIDGET(label)); gtk_widget_show(w); // set menu data to relate to class gtk_object_set_user_data(GTK_OBJECT(w),this); // create callback info data for action handling _wd * wd = new _wd(this, id); UT_ASSERT(wd); // find parent menu item GtkWidget * wParent; bResult = stack.viewTop((void **)&wParent); UT_ASSERT(bResult); gtk_object_set_data(GTK_OBJECT(wMenuRoot), szLabelName, w); // bury in parent gtk_menu_append(GTK_MENU(wParent), w); // connect callbacks gtk_signal_connect(GTK_OBJECT(w), "activate", GTK_SIGNAL_FUNC(_wd::s_onActivate), wd); gtk_signal_connect(GTK_OBJECT(w), "select", GTK_SIGNAL_FUNC(_wd::s_onMenuItemSelect), wd); gtk_signal_connect(GTK_OBJECT(w), "deselect", GTK_SIGNAL_FUNC(_wd::s_onMenuItemDeselect), wd); // we always set the acccelerators in "normal" menu items, but // not top-level (begin_submenus), as shown below gtk_accel_label_set_accel_widget(GTK_ACCEL_LABEL(label), w); // bind to parent item's accel group if ((keyCode != GDK_VoidSymbol)) { gtk_widget_add_accelerator(w, "activate_item", GTK_MENU(wParent)->accel_group, keyCode, 0, GTK_ACCEL_LOCKED); } // item is created, add to class vector m_vecMenuWidgets.addItem(w); break; } // give it a fake, with no label, to make sure it passes the // test that an empty (to be replaced) item in the vector should // have no children GtkWidget * w = gtk_menu_item_new(); UT_ASSERT(w); m_vecMenuWidgets.addItem(w); break; } case EV_MLF_BeginSubMenu: { const char ** data = _ev_GetLabelName(m_pUnixApp, m_pUnixFrame, pAction, pLabel); szLabelName = data[0]; if (szLabelName && *szLabelName) { char buf[1024]; // convert label into underscored version _ev_convert(buf, szLabelName); // create the item widget GtkWidget * w = gtk_menu_item_new(); gtk_object_set_user_data(GTK_OBJECT(w), this); gtk_widget_show(w); // create callback info data for action handling _wd * wd = new _wd(this, id); UT_ASSERT(wd); // find parent menu item GtkWidget * wParent; bResult = stack.viewTop((void **)&wParent); UT_ASSERT(bResult); gtk_object_set_data(GTK_OBJECT(wMenuRoot), szLabelName, w); // bury the widget in parent menu gtk_container_add(GTK_CONTAINER(wParent), w); // since we are starting a new sub menu, create a shell for new items GtkWidget * wsub = gtk_menu_new(); // Here's the tricky part: // If the underlined character conflicts with ANY accelerator // in the keyboard layer, don't do the underline construction, // but instead make a label with no underlines (and no accelerators). // get the underlined value from the candidate label guint keyCode = _ev_get_underlined_char(buf); // this pointer will be filled conditionally later GtkWidget * label = NULL; // GTK triggers the menu accelerators off of MOD1 ***without // regard to what XK_ keysym is bound to it. therefore, if // MOD1 is bound to XK_Alt_{L,R}, we do the following. bool bAltOnMod1 = (ev_UnixKeyboard::getAltModifierMask() == GDK_MOD1_MASK); bool bConflict = false; // Lookup any bindings cooresponding to MOD1-key and the lower-case // version of the underlined char, since all the menus ignore upper // case (SHIFT-MOD1-[char]) invokations of accelerators. if (bAltOnMod1) { EV_EditEventMapper * pEEM = m_pUnixFrame->getEditEventMapper(); UT_ASSERT(pEEM); EV_EditMethod * pEM = NULL; pEEM->Keystroke(EV_EKP_PRESS|EV_EMS_ALT|tolower(keyCode),&pEM); // if the pointer is valid, there is a conflict bConflict = (pEM != NULL); } if (bConflict) { // construct the label with NO underlined text and // no accelerators bound char * dup = NULL; // clone string just for the space it gives us, the data // is trashed by _ev_strip_underlines() UT_cloneString(dup, buf); // get a clean string _ev_strip_underline(dup, buf); label = gtk_accel_label_new(dup); if (dup) { free(dup); dup = NULL; } } else { label = gtk_accel_label_new("SHOULD NOT APPEAR"); // get a newly padded underscore version char * padString = _ev_skip_first_underscore_pad_rest(buf); UT_ASSERT(padString); guint keyCode = gtk_label_parse_uline(GTK_LABEL(label), padString); FREEP(padString); if ((keyCode != GDK_VoidSymbol)) { // bind to top level if parent is top level if (wParent == wMenuRoot) { gtk_widget_add_accelerator(w, "activate_item", m_accelGroup, keyCode, GDK_MOD1_MASK, GTK_ACCEL_LOCKED); } else { // just bind to be triggered by parent gtk_widget_add_accelerator(w, "activate_item", GTK_MENU(wParent)->accel_group, keyCode, 0, GTK_ACCEL_LOCKED); } // TODO: Should these happen for every widget, even if // TODO: we don't register an accelerator? gtk_accel_label_set_accel_widget(GTK_ACCEL_LABEL(label), w); } } UT_ASSERT(label); gtk_widget_show(label); // we always set an accel group, even if we don't actually bind any // to this widget wd->m_accelGroup = gtk_accel_group_new(); gtk_menu_set_accel_group(GTK_MENU(wsub), wd->m_accelGroup); // This stuff happens to every label: // set alignment inside the label; add to container gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.5); gtk_container_add(GTK_CONTAINER(w), label); // menu items with sub menus attached (w) get this signal // bound to their children so they can trigger a refresh gtk_signal_connect(GTK_OBJECT(wsub), "map", GTK_SIGNAL_FUNC(_wd::s_onInitMenu), wd); gtk_signal_connect(GTK_OBJECT(wsub), "unmap", GTK_SIGNAL_FUNC(_wd::s_onDestroyMenu), wd); gtk_object_set_user_data(GTK_OBJECT(wsub),this); // add to menu bar gtk_object_set_data(GTK_OBJECT(wMenuRoot), _ev_FakeName(szLabelName, tmp++), wsub); gtk_menu_item_set_submenu(GTK_MENU_ITEM(w), wsub); stack.push(wsub); // item is created, add to vector m_vecMenuWidgets.addItem(w); break; } // give it a fake, with no label, to make sure it passes the // test that an empty (to be replaced) item in the vector should // have no children GtkWidget * w = gtk_menu_item_new(); UT_ASSERT(w); m_vecMenuWidgets.addItem(w); break; } case EV_MLF_EndSubMenu: { // pop and inspect GtkWidget * w; bResult = stack.pop((void **)&w); UT_ASSERT(bResult); // item is created (albeit empty in this case), add to vector m_vecMenuWidgets.addItem(w); break; } case EV_MLF_Separator: { GtkWidget * w = gtk_menu_item_new(); UT_ASSERT(w); gtk_widget_set_sensitive(w, FALSE); gtk_object_set_user_data(GTK_OBJECT(w),this); GtkWidget * wParent; bResult = stack.viewTop((void **)&wParent); UT_ASSERT(bResult); gtk_object_set_data(GTK_OBJECT(wMenuRoot), _ev_FakeName("separator",tmp++), w); gtk_widget_show(w); gtk_menu_append(GTK_MENU(wParent),w); // item is created, add to class vector m_vecMenuWidgets.addItem(w); break; } case EV_MLF_BeginPopupMenu: case EV_MLF_EndPopupMenu: m_vecMenuWidgets.addItem(NULL); // reserve slot in vector so indexes will be in sync break; default: UT_ASSERT(0); break; } } // make sure our last item on the stack is the one we started with GtkWidget * wDbg = NULL; bResult = stack.pop((void **)&wDbg); UT_ASSERT(bResult); UT_ASSERT(wDbg == wMenuRoot); // we also have to bind the top level window to our // accelerator group for this menu... it needs to join in // on the action. gtk_accel_group_attach(m_accelGroup, GTK_OBJECT(m_pUnixFrame->getTopLevelWindow())); gtk_accel_group_lock(m_accelGroup); return true; } bool EV_UnixMenu::_refreshMenu(AV_View * pView, GtkWidget * wMenuRoot) { // update the status of stateful items on menu bar. const EV_Menu_ActionSet * pMenuActionSet = m_pUnixApp->getMenuActionSet(); UT_ASSERT(pMenuActionSet); UT_uint32 nrLabelItemsInLayout = m_pMenuLayout->getLayoutItemCount(); // we keep a stack of the widgets so that we can properly // parent the menu items and deal with nested pull-rights. bool bResult; UT_Stack stack; stack.push(wMenuRoot); // -1 will catch the case where we're inserting and haven't actually // entered into a real menu (only at a top level menu) gint nPositionInThisMenu = -1; for (UT_uint32 k = 0; k < nrLabelItemsInLayout; ++k) { EV_Menu_LayoutItem * pLayoutItem = m_pMenuLayout->getLayoutItem(k); XAP_Menu_Id id = pLayoutItem->getMenuId(); const EV_Menu_Action * pAction = pMenuActionSet->getAction(id); const EV_Menu_Label * pLabel = m_pMenuLabelSet->getLabel(id); switch (pLayoutItem->getMenuLayoutFlags()) { case EV_MLF_Normal: { // Keep track of where we are in this menu; we get cut down // to zero on the creation of each new submenu. nPositionInThisMenu++; // see if we need to enable/disable and/or check/uncheck it. bool bEnable = true; bool bCheck = false; if (pAction->hasGetStateFunction()) { EV_Menu_ItemState mis = pAction->getMenuItemState(pView); if (mis & EV_MIS_Gray) bEnable = false; if (mis & EV_MIS_Toggled) bCheck = true; } // must have an entry for each and every layout item in the vector UT_ASSERT((k < m_vecMenuWidgets.getItemCount() - 1)); // Get the dynamic label const char ** data = _ev_GetLabelName(m_pUnixApp, m_pUnixFrame, pAction, pLabel); const char * szLabelName = data[0]; // First we check to make sure the item exists. If it does not, // we create it and continue on. GList * testchildren = gtk_container_children(GTK_CONTAINER(m_vecMenuWidgets.getNthItem(k))); if (!testchildren) { // This should be the only place refreshMenu touches // callback hooks, since this handles the case a widget doesn't // exist for a given layout item if (szLabelName && *szLabelName) { // find parent menu item GtkWidget * wParent; bResult = stack.viewTop((void **)&wParent); UT_ASSERT(bResult); char labelbuf[1024]; // convert label into underscored version _ev_convert(labelbuf, szLabelName); // create a label GtkLabel * label = GTK_LABEL(gtk_accel_label_new("SHOULD NOT APPEAR")); UT_ASSERT(label); // get a newly padded underscore version char * padString = _ev_skip_first_underscore_pad_rest(labelbuf); UT_ASSERT(padString); gtk_label_parse_uline(GTK_LABEL(label), padString); FREEP(padString); // create the item with the underscored label GtkWidget * w = gtk_menu_item_new(); UT_ASSERT(w); // show and add the label to our menu item gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.5); gtk_container_add(GTK_CONTAINER(w), GTK_WIDGET(label)); gtk_accel_label_set_accel_widget(GTK_ACCEL_LABEL(label), w); gtk_widget_show(GTK_WIDGET(label)); gtk_widget_show(w); // set menu data to relate to class gtk_object_set_user_data(GTK_OBJECT(w),this); // create callback info data for action handling _wd * wd = new _wd(this, id); UT_ASSERT(wd); // set parent data stuff gtk_object_set_data(GTK_OBJECT(wMenuRoot), szLabelName, w); // bury in parent gtk_menu_insert(GTK_MENU(GTK_MENU_ITEM(wParent)->submenu), w, nPositionInThisMenu); // connect callbacks gtk_signal_connect(GTK_OBJECT(w), "activate", GTK_SIGNAL_FUNC(_wd::s_onActivate), wd); gtk_signal_connect(GTK_OBJECT(w), "select", GTK_SIGNAL_FUNC(_wd::s_onMenuItemSelect), wd); gtk_signal_connect(GTK_OBJECT(w), "deselect", GTK_SIGNAL_FUNC(_wd::s_onMenuItemDeselect), wd); // we do NOT add a new item, we point the existing index at our new widget // (update the pointers) void ** old = NULL; if (m_vecMenuWidgets.setNthItem(k, w, old)) { UT_DEBUGMSG(("Could not update dynamic menu widget vector item %s.\n", k)); UT_ASSERT(0); } break; } else { // do not create a widget if the label is blank, it should not appear in the // menu } } // No dynamic label, check/enable if (!pAction->hasDynamicLabel()) { // if no dynamic label, all we need to do // is enable/disable and/or check/uncheck it. GtkWidget * item = (GtkWidget *) m_vecMenuWidgets.getNthItem(k); UT_ASSERT(item); // check boxes if (GTK_IS_CHECK_MENU_ITEM(item)) // must use this line instead of calling // gtk_check_menu_item_set_active(...) because it // generates an "activate" signal -- shack / sterwill GTK_CHECK_MENU_ITEM(item)->active = bCheck; // all get the gray treatment gtk_widget_set_sensitive(GTK_WIDGET(item), bEnable); break; } // Get the item GtkWidget * item = (GtkWidget *) m_vecMenuWidgets.getNthItem(k); // if item is null, there is no widget for it, so ignore its attributes for // this pass if (!item) break; // Dynamic label, check for remove bool bRemoveIt = (!szLabelName || !*szLabelName); if (bRemoveIt) { // unbind all accelerators gtk_widget_remove_accelerators(item, "activate_item", FALSE); // wipe it out gtk_widget_destroy(item); // we must also mark this item in the vector as "removed", // which means setting [k] equal to a fake item as done // on creation of dynamic items. // give it a fake, with no label, to make sure it passes the // test that an empty (to be replaced) item in the vector should // have no children GtkWidget * w = gtk_menu_item_new(); UT_ASSERT(w); void ** blah = NULL; if(m_vecMenuWidgets.setNthItem(k, w, blah)) { UT_DEBUGMSG(("Could not update dynamic menu widget vector item %s.\n", k)); UT_ASSERT(0); } break; } // Dynamic label, check for add/change // We always change the labels every time, it's actually cheaper // than doing the test for conditional changes. { // Get a list of children. If there are any, destroy them GList * children = gtk_container_children(GTK_CONTAINER(item)); if (children) { // Get the first item in the list GList * firstItem = g_list_first(children); UT_ASSERT(firstItem); // First item's data should be the label, since we added it first // in construction. GtkWidget * labelChild = GTK_WIDGET(firstItem->data); UT_ASSERT(labelChild); // destroy the current label gtk_container_remove(GTK_CONTAINER(item), labelChild); // unbind all accelerators gtk_widget_remove_accelerators(item, "activate_item", FALSE); //gtk_widget_destroy(labelChild); } // create a new updated label char labelbuf[1024]; // convert label into underscored version _ev_convert(labelbuf, szLabelName); // create a label GtkLabel * label = GTK_LABEL(gtk_accel_label_new("SHOULD NOT APPEAR")); UT_ASSERT(label); // get a newly padded underscore version char * padString = _ev_skip_first_underscore_pad_rest(labelbuf); UT_ASSERT(padString); guint keyCode = gtk_label_parse_uline(label, padString); FREEP(padString); // show and add the label to our menu item gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.5); gtk_container_add(GTK_CONTAINER(item), GTK_WIDGET(label)); gtk_accel_label_set_accel_widget(GTK_ACCEL_LABEL(label), item); gtk_widget_show(GTK_WIDGET(label)); // bind to parent item's accel group if ((keyCode != GDK_VoidSymbol))// && parent_accel_group) { gtk_widget_add_accelerator(item, "activate_item", GTK_MENU(item->parent)->accel_group, keyCode, 0, GTK_ACCEL_LOCKED); } // finally, enable/disable and/or check/uncheck it. if (GTK_IS_CHECK_MENU_ITEM(item)) GTK_CHECK_MENU_ITEM(item)->active = bCheck; gtk_widget_set_sensitive((GtkWidget *) item, bEnable); } // we are done with this menu item } break; case EV_MLF_Separator: nPositionInThisMenu++; break; case EV_MLF_BeginSubMenu: { nPositionInThisMenu = -1; // we need to nest sub menus to have some sort of context so // we can parent menu items GtkWidget * item = (GtkWidget *) m_vecMenuWidgets.getNthItem(k); UT_ASSERT(item); bool bEnable = true; if (pAction->hasGetStateFunction()) { EV_Menu_ItemState mis = pAction->getMenuItemState(pView); if (mis & EV_MIS_Gray) bEnable = false; } gtk_widget_set_sensitive((GtkWidget *) item, bEnable); // must have an entry for each and every layout item in the vector stack.push(item); break; } case EV_MLF_EndSubMenu: { GtkWidget * item = NULL; bResult = stack.pop((void **)&item); UT_ASSERT(bResult); break; } case EV_MLF_BeginPopupMenu: case EV_MLF_EndPopupMenu: break; default: UT_ASSERT(0); break; } } GtkWidget * wDbg = NULL; bResult = stack.pop((void **)&wDbg); UT_ASSERT(bResult); UT_ASSERT(wDbg == wMenuRoot); return true; } /*! * That will add a new menu entry for the menu item at layout_pos. * * @param layout_pos UT_uint32 with the relative position of the item in the * menu. * @return true if there were no problems. False elsewere. */ bool EV_UnixMenu::_doAddMenuItem(UT_uint32 layout_pos) { UT_DEBUGMSG(("JCA: layout_pos = [%d]\n", layout_pos)); if (layout_pos > 0) { UT_DEBUGMSG(("Trying to insert at [%d] in a vector of size [%d].\n", layout_pos, m_vecMenuWidgets.size())); UT_sint32 err = m_vecMenuWidgets.insertItemAt(NULL, layout_pos); if (err != 0) UT_DEBUGMSG(("Error [%d] inserting NULL item in a ut_vector.\n", err)); return (err == 0); } return false; } /*****************************************************************/ EV_UnixMenuBar::EV_UnixMenuBar(XAP_UnixApp * pUnixApp, XAP_UnixFrame * pUnixFrame, const char * szMenuLayoutName, const char * szMenuLabelSetName) : EV_UNIXBASEMENU(pUnixApp, pUnixFrame, szMenuLayoutName, szMenuLabelSetName) { } EV_UnixMenuBar::~EV_UnixMenuBar() { } void EV_UnixMenuBar::destroy(void) { gtk_widget_destroy(m_wHandleBox); } bool EV_UnixMenuBar::synthesizeMenuBar() { GtkWidget * wVBox = m_pUnixFrame->getVBoxWidget(); m_wHandleBox = gtk_handle_box_new(); UT_ASSERT(m_wHandleBox); // Just create, don't show the menu bar yet. It is later added // to a 3D handle box and shown m_wMenuBar = gtk_menu_bar_new(); synthesizeMenu(m_wMenuBar); // show up the properly connected menu structure gtk_widget_show(m_wMenuBar); // pack it in a handle box gtk_container_add(GTK_CONTAINER(m_wHandleBox), m_wMenuBar); gtk_widget_show(m_wHandleBox); // put it at position 1 in the vbox gtk_box_pack_start(GTK_BOX(wVBox), m_wHandleBox, FALSE, TRUE, 0); return true; } bool EV_UnixMenuBar::rebuildMenuBar() { GtkWidget * wVBox = m_pUnixFrame->getVBoxWidget(); m_wHandleBox = gtk_handle_box_new(); UT_ASSERT(m_wHandleBox); // Just create, don't show the menu bar yet. It is later added // to a 3D handle box and shown m_wMenuBar = gtk_menu_bar_new(); synthesizeMenu(m_wMenuBar); // show up the properly connected menu structure gtk_widget_show(m_wMenuBar); // pack it in a handle box gtk_container_add(GTK_CONTAINER(m_wHandleBox), m_wMenuBar); gtk_widget_show(m_wHandleBox); // put it at position 1 in the vbox gtk_box_pack_start(GTK_BOX(wVBox), m_wHandleBox, FALSE, TRUE, 0); // was start gtk_box_reorder_child(GTK_BOX(wVBox), m_wHandleBox,0); return true; } bool EV_UnixMenuBar::refreshMenu(AV_View * pView) { // this makes an exception for initialization where a view // might not exist... silly to refresh the menu then; it will // happen in due course to its first display if (pView) return _refreshMenu(pView,m_wMenuBar); return true; } /*****************************************************************/ EV_UnixMenuPopup::EV_UnixMenuPopup(XAP_UnixApp * pUnixApp, XAP_UnixFrame * pUnixFrame, const char * szMenuLayoutName, const char * szMenuLabelSetName) : EV_UNIXBASEMENU(pUnixApp, pUnixFrame, szMenuLayoutName, szMenuLabelSetName) { } EV_UnixMenuPopup::~EV_UnixMenuPopup() { } GtkWidget * EV_UnixMenuPopup::getMenuHandle() const { return m_wMenuPopup; } bool EV_UnixMenuPopup::synthesizeMenuPopup() { m_wMenuPopup = gtk_menu_new(); _wd * wd = new _wd(this, 0); UT_ASSERT(wd); wd->m_accelGroup = gtk_accel_group_new(); gtk_menu_set_accel_group(GTK_MENU(m_wMenuPopup), wd->m_accelGroup); gtk_signal_connect(GTK_OBJECT(m_wMenuPopup), "map", GTK_SIGNAL_FUNC(_wd::s_onInitMenu), wd); gtk_signal_connect(GTK_OBJECT(m_wMenuPopup), "unmap", GTK_SIGNAL_FUNC(_wd::s_onDestroyPopupMenu), wd); gtk_object_set_user_data(GTK_OBJECT(m_wMenuPopup),this); synthesizeMenu(m_wMenuPopup); return true; } bool EV_UnixMenuPopup::refreshMenu(AV_View * pView) { // this makes an exception for initialization where a view // might not exist... silly to refresh the menu then; it will // happen in due course to its first display if (pView) return _refreshMenu(pView, m_wMenuPopup); return true; }