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