1 /*
2   FXiTe - The Free eXtensIble Text Editor
3   Copyright (c) 2009-2013 Jeffrey Pohlmeyer <yetanothergeek@gmail.com>
4 
5   This program is free software; you can redistribute it and/or modify it
6   under the terms of the GNU General Public License version 3 as
7   published by the Free Software Foundation.
8 
9   This software is distributed in the hope that it will be useful,
10   but WITHOUT ANY WARRANTY; without even the implied warranty of
11   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12   GNU General Public License for more details.
13 
14   You should have received a copy of the GNU General Public License
15   along with this program; if not, write to the Free Software
16   Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
17 */
18 
19 #include <cctype>
20 
21 #include <fx.h>
22 #include "appname.h"
23 #include "appwin.h"
24 #include "color_funcs.h"
25 #include "scidoc.h"
26 #include "shmenu.h"
27 #include "popmenu.h"
28 
29 #include "intl.h"
30 #include "menuspec.h"
31 
32 #ifdef WIN32
33 # define QUIT_CMD "Alt+F4"
34 #else
35 # define QUIT_CMD "Ctrl+Q"
36 #endif
37 
38 
39 
40 /*
41 To add a new menu command:
42   1. Declare an event handler and an ID_* enum value in "appwin.h"
43   2. Map the method to the enum value in the TopWindowMap[] in "appwin_ev.cpp"
44   3. Implement the event handler in "appwin_ev.cpp"
45   4. Add a new menu specification to the menu_specs[] list in "menuspec.cpp".
46   5. Add a new menu item in CreateMenus() in "appwin_mnu.cpp" ( using MkMnuCmd() MkMnuChk() or MkMnuRad() )
47 */
48 
49 
50 #define TW TopWindow
51 static MenuSpec menu_specs[] = {
52   {TW::ID_NEW,              "FileNew",         _("&New"),               _("new file"),     "Ctrl+N",       'm', {NULL}},
53   {TW::ID_OPEN_FILES,       "FileOpenFile",    _("&Open Files..."),     _("open files"),   "Ctrl+O",       'm', {NULL}},
54   {TW::ID_OPEN_SELECTED,    "FileOpenSel",     _("Open Selecte&d"),     _("open sel"),     "Ctrl+Y",       'm', {NULL}},
55   {TW::ID_SAVE,             "FileSave",        _("&Save"),              _("save file"),    "Ctrl+S",       'm', {NULL}},
56   {TW::ID_SAVEAS,           "FileSaveAs",      _("Save &As..."),        _("save as"),      "Ctrl+Shift+S", 'm', {NULL}},
57   {TW::ID_SAVEALL,          "FileSaveAll",     _("Save All"),           _("save all"),     "",             'm', {NULL}},
58   {TW::ID_SAVECOPY,         "FileSaveCopy",    _("Save a co&py..."),    _("save copy"),    "",             'm', {NULL}},
59   {TW::ID_EXPORT_PDF,       "FileExportPdf",   _("As &PDF..."),         _("exp pdf"),      "",             'm', {NULL}},
60   {TW::ID_EXPORT_HTML,      "FileExportHtml",  _("As &HTML..."),        _("exp html"),     "",             'm', {NULL}},
61   {TW::ID_RELOAD,           "FileReload",      _("&Reload"),            _("reload"),       "Ctrl+F5",      'm', {NULL}},
62   {TW::ID_CLOSE,            "FileClose",       _("&Close"),             _("close file"),   "Ctrl+W",       'm', {NULL}},
63   {TW::ID_CLOSEALL,         "FileCloseAll",    _("Close All"),          _("close all"),    "Ctrl+Shift+W", 'm', {NULL}},
64   {TW::ID_INSERT_FILE,      "FileInsert",      _("&Insert file..."),    _("insert file"),  "",             'm', {NULL}},
65   {TW::ID_LOAD_TAGS,        "FileLoadTags",    _("Load ta&gs file..."), _("load tags"),    "",             'm', {NULL}},
66   {TW::ID_SELECT_DIR,       "FileSelectDir",   _("&Working Directory..."),_("work dir"),   "",             'm', {NULL}},
67   {TW::ID_QUIT,             "FileQuit",        _("&Quit"),              _("quit"),         QUIT_CMD,       'm', {NULL}},
68   {TW::ID_UNDO,             "EditUndo",        _("&Undo"),              _("undo"),         "Ctrl+Z",       'm', {NULL}},
69   {TW::ID_REDO,             "EditRedo",        _("&Redo"),              _("redo"),         "Ctrl+Shift+Z", 'm', {NULL}},
70   {TW::ID_CUT,              "EditCut",         _("Cu&t"),               _("cut"),          "Ctrl+X",       'm', {NULL}},
71   {TW::ID_COPY,             "EditCopy",        _("&Copy"),              _("copy"),         "Ctrl+C",       'm', {NULL}},
72   {TW::ID_PASTE,            "EditPaste",       _("&Paste"),             _("paste"),        "Ctrl+V",       'm', {NULL}},
73   {TW::ID_DEL_WORD_LEFT,    "EditDelWordLeft", _("Word &Left"),         _("word bksp"),    "Ctrl+Back",    'm', {NULL}},
74   {TW::ID_DEL_WORD_RIGHT,   "EditDelWordRight",_("Word &Right"),        _("word del"),     "Ctrl+Del",     'm', {NULL}},
75   {TW::ID_DEL_LINE_LEFT,    "EditDelLineStart",_("To Line &Start"),     _("line bksp"),    "Ctrl+Shift+Back",'m',{NULL}},
76   {TW::ID_DEL_LINE_RIGHT,   "EditDelLineEnd",  _("To Line &End"),       _("line trunc"),   "Ctrl+Shift+Del",'m',{NULL}},
77   {TW::ID_TOLOWER,          "EditLower",       _("Lo&wer Case"),        _("lower"),        "Ctrl+Shift+^", 'm', {NULL}},
78   {TW::ID_TOUPPER,          "EditUpper",       _("Upp&er Case"),        _("upper"),        "Ctrl+6",       'm', {NULL}},
79   {TW::ID_INDENT_STEP,      "EditSpaceIn",     _("&Space right"),       _("space right"),  "Ctrl+0",       'm', {NULL}},
80   {TW::ID_UNINDENT_STEP,    "EditUnspace",     _("Spa&ce left"),        _("space left"),   "Ctrl+9",       'm', {NULL}},
81   {TW::ID_INDENT_FULL,      "EditTabIn",       _("&Indent right  "),    _("shift right"),  "Ctrl+Shift+)", 'm', {NULL}},
82   {TW::ID_UNINDENT_FULL,    "EditUntab",       _("In&dent left   "),    _("shift left"),   "Ctrl+Shift+(", 'm', {NULL}},
83   {TW::ID_READONLY,         "EditReadOnly",    _("Read Onl&y"),         _("read only"),    "",             'm', {NULL}},
84   {TW::ID_WORDWRAP,         "EditWordWrap",    _("Word Wr&ap"),         _("word wrap"),    "",             'm', {NULL}},
85   {TW::ID_FMT_DOS,          "EditFmtDos",      _("&DOS [CR/LF]"),       _("dos"),          "",             'm', {NULL}},
86   {TW::ID_FMT_MAC,          "EditFmtMac",      _("&Mac [CR]"),          _("mac"),          "",             'm', {NULL}},
87   {TW::ID_FMT_UNIX,         "EditFmtUnix",     _("&Unix [LF]"),         _("unix"),         "",             'm', {NULL}},
88   {TW::ID_PREFS_DIALOG,     "EditPrefs",       _("Prefere&nces..."),    _("prefs"),        "Ctrl+P",       'm', {NULL}},
89   {TW::ID_FIND,             "SearchFind",      _("&Find..."),           _("find"),         "Ctrl+F",       'm', {NULL}},
90   {TW::ID_FINDNEXT,         "SearchFindNext",  _("Find &Next   "),      _("next"),         "Ctrl+G",       'm', {NULL}},
91   {TW::ID_FINDPREV,         "SearchFindPrev",  _("Find &Prev   "),      _("prev"),         "Ctrl+Shift+G", 'm', {NULL}},
92   {TW::ID_NEXT_SELECTED,    "SearchNextSel",   _("&Next"),              _("next sel"),     "Ctrl+H",       'm', {NULL}},
93   {TW::ID_PREV_SELECTED,    "SearchPrevSel",   _("&Prev   "),           _("prev sel"),     "Ctrl+Shift+H", 'm', {NULL}},
94   {TW::ID_REPLACE_IN_DOC,   "SearchReplace",   _("&Replace... "),       _("repl"),         "Ctrl+R",       'm', {NULL}},
95   {TW::ID_GOTO,             "SearchGoTo",      _("&Go to..."),          _("goto"),         "Ctrl+L",       'm', {NULL}},
96   {TW::ID_GOTO_SELECTED,    "SearchGoToSel",   _("Go to selecte&d"),    _("goto sel"),     "Ctrl+E",       'm', {NULL}},
97   {TW::ID_GOTO_ERROR,       "SearchGoToError", _("Go to &error"),       _("goto err"),     "Ctrl+Shift+E", 'm', {NULL}},
98   {TW::ID_BOOKMARK_SET,     "SearchMarkSet",   _("&Set"),               _("set mark"),     "Ctrl+M",       'm', {NULL}},
99   {TW::ID_BOOKMARK_RETURN,  "SearchMarkReturn",_("&Return"),            _("goto mark"),    "Ctrl+Shift+M", 'm', {NULL}},
100   {TW::ID_FIND_TAG,         "SearchFindDef",   _("Find &Definition"),   _("find tag"),     "Ctrl+D",       'm', {NULL}},
101   {TW::ID_SHOW_CALLTIP,     "SearchShowTip",   _("Show &Calltip"),      _("show tip"),     "Ctrl+'",       'm', {NULL}},
102   {TW::ID_AUTO_COMPLETE,    "SearchShowComp",  _("Show Comple&tions"),  _("auto comp"),    "Alt+'",        'm', {NULL}},
103   {TW::ID_SHOW_STATUSBAR,   "ViewStatusBar",   _("&Status bar"),        _("stats bar"),    "",             'm', {NULL}},
104   {TW::ID_SHOW_LINENUMS,    "ViewLineNumbers", _("Line &numbers"),      _("line nums"),    "",             'm', {NULL}},
105   {TW::ID_SHOW_TOOLBAR,     "ViewToolbar",     _("Tool&bar"),           _("hide"),         "",             'm', {NULL}},
106   {TW::ID_SHOW_WHITESPACE,  "ViewWhiteSpace",  _("&White space"),       _("white space"),  "",             'm', {NULL}},
107   {TW::ID_SHOW_MARGIN,      "ViewRightMargin", _("Right &Margin"),      _("show margin"),  "",             'm', {NULL}},
108   {TW::ID_SHOW_INDENT,      "ViewIndent",      _("Indent &Guides"),     _("show guides"),  "",             'm', {NULL}},
109   {TW::ID_SHOW_CARET_LINE,  "ViewCaretLine",   _("&Caret Line"),        _("caret line"),   "",             'm', {NULL}},
110   {TW::ID_SHOW_OUTLIST,     "ViewOutputPane",  _("&Output pane"),       _("show panel"),   "",             'm', {NULL}},
111   {TW::ID_INVERT_COLORS,    "ViewInvertColors",_("&Inverted colors"),   _("invert color"), "",             'm', {NULL}},
112   {TW::ID_TABS_TOP,         "ViewTabsTop",     _("&Top"),               _("tabs top"),     "",             'm', {NULL}},
113   {TW::ID_TABS_BOTTOM,      "ViewTabsBottom",  _("&Bottom"),            _("tabs btm"),     "",             'm', {NULL}},
114   {TW::ID_TABS_LEFT,        "ViewTabsLeft",    _("&Left"),              _("tabs lft"),     "",             'm', {NULL}},
115   {TW::ID_TABS_RIGHT,       "ViewTabsRight",   _("&Right"),             _("tabs rgt"),     "",             'm', {NULL}},
116   {TW::ID_TABS_UNIFORM,     "ViewTabsUniform", _("&Uniform"),           _("tabs equal"),   "",             'm', {NULL}},
117   {TW::ID_TABS_COMPACT,     "ViewTabsPacked",  _("&Packed"),            _("tabs pack"),    "",             'm', {NULL}},
118   {TW::ID_TABS_BY_POS,      "ViewTabsAuto",    _("&Automatic"),         _("tabs auto"),    "",             'm', {NULL}},
119   {TW::ID_ZOOM_IN,          "ViewZoomIn",      _("&In "),               _("zoom in"),      "Ctrl+=",       'm', {NULL}},
120   {TW::ID_ZOOM_OUT,         "ViewZoomOut",     _("&Out "),              _("zoom out"),     "Ctrl+-",       'm', {NULL}},
121   {TW::ID_ZOOM_NONE,        "ViewZoomDef",     _("&Default "),          _("zoom none"),    "Ctrl+1",       'm', {NULL}},
122   {TW::ID_ZOOM_NEAR,        "ViewZoomNear",    _("&Closest "),          _("zoom near"),    "Ctrl+Shift++", 'm', {NULL}},
123   {TW::ID_ZOOM_FAR,         "ViewZoomFar",     _("&Furthest "),         _("zoom far"),     "Ctrl+Shift+_", 'm', {NULL}},
124   {TW::ID_CYCLE_SPLITTER,   "ViewSplitView",   _("Split &View"),        _("split view"),   "Ctrl+2",       'm', {NULL}},
125   {TW::ID_CLEAR_OUTPUT,     "ViewClearOutput", _("&Clear Output"),      _("clear pane"),   "Ctrl+Shift+L", 'm', {NULL}},
126   {TW::ID_FILTER_SEL,       "ToolsFilterSel",  _("&Filter selection"),  _("filter sel"),   "Alt+R",        'm', {NULL}},
127   {TW::ID_INSERT_CMD_OUT,   "ToolsInsert",     _("&Insert command"),    _("insert cmd"),   "Alt+I",        'm', {NULL}},
128   {TW::ID_RUN_COMMAND,      "ToolsExecute",    _("&Execute command"),   _("exec cmd"),     "Alt+X",        'm', {NULL}},
129   {TW::ID_MACRO_RECORD,     "ToolsRecMacro",   _("Re&cord macro"),      _("record macro"), "Alt+K",        'm', {NULL}},
130   {TW::ID_MACRO_PLAYBACK,   "ToolsPlayMacro",  _("&Play macro"),        _("play macro"),   "Ctrl+K",       'm', {NULL}},
131   {TW::ID_MACRO_TRANSLATE,  "ToolsShowMacro",  _("&Show macro"),        _("show macro"),   "Ctrl+Shift+K", 'm', {NULL}},
132   {TW::ID_CONFIGURE_TOOLS,  "ToolsCustomize",  _("Customi&ze menu..."), _("edit tools"),   "",             'm', {NULL}},
133   {TW::ID_RESCAN_USER_MENU, "ToolsRebuild",    _("Re&build menu"),      _("update tools"), "",             'm', {NULL}},
134   {TW::ID_TAB_NEXT,         "DocsFocusNext",   _("&Next"),              _("next doc"),     "Ctrl+PgDn",    'm', {NULL}},
135   {TW::ID_TAB_PREV,         "DocsFocusPrev",   _("&Prev"),              _("prev doc"),     "Ctrl+PgUp",    'm', {NULL}},
136   {TW::ID_TAB_TOFIRST,      "DocsMoveToFirst", _("&First"),             _("move first"),   "",             'm', {NULL}},
137   {TW::ID_TAB_TOLAST,       "DocsMoveToLast",  _("&Last"),              _("move last"),    "",             'm', {NULL}},
138   {TW::ID_TAB_UP,           "DocsMoveUp",      _("&Up (Left)"),         _("move left"),    "",             'm', {NULL}},
139   {TW::ID_TAB_DOWN,         "DocsMoveDown",    _("&Down (Right)"),      _("move right"),   "",             'm', {NULL}},
140   {TW::ID_FOCUS_OUTLIST,    "DocsFocusPane",   _("&Output Pane"),       _("focus panel"),  "F4",           'm', {NULL}},
141   {TW::ID_SHOW_HELP,        "HelpHelp",        _("&Help..."),           _("help"),         "F1",           'm', {NULL}},
142   {TW::ID_SHOW_LUA_HELP,    "HelpMacro",       _("&Macro help..."),     _("macro help"),   "",             'm', {NULL}},
143   {TW::ID_HELP_ABOUT,       "HelpAbout",       _("&About..."),          _("about"),        "",             'm', {NULL}},
144   {TW::ID_POPUP_SELECT_ALL, "PopupSelectAll",  _("Select &All"),        _("sel all"),      "",             'm', {NULL}},
145   {TW::ID_POPUP_DELETE_SEL, "PopupDeleteSel",  _("&Delete"),            _("del sel"),      "",             'm', {NULL}},
146   {TW::ID_KILL_COMMAND,     "KillCommand",     "\0",                    NULL,              "Ctrl+.",       'm', {NULL}},
147   {TW::ID_LAST,             "\0",              "\0",                    NULL,              "\0",           'm', {NULL}}
148 };
149 
150 
151 /*
152 MenuSpec types:
153   m: FXMenuCommand (default)
154   r: FXMenuRadio
155   k: FXMenuCheck
156   u: User-defined menu item from Tools menu item, used to create a toolbar button.
157   x: User-defined menu item is in a "transitional" state:
158       The mc field is overloaded and points to a filename, instead of a menu item.
159 */
160 
161 
162 /*
163   This array is used to create the buttons on the toolbar -
164   If an element contains a positive integer, it will be passed directly
165   to the button's constructor method as the the FXSelector "sel" argument.
166   Th special case of ID_LAST is used as semamphore, it means the remaining buttons are unused.
167   If the value is negative, it contains a unique identifier that should match
168   the "sel" field for one of the MenuSpec elements in the custom_commands[] array,
169   and a button will be created that calls the handler for the FXMenuCommand
170   referenced by that MenuSpec's ms_mc field.
171   The initial values here are simply reasonable defaults for a "typical" toolbar.
172 */
173 static FXint toolbar_buttons[TBAR_MAX_BTNS+1]= {
174   TW::ID_NEW,
175   TW::ID_OPEN_FILES,
176   TW::ID_SAVE,
177   TW::ID_SAVEAS,
178   TW::ID_CUT,
179   TW::ID_COPY,
180   TW::ID_PASTE,
181   TW::ID_UNDO,
182   TW::ID_REDO,
183   TW::ID_FIND,
184   TW::ID_NEXT_SELECTED,
185   TW::ID_PREV_SELECTED,
186   TW::ID_BOOKMARK_SET,
187   TW::ID_BOOKMARK_RETURN,
188   TW::ID_INDENT_FULL,
189   TW::ID_UNINDENT_FULL,
190   TW::ID_LAST,
191   TW::ID_LAST,
192   TW::ID_LAST,
193   TW::ID_LAST,
194   TW::ID_LAST,
195   TW::ID_LAST,
196   TW::ID_LAST,
197   TW::ID_LAST,
198   TW::ID_LAST,
199 };
200 
201 // This array holds information about toolbar buttons that the user created from
202 // his own user-defined menu items from the Tools menu.
203 static MenuSpec* custom_commands[TBAR_MAX_BTNS+1] = {
204   NULL,NULL,NULL,NULL,
205   NULL,NULL,NULL,NULL,
206   NULL,NULL,NULL,NULL,
207   NULL,NULL,NULL,NULL,
208   NULL,NULL,NULL,NULL,
209   NULL,NULL,NULL,NULL,
210   NULL };
211 
212 
213 
214 // We aren't using any fancy icons, but we can at least make each button a different color...
215 static ColorName tbar_colors[TBAR_MAX_BTNS] = {
216   "#BBFFFF",
217   "#FFEECC",
218   "#CCEEFF",
219   "#CCFFEE",
220   "#DDDDFF",
221   "#DDEEEE",
222   "#FFBBFF",
223   "#DDFFDD",
224   "#ECECEC",
225   "#EECCFF",
226   "#EEEEDD",
227   "#EEDDEE",
228   "#EEFFCC",
229   "#FFCCEE",
230   "#FFFFBB",
231   "#FFDDDD",
232   "#BBFFFF",
233   "#FFEECC",
234   "#CCEEFF",
235   "#CCFFEE",
236   "#DDDDFF",
237   "#DDEEEE",
238   "#FFBBFF",
239   "#DDFFDD",
240 };
241 
242 
243 
TBarColors(FXint i)244 const char*MenuMgr::TBarColors(FXint i) { return (const char*)(tbar_colors[i]); }
245 
246 
RemoveToolbarButton(FXint index)247 static void RemoveToolbarButton(FXint index)
248 {
249   for (FXint i=index; i<TBAR_MAX_BTNS; i++) {
250     toolbar_buttons[i]=toolbar_buttons[i+1];
251     if (toolbar_buttons[i]==TW::ID_LAST) { break; }
252   }
253 }
254 
255 
256 
257 // The text for a button created from a user-defined menu uses
258 // the first two words from the menu item's label. Each of the
259 // two words is then truncated to a maximum of five characters.
SetSpecTBarBtnText(MenuSpec * spec)260 static void SetSpecTBarBtnText(MenuSpec*spec)
261 {
262   FXString btn_txt=spec->ms_mc->getText();
263   if (btn_txt.contains(' ')) {
264     FXString btn_txt2=btn_txt.section(' ',1);
265     btn_txt=btn_txt.section(' ',0);
266     btn_txt.trunc(5);
267     btn_txt2.trunc(5);
268     btn_txt.append(" ");
269     btn_txt.append(btn_txt2);
270   } else {
271     btn_txt.trunc(5);
272   }
273   btn_txt.lower();
274   spec->btn_txt=strdup(btn_txt.text());
275 }
276 
277 
278 
279 //  When UserMenu objects are re-scanned, the menu commands they contain become invalid.
280 //  If any of our toolbar buttons reference these menu commands, we need to invalidate the
281 //  menu commands, while at the same time saving the filename that the item pointed to.
282 //  That way, when the menu is rebuilt, we can compare the filenames referenced by the new
283 //  menu item to our saved filenames and point the toolbar button to the new menu command.
284 // ( This method is called by the menu item's destructor. )
InvalidateUsrTBarCmd(FXMenuCommand * mc)285 void MenuMgr::InvalidateUsrTBarCmd(FXMenuCommand*mc)
286 {
287   for (FXint i=0; i<TBAR_MAX_BTNS; i++) {
288     MenuSpec*spec=custom_commands[i];
289     if (spec && (spec->type=='u') && (spec->ms_mc==mc)) {
290       spec->type='x';
291       char*tmp=strdup((const char*)(spec->ms_mc->getUserData()));
292       spec->ms_fn=tmp;
293     }
294   }
295 }
296 
297 
298 
299 //  When UserMenu objects are re-scanned, a completely new set of menu items is created.
300 //  Most of these simply replace existing items, so any of the custom toolbar button specs
301 //  that point to the old copy of the menu item will need to be updated so they will point
302 //  to the new instance of the item.
303 // ( This method is called by the menu item's constructor. )
ValidateUsrTBarCmd(FXMenuCommand * mc)304 void MenuMgr::ValidateUsrTBarCmd(FXMenuCommand *mc)
305 {
306   if (mc) {
307     const char*tmp=(const char*)(mc->getUserData());
308     if (tmp) {
309       for (FXint i=0; i<TBAR_MAX_BTNS; i++) {
310         MenuSpec*spec=custom_commands[i];
311         if (spec && spec->ms_fn && (spec->type=='x') && (strcmp(spec->ms_fn,tmp)==0)) {
312           free(spec->ms_fn);
313           spec->ms_mc=mc;
314           spec->type='u';
315           if (spec->btn_txt==NULL) { SetSpecTBarBtnText(spec); }
316           return;
317         }
318       }
319     }
320   }
321 }
322 
323 
324 
325 // After we have completed re-scanning the UserMenu object, there might
326 // still be some items that were deleted and never re-created. This procedure
327 // cleans up any remaining "orphaned" items.
PurgeTBarCmds()328 void MenuMgr::PurgeTBarCmds()
329 {
330   for (FXint i=0; i<TBAR_MAX_BTNS; i++) {
331     MenuSpec*spec=custom_commands[i];
332     if (spec && (spec->type=='x')) {
333       if (spec->ms_fn) { free(spec->ms_fn); }
334       if (spec->btn_txt) { free((char*)(spec->btn_txt)); }
335       for (FXint j=0; j<TBAR_MAX_BTNS; j++) {
336         if (toolbar_buttons[j]==spec->sel) { toolbar_buttons[j]=0; }
337       }
338       // If any toolbar button holds a reference to this spec, make sure we set it to NULL...
339       TopWindow::instance()->RemoveTBarBtnData(spec);
340       delete spec;
341       custom_commands[i]=NULL;
342     }
343   }
344   bool found;
345   do {
346     found=false;
347     for (FXint i=0; i<TBAR_MAX_BTNS; i++) {
348       if (toolbar_buttons[i]==0) {
349         RemoveToolbarButton(i);
350         found=true;
351         break;
352       }
353     }
354   } while (found);
355   // Somewhat unrelated, but now is also a good time to clear any old
356   // button references from the builtin commands...
357   for (MenuSpec*spec=menu_specs; spec && spec->ms_mc; spec++) {
358     spec->ms_mc->setUserData(NULL);
359   }
360 }
361 
362 
363 
364 #define _GetCaption(p) ((FXMenuCaption*)((p)->getParent()->getUserData()))
365 
366 
GetTipFromFilename(const char * filename,FXString & tip)367 void MenuMgr::GetTipFromFilename(const char*filename, FXString &tip)
368 {
369   tip=filename;
370   tip.erase(0,TopWindow::ConfigDir().length());
371   FXString path=FXPath::directory(tip);
372 #ifdef WIN32
373   path.substitute(PATHSEP,'/');
374 #endif
375   tip=FXPath::title(tip);
376   tip.erase(0,3);
377   if (tip.find('.')>=0) { tip=FXPath::title(tip); }
378   tip=tip.section('@',0);
379   tip.prepend(path+"/");
380   tip.substitute("_","");
381   tip.substitute('-',' ');
382   FXint n=0;
383   do {
384     n=tip.find('/',n);
385     if (n>=0) {
386       if (isdigit(tip[n+1])&&isdigit(tip[n+2])&&(tip[n+3]=='.')) { tip.erase(n+1,3); }
387       n++;
388     } else {
389       break;
390     }
391   } while (1);
392   tip.substitute("/"," -> ");
393   tip.at(0)=toupper(tip.text()[0]);
394   for (char*c=&(tip.at(0)); *c; c++) { if (c[0]==' ') { c[1]=toupper(c[1]); } }
395 }
396 
397 
398 
399 // Construct a tooltip string based on a menu item's path.
GetTBarBtnTip(MenuSpec * spec,FXString & tip)400 void MenuMgr::GetTBarBtnTip(MenuSpec*spec, FXString &tip)
401 {
402   if (spec&&spec->ms_mc) {
403     switch (spec->type) {
404       case 'u': {
405         GetTipFromFilename((const char*)spec->ms_mc->getUserData(),tip);
406         break;
407       }
408       case 'x': {
409         GetTipFromFilename(spec->ms_fn,tip);
410         break;
411       }
412       default: {
413         tip=spec->ms_mc->getText();
414         for (FXMenuCaption*cpn=_GetCaption(spec->ms_mc); cpn; cpn=_GetCaption(cpn)) {
415           tip.prepend(cpn->getText()+" -> ");
416         }
417         break;
418       }
419     }
420   } else {
421     tip=spec?spec->mnu_txt:FXString::null;
422     tip.substitute("&", "",true);
423   }
424 }
425 
426 
427 
428 // Returns a unique negative ID number for a new user-defined toolbar button.
GetUniqueID()429 static FXint GetUniqueID()
430 {
431   FXint unique=0;
432   bool exists=true;
433   do {
434     unique--;
435     exists=false;
436     for (FXint i=0; i<TBAR_MAX_BTNS; i++) {
437       if (custom_commands[i] && (custom_commands[i]->sel==unique)) {
438         exists=true;
439         break;
440       }
441     }
442   } while (exists);
443   return unique;
444 }
445 
446 
447 
448 //  Create a new MenuSpec for a toolbar button from a user-defined menu item.
AddTBarUsrCmd(FXMenuCommand * mc)449 MenuSpec* MenuMgr::AddTBarUsrCmd(FXMenuCommand*mc)
450 {
451   FXint unique=GetUniqueID();
452   for (FXint i=0; i<TBAR_MAX_BTNS; i++) {
453     if (custom_commands[i]==NULL) {
454       MenuSpec*spec=new MenuSpec;
455       spec->type='u';
456       spec->ms_mc=mc;
457       snprintf(spec->pref,sizeof(spec->pref)-1,"Custom_%d",abs(unique));
458       SetSpecTBarBtnText(spec);
459       spec->sel=GetUniqueID();
460       custom_commands[i]=spec;
461       return spec;
462     }
463   }
464   return NULL;
465 }
466 
467 
468 
469 //  When we read a user-defined toolbar item from the registry, its menu item has not
470 //  yet been created, so we set up a "transitional" menu spec containing the path to
471 //  the script file. This information will be used later to create a toolbar button,
472 //  after the user-defined menus are in place.
RegTBarUsrCmd(FXint index,const char * pref,const char * filename)473 static MenuSpec* RegTBarUsrCmd(FXint index, const char*pref, const char*filename)
474 {
475   FXint unique=0;
476   for (FXint i=0; i<TBAR_MAX_BTNS; i++) {
477     if (custom_commands[i]==NULL) {
478       MenuSpec*spec=new MenuSpec;
479       spec->type='x';
480       spec->ms_fn=strdup(filename);
481       sscanf(pref, "Custom_%d",&unique);
482       spec->sel = -(unique);
483       strncpy(spec->pref,pref,sizeof(spec->pref)-1);
484       spec->btn_txt=NULL;
485       custom_commands[i]=spec;
486       toolbar_buttons[index]=spec->sel;
487       return spec;
488     }
489   }
490   return NULL;
491 }
492 
493 
494 // The user has decided to remove a toolbar button for a user-defined menu item.
RemoveTBarUsrCmd(MenuSpec * spec)495 void MenuMgr::RemoveTBarUsrCmd(MenuSpec*spec)
496 {
497  if (spec==NULL) { return; }
498  for (FXint i=0; i<TBAR_MAX_BTNS; i++) {
499     if (custom_commands[i]==spec) {
500       custom_commands[i]=NULL;
501       // If any toolbar button holds a reference to this spec, make sure we set it to NULL...
502       TopWindow::instance()->RemoveTBarBtnData(spec);
503     }
504   }
505   if (spec->btn_txt) free((char*)spec->btn_txt);
506   delete(spec);
507 }
508 
509 
510 
511 // Free up memory allocated to the custom_commands[] array when the program exits.
FreeTBarUsrCmds()512 static void FreeTBarUsrCmds() {
513   for (FXint i=0; i<TBAR_MAX_BTNS; i++) {
514    MenuSpec*spec=custom_commands[i];
515    if (spec) {
516      custom_commands[i]=NULL;
517      if (spec->btn_txt) free((char*)spec->btn_txt);
518      delete spec;
519    }
520   }
521 }
522 
523 
524 
LookupMenu(FXint sel)525 MenuSpec* MenuMgr::LookupMenu(FXint sel)
526 {
527   if (sel>0) {
528     MenuSpec*spec;
529     for (spec=menu_specs; spec->sel!=TW::ID_LAST; spec++) {
530       if (spec->sel==sel) { return spec; }
531     }
532   } else {
533     for (FXint i=0; i<TBAR_MAX_BTNS; i++) {
534       if (custom_commands[i] && (custom_commands[i]->sel==sel)) { return custom_commands[i]; }
535     }
536   }
537   return NULL;
538 }
539 
540 
541 
LookupMenuByPref(const char * pref)542 MenuSpec* MenuMgr::LookupMenuByPref(const char*pref)
543 {
544   if (pref) {
545     MenuSpec*spec;
546     for (spec=menu_specs; spec->sel!=TW::ID_LAST; spec++) {
547       if (strcmp(spec->pref, pref)==0) { return spec; }
548     }
549   }
550   return NULL;
551 }
552 
553 
554 
TBarBtns()555 FXint*MenuMgr::TBarBtns() { return toolbar_buttons; }
556 
557 
558 
MenuSpecs()559 MenuSpec*MenuMgr::MenuSpecs() { return menu_specs; }
560 
561 
562 
GetUsrCmdPath(MenuSpec * spec)563 const char* MenuMgr::GetUsrCmdPath(MenuSpec*spec) {
564   if (spec) {
565     switch (spec->type) {
566       case 'x': { return spec->ms_fn; }
567       case 'u': { return spec->ms_mc?((const char*)(spec->ms_mc->getUserData())):NULL; }
568     }
569   }
570   return NULL;
571 }
572 
573 
574 
575 static const char* DefaultPopupCommands[] = {
576   "EditUndo",
577   "EditRedo",
578   "",
579   "EditCut",
580   "EditCopy",
581   "EditPaste",
582   "PopupDeleteSel",
583   "",
584   "PopupSelectAll",
585   NULL
586 };
587 
588 static const char*popup_section=NULL;
589 
590 static char* PopupCommands[POPUP_MAX_CMDS];
591 
592 
593 
FreePopupCommands()594 void MenuMgr::FreePopupCommands()
595 {
596   for (FXint i=0; PopupCommands[i]; i++) {
597     free(PopupCommands[i]);
598     PopupCommands[i]=NULL;
599   }
600 }
601 
602 
603 
GetPopupCommands()604 char**MenuMgr::GetPopupCommands()
605 {
606   return PopupCommands;
607 }
608 
609 
ReadPopupMenu(FXRegistry * reg,const char * popup_sect)610 void MenuMgr::ReadPopupMenu(FXRegistry*reg, const char* popup_sect) {
611   popup_section=popup_sect;
612   FreePopupCommands();
613   if (reg->existingSection(popup_sect)) {
614     for (FXint i=0; i<POPUP_MAX_CMDS; i++) {
615       char keyname[32];
616       memset(keyname,0, sizeof(keyname));
617       snprintf(keyname,sizeof(keyname)-1,"Command_%d", i+1);
618       if (reg->existingEntry(popup_sect,keyname)) {
619         const char*tmp=reg->readStringEntry(popup_sect,keyname);
620         PopupCommands[i]=strdup(tmp?tmp:"");
621       }
622     }
623   } else {
624     for (FXint i=0; DefaultPopupCommands[i]; i++) {
625       PopupCommands[i]=strdup(DefaultPopupCommands[i]);
626     }
627   }
628 }
629 
630 
631 
WritePopupMenu(FXRegistry * reg,const char * popup_sect)632 void MenuMgr::WritePopupMenu(FXRegistry*reg, const char* popup_sect) {
633   reg->deleteSection(popup_sect);
634   for (FXint i=0; PopupCommands[i]; i++) {
635     char keyname[32];
636     memset(keyname,0, sizeof(keyname));
637     snprintf(keyname,sizeof(keyname)-1,"Command_%d", i+1);
638     reg->writeStringEntry(popup_sect,keyname,PopupCommands[i]);
639   }
640   FreePopupCommands();
641 }
642 
643 
644 
ShowPopupMenu(FXPoint * pt)645 void MenuMgr::ShowPopupMenu(FXPoint*pt)
646 {
647   TopWindowBase*tw=TopWindowBase::instance();
648   SciDoc*sci=tw->FocusedDoc();
649   FXMenuPane *mnu=new FXMenuPane(tw);
650   static FXint toolpathlen=tw->ConfigDir().length()+6;
651   for (char**pref=PopupCommands; *pref; pref++) {
652     if ((*pref)[0]) {
653       if (strchr(*pref,PATHSEP)) {
654         if (FXStat::isFile(*pref)) {
655           FXString label;
656           FXSelector sel=0;
657           const FXchar *subdir=(*pref)+toolpathlen;
658           switch (subdir[0]) {
659             case 'c': {
660               if ((strncmp(subdir,"commands",8)==0)&&(subdir[8]==PATHSEP)) { sel=TopWindow::ID_USER_COMMAND; }
661               break;
662             }
663             case 'f': {
664               if ((strncmp(subdir,"filters",7)==0)&&(subdir[7]==PATHSEP)) { sel=TopWindow::ID_USER_FILTER; }
665               break;
666             }
667             case 'm': {
668               if ((strncmp(subdir,"macros",6)==0)&&(subdir[6]==PATHSEP)) { sel=TopWindow::ID_USER_MACRO; }
669               break;
670             }
671             case 's': {
672               if ((strncmp(subdir,"snippets",8)==0&&(subdir[8]==PATHSEP))) { sel=TopWindow::ID_USER_SNIPPET; }
673               break;
674             }
675           }
676           if (sel && UserMenu::MakeLabelFromPath(*pref, label)) {
677             FXMenuCommand*mc = new PopUpMnuCmd(mnu,label,NULL,tw,sel);
678             mc->setUserData((void*)(*pref));
679             if ((sel==TopWindow::ID_USER_FILTER)&&(!sci->GetSelLength())) { mc->disable(); }
680           }
681         }
682       } else {
683         MenuSpec* spec=LookupMenuByPref(*pref);
684         if (spec) {
685           FXMenuCommand*mc = new PopUpMnuCmd(mnu,spec->mnu_txt,NULL,tw,spec->sel);
686           switch (spec->sel) {
687             case TopWindow::ID_UNDO:{
688               if (!sci->sendMessage(SCI_CANUNDO,0,0)) { mc->disable(); }
689               break;
690             }
691             case TopWindow::ID_REDO:{
692               if (!sci->sendMessage(SCI_CANREDO,0,0)) { mc->disable(); }
693               break;
694             }
695             case TopWindow::ID_PASTE:{
696               if (!sci->sendMessage(SCI_CANPASTE,0,0)) { mc->disable(); }
697               break;
698             }
699             case TopWindow::ID_CUT:
700             case TopWindow::ID_COPY:
701             case TopWindow::ID_POPUP_DELETE_SEL:{
702               if (!sci->GetSelLength()) { mc->disable(); }
703               break;
704             }
705           }
706         }
707       }
708     } else {
709       new FXMenuSeparator(mnu);
710     }
711   }
712   mnu->create();
713   mnu->show();
714   mnu->popup(NULL,pt->x,pt->y);
715   mnu->grabKeyboard();
716   sci->getApp()->runModalWhileShown(mnu);
717   delete mnu;
718 }
719 
720 
721 
MakeMenuCommand(FXComposite * p,FXObject * tgt,FXSelector sel,char type,bool checked)722 FXMenuCommand*MenuMgr::MakeMenuCommand(FXComposite*p, FXObject*tgt, FXSelector sel, char type, bool checked)
723 {
724   MenuSpec*spec=MenuMgr::LookupMenu(sel);
725 
726   if (spec) {
727     FXWindow*own;
728     FXAccelTable *table;
729     switch(type) {
730       case 'm': {
731         spec->ms_mc = new FXMenuCommand(p,spec->mnu_txt,NULL,tgt,sel);
732         break;
733       }
734       case 'k': {
735         spec->ms_mc = (FXMenuCommand*) new FXMenuCheck(p,spec->mnu_txt,tgt,sel);
736         ((FXMenuCheck*)spec->ms_mc)->setCheck(checked);
737         break;
738       }
739       case 'r': {
740         spec->ms_mc = (FXMenuCommand*) new FXMenuRadio(p,spec->mnu_txt,tgt,sel);
741         break;
742       }
743       default: {
744         fxwarning(_("%s: Warning: unknown menu type: '%c'.\n"), EXE_NAME, type);
745         spec->ms_mc = new FXMenuCommand(p,spec->mnu_txt,NULL,tgt,sel);
746       }
747     }
748     FXHotKey acckey=parseAccel(spec->accel);
749     if (acckey) {
750       spec->ms_mc->setAccelText(spec->accel);
751       own=p->getShell()->getOwner();
752       if (own) {
753         table=own->getAccelTable();
754         if (table) {
755           if (table->hasAccel(acckey)) {
756             fxwarning(_("%s: Warning: action \"%s\" overrides existing accelerator.\n"), EXE_NAME, spec->pref);
757           }
758           table->addAccel(acckey,tgt,FXSEL(SEL_COMMAND,sel));
759         }
760       }
761     } else {
762       if (spec->accel[0]) {
763         fxwarning(_("%s: Warning: Failed to parse accelerator for \"%s\"\n"), EXE_NAME, spec->pref);
764       }
765     }
766     spec->type=type;
767     return spec->ms_mc;
768   } else {
769     fxwarning(_("%s: Warning: Could not build menu for selector #%d\n"), EXE_NAME, sel);
770     return NULL;
771   }
772 }
773 
774 
775 
ReadMenuSpecs(FXRegistry * reg,const char * keys_sect)776 void MenuMgr::ReadMenuSpecs(FXRegistry*reg, const char* keys_sect)
777 {
778   for (MenuSpec*spec=menu_specs; spec->sel!=TopWindow::ID_LAST; spec++) {
779     FXString acc=reg->readStringEntry(keys_sect,spec->pref,spec->accel);
780 #ifdef WIN32
781     if (acc.contains("Shift")) {
782       acc.substitute(')','0');
783       acc.substitute('(','9');
784     }
785 #endif
786     if (strcmp(acc.text(),spec->accel)!=0) {
787       memset(spec->accel,0,sizeof(spec->accel));
788       strncpy(spec->accel,acc.text(),sizeof(spec->accel)-1);
789     }
790   }
791 }
792 
793 
794 
WriteMenuSpecs(FXRegistry * reg,const char * keys_sect)795 void MenuMgr::WriteMenuSpecs(FXRegistry*reg, const char* keys_sect)
796 {
797   for (MenuSpec*spec=menu_specs; spec->sel!=TopWindow::ID_LAST; spec++) {
798     reg->writeStringEntry(keys_sect,spec->pref,spec->accel);
799   }
800 }
801 
802 
803 
ReadToolbarButtons(FXRegistry * reg,const char * tbar_sect)804 void MenuMgr::ReadToolbarButtons(FXRegistry*reg, const char* tbar_sect)
805 {
806   for (FXint i=0; i<TBAR_MAX_BTNS; i++) {
807     char keyname[32];
808     memset(keyname,0, sizeof(keyname));
809     snprintf(keyname,sizeof(keyname)-1,"Button_%d", i+1);
810     MenuSpec*spec=MenuMgr::LookupMenu(toolbar_buttons[i]);
811     const FXchar *keyval=reg->readStringEntry(tbar_sect,keyname,spec?spec->pref:"");
812     spec=LookupMenuByPref(keyval);
813     if (spec) {
814       toolbar_buttons[i]=spec->sel;
815     } else {
816       toolbar_buttons[i]=TopWindow::ID_LAST;
817       if ( keyval && (strncmp(keyval,"Custom_",7)==0) && isdigit(keyval[7]) ) {
818         const FXchar *filename=reg->readStringEntry(tbar_sect,keyval,NULL);
819         if (filename && FXStat::isFile(filename)) {
820            RegTBarUsrCmd(i,keyval,filename);
821         }
822       }
823     }
824   }
825 }
826 
827 
828 
WriteToolbarButtons(FXRegistry * reg,const char * tbar_section)829 void MenuMgr::WriteToolbarButtons(FXRegistry*reg, const char* tbar_section)
830 {
831   for (FXint i=0; i<TBAR_MAX_BTNS; i++) {
832     char keyname[32];
833     memset(keyname,0, sizeof(keyname));
834     snprintf(keyname,sizeof(keyname)-1,"Button_%d", i+1);
835     MenuSpec*spec=MenuMgr::LookupMenu(toolbar_buttons[i]);
836     if (spec) {
837       reg->writeStringEntry(tbar_section,keyname,spec->pref);
838       if (spec->type=='u') {
839         reg->writeStringEntry(tbar_section,spec->pref,(const char*)(spec->ms_mc->getUserData()));
840       }
841     } else {
842       reg->writeStringEntry(tbar_section,keyname,"");
843     }
844   }
845   FreeTBarUsrCmds();
846 }
847 
848 
849 
850 // Update a set of radio buttons
RadioUpdate(FXSelector curr,FXSelector min,FXSelector max)851 void MenuMgr::RadioUpdate(FXSelector curr, FXSelector min, FXSelector max)
852 {
853   for (FXSelector i=min; i<=max; i++) {
854     MenuSpec*spec=MenuMgr::LookupMenu(i);
855     if (spec && spec->ms_mc) {
856       ((FXMenuRadio*)(spec->ms_mc))->setCheck(curr==i);
857       FXButton*btn=(FXButton*)spec->ms_mc->getUserData();
858       if (btn) {
859         if (curr==i) {
860           btn->setFrameStyle(btn->getFrameStyle()|FRAME_THICK|FRAME_RAISED);
861           btn->setState(btn->getState()|STATE_ENGAGED);
862         } else {
863           btn->setFrameStyle(FRAME_NONE);
864           btn->setState(btn->getState()&~STATE_ENGAGED);
865         }
866       }
867     }
868   }
869 }
870 
871 
872 
UpdateEolMenu(SciDoc * sci)873 void MenuMgr::UpdateEolMenu(SciDoc*sci)
874 {
875   switch (sci->sendMessage(SCI_GETEOLMODE,0,0)) {
876     case SC_EOL_CRLF: { RadioUpdate(TW::ID_FMT_DOS,  TW::ID_FMT_DOS, TW::ID_FMT_UNIX);  break; }
877     case SC_EOL_CR:   { RadioUpdate(TW::ID_FMT_MAC,  TW::ID_FMT_DOS, TW::ID_FMT_UNIX);  break; }
878     case SC_EOL_LF:   { RadioUpdate(TW::ID_FMT_UNIX, TW::ID_FMT_DOS, TW::ID_FMT_UNIX); break; }
879   }
880 }
881 
882 
883 
SetFileFormat(SciDoc * sci,FXSelector sel)884 void MenuMgr::SetFileFormat(SciDoc*sci, FXSelector sel)
885 {
886   long EolMode=SC_EOL_LF;
887   switch (sel) {
888     case TW::ID_FMT_DOS:{
889       EolMode=SC_EOL_CRLF;
890       break;
891     }
892     case TW::ID_FMT_MAC:{
893       EolMode=SC_EOL_CR;
894       break;
895     }
896     case TW::ID_FMT_UNIX:{
897       EolMode=SC_EOL_LF;
898       break;
899     }
900   }
901   sci->sendMessage(SCI_SETEOLMODE,EolMode,0);
902   sci->sendMessage(SCI_CONVERTEOLS,EolMode,0);
903   RadioUpdate(sel,TW::ID_FMT_DOS,TW::ID_FMT_UNIX);
904 }
905 
906 
907 
SetTabOrientation(FXSelector sel)908 char MenuMgr::SetTabOrientation(FXSelector sel)
909 {
910   RadioUpdate(sel, TW::ID_TABS_TOP, TW::ID_TABS_RIGHT);
911   switch(sel){
912     case TW::ID_TABS_TOP:    return 'T';
913     case TW::ID_TABS_BOTTOM: return 'B';
914     case TW::ID_TABS_LEFT:   return 'L';
915     case TW::ID_TABS_RIGHT:  return 'R';
916     default: return 'T';
917   }
918 }
919 
920