1 /*
2 FXiTe - The Free eXtensIble Text Editor
3 Copyright (c) 2009-2012 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
20 #include <cctype>
21 #include <fxkeys.h>
22 #include <fx.h>
23 #include "sl.h"
24
25 #include "menuspec.h"
26 #include "compat.h"
27
28 #include "shmenu.h"
29
30 #define MAX_DEPTH 16 // Maximum recursion level
31 #define MAX_ENTRIES_PER 32 // Maximum entries per menu pane
32 #define MAX_ENTRIES_ALL 512 // Maximum total entries per UserMenu object
33
34 FXDEFMAP(UserMenu) UserMenuMap[] = { };
35
36 FXIMPLEMENT(UserMenu,FXObject,UserMenuMap,ARRAYNUMBER(UserMenuMap));
37
38
39
40 class UsrMnuCmd: public FXMenuCommand {
41 FXDECLARE(UsrMnuCmd);
UsrMnuCmd()42 UsrMnuCmd(){}
43 public:
UsrMnuCmd(FXComposite * p,const FXString & text,FXObject * tgt=NULL,FXSelector sel=0)44 UsrMnuCmd( FXComposite *p,
45 const FXString &text, FXObject *tgt=NULL, FXSelector sel=0):FXMenuCommand(p,text,NULL,tgt,sel){}
46
onButtonRelease(FXObject * o,FXSelector sel,void * p)47 long onButtonRelease(FXObject*o,FXSelector sel,void*p) {
48 FXbool active=isActive();
49 if(!isEnabled()) { return 0; }
50 getParent()->handle(this,FXSEL(SEL_COMMAND,ID_UNPOST),NULL);
51 if(active && target){ target->tryHandle(this,FXSEL(SEL_COMMAND,message),(void*)(FXuval)2); }
52 return 1;
53 }
onKeyRelease(FXObject * o,FXSelector sel,void * p)54 long onKeyRelease(FXObject*o,FXSelector sel,void* p){
55 FXEvent* ev=(FXEvent*)p;
56 if(isEnabled() && (flags&FLAG_PRESSED)){
57 if(ev->code==KEY_space || ev->code==KEY_KP_Space || ev->code==KEY_Return || ev->code==KEY_KP_Enter) {
58 flags&=~FLAG_PRESSED;
59 getParent()->handle(this,FXSEL(SEL_COMMAND,ID_UNPOST),NULL);
60 if(target) target->tryHandle(this,FXSEL(SEL_COMMAND,message),(void*)(FXuval)((ev->state&CONTROLMASK)?2:1));
61 return 1;
62 }
63 }
64 return 0;
65 }
ValidateTBarCmd()66 void ValidateTBarCmd() { MenuMgr::ValidateUsrTBarCmd(this); }
~UsrMnuCmd()67 ~UsrMnuCmd() {
68 MenuMgr::InvalidateUsrTBarCmd(this);
69 free(getUserData());
70 }
71 };
72
73
74
75 FXDEFMAP(UsrMnuCmd) MyCmdMap[] = {
76 FXMAPFUNC(SEL_KEYRELEASE,0,UsrMnuCmd::onKeyRelease),
77 FXMAPFUNC(SEL_RIGHTBUTTONRELEASE,0,UsrMnuCmd::onButtonRelease)
78 };
79
FXIMPLEMENT(UsrMnuCmd,FXMenuCommand,MyCmdMap,ARRAYNUMBER (MyCmdMap))80 FXIMPLEMENT(UsrMnuCmd,FXMenuCommand,MyCmdMap,ARRAYNUMBER(MyCmdMap))
81
82
83
84 UserMenu::UserMenu(FXMenuPane *owner,
85 const FXString &label, const FXString &dirname, char tag, FXObject *tgt, FXSelector sel, const char**flags)
86 {
87 target=(FXWindow*)tgt;
88 selector=sel;
89 _flags=flags;
90 _tag=tag;
91 topcasc=NULL;
92 toplabel=label.text();
93 topdir=dirname.text();
94 topowner=owner;
95 rescan();
96 }
97
98
99
DeletePanes()100 void UserMenu::DeletePanes()
101 {
102 for (FXint i=0; i<panes.no(); i++) {
103 FXMenuPane*pane=panes[i];
104 if (pane->getUserData()) { free(pane->getUserData()); }
105 delete pane;
106 }
107 panes.clear();
108 }
109
110
111
~UserMenu()112 UserMenu::~UserMenu()
113 {
114 DeletePanes();
115 if (topcasc) {
116 delete(topcasc);
117 topcasc=NULL;
118 }
119 }
120
121
122
create()123 void UserMenu::create()
124 {
125 for (FXint i=0; i<panes.no(); i++) {
126 panes[i]->create();
127 }
128 topcasc->create();
129 }
130
131
132
rescan()133 void UserMenu::rescan()
134 {
135 bool created=false;
136 level=1;
137 count=0;
138 hasitems=false;
139 DeletePanes();
140 if (topcasc) {
141 created=true;
142 delete(topcasc);
143 topcasc=NULL;
144 }
145 ScanDir(topowner,topdir.text());
146 if (created) { create(); }
147 if (topcasc) {
148 if (hasitems) {
149 if (panes[0]->numChildren()==1) {
150 FXMenuCascade*mc=(FXMenuCascade*)panes[0]->getFirst();
151 if (mc && (strcmp(mc->getClassName(),"FXMenuCascade")==0)) {
152 if (mc->getText()=="Hidden") {
153 topcasc->hide();
154 }
155 }
156 }
157 } else { topcasc->hide(); }
158 }
159 }
160
161
162
enable()163 void UserMenu::enable()
164 {
165 if (topcasc) { topcasc->enable(); }
166 }
167
168
169
disable()170 void UserMenu::disable()
171 {
172 if (topcasc) { topcasc->disable(); }
173 }
174
175
176
getText()177 FXString UserMenu::getText()
178 {
179 if (topcasc) {return topcasc->getText(); } else {
180 FXString rv=FXPath::name(topdir);
181 rv[0]=toupper(rv[0]);
182 return rv;
183 }
184 }
185
186
187
setText(const FXString & s)188 void UserMenu::setText(const FXString &s)
189 {
190 if (topcasc) { topcasc->setText(s); }
191 }
192
193
194
MakeLabelFromPath(const char * path,FXString & label)195 bool UserMenu::MakeLabelFromPath(const char*path, FXString &label)
196 {
197 label=FXPath::name(path);
198
199 if ( (label.length()>=3) && isdigit(label[0]) && isdigit(label[0]) && (label[2]=='.')) {
200 label.erase(0,3);
201 } else {
202 return false;
203 }
204
205 label=FXPath::stripExtension(label);
206
207 if (!(FXPath::extension(label)).empty()) {
208 label=FXPath::stripExtension(label);
209 }
210 FXString accel="";
211 FXint at=label.find('@');
212 if (at>=0) {
213 accel=label.text();
214 accel.erase(0,at+1);
215 label.trunc(at);
216 }
217 if (isdigit(label[0])&&isdigit(label[1]&&(label[2]=='.'))) { label.erase(0,3); }
218 label.lower();
219 label.substitute('_', '&', false);
220 label.substitute('-', ' ', true);
221 label.substitute('\t', ' ', true);
222 label.trim();
223 label.simplify();
224 if (!accel.empty()) {
225 label.append("\t");
226 label.append(accel);
227 }
228 if (label[0]=='&') {
229 label[1]=toupper(label[1]);
230 } else {
231 label[0]=toupper(label[0]);
232 }
233 return true;
234 }
235
236
237
238 typedef struct _StrNode {
239 struct _StrNode*next;
240 char* data;
241 } StrNode;
242
243
244
NodeCmp(StrNode * n1,StrNode * n2)245 static int NodeCmp(StrNode*n1, StrNode*n2)
246 {
247 return strcasecmp(n1->data, n2->data);
248 }
249
250
251 typedef int (*SlFunc)(void*,void*);
252
NodeFree(void * p)253 static void NodeFree(void*p)
254 {
255 StrNode*n=(StrNode*)p;
256 free(n);
257 }
258
259
260
NodeNew(const char * s)261 static StrNode*NodeNew(const char*s)
262 {
263 StrNode*n=(StrNode*)malloc(sizeof(StrNode));
264 n->next=NULL;
265 n->data=strdup(s);
266 return n;
267 }
268
269
270
271 class UserMenuHelper {
272 public:
IncCount(UserMenu * um)273 static void IncCount(UserMenu*um) { um->count++; um->hasitems=true; }
ScanDir(UserMenu * um,FXMenuPane * parent,const char * directory)274 static void ScanDir(UserMenu*um, FXMenuPane*parent, const char *directory) { um->ScanDir(parent, directory); }
275 };
276
277
278
NodeCB(StrNode * n,FXMenuPane * mp)279 int NodeCB(StrNode*n, FXMenuPane*mp)
280 {
281 UserMenu*um=(UserMenu*)mp->getUserData();
282 if ( (mp->numChildren()<MAX_ENTRIES_PER) && (um->getCount()<MAX_ENTRIES_ALL)) {
283 if ( FXStat::isDirectory(n->data) ) {
284 UserMenuHelper::ScanDir(um, mp, n->data);
285 free(n->data);
286 } else {
287 FXString label;
288 if ( UserMenu::MakeLabelFromPath(n->data, label) ) {
289 UserMenuHelper::IncCount(um);
290 UsrMnuCmd*cmd=new UsrMnuCmd(mp,label,um->getTarget(),um->getSelector());
291 cmd->setUserData(n->data);
292 cmd->ValidateTBarCmd();
293 }
294 }
295 }
296 return 0;
297 }
298
299
300
ScanDir(FXMenuPane * parent,const char * directory)301 void UserMenu::ScanDir(FXMenuPane*parent, const char *directory)
302 {
303 FXint matchmode=FILEMATCH_FILE_NAME|FILEMATCH_NOESCAPE;
304 FXString fn;
305 FXString pathname;
306 FXString pattern="*";
307 FXDir dir;
308 FXStat info;
309 level++;
310 if (( level<=MAX_DEPTH ) && dir.open(directory)) {
311 void*list=NULL;
312
313 #ifdef FOX_1_6
314 while (dir.next()) {
315 fn=dir.name();
316 #else
317 FXString name;
318 while (dir.next(name)) {
319 fn=name.text();
320 #endif
321 if ( fn[0]=='.') { continue; }
322 pathname=directory;
323 if(!ISPATHSEP(pathname[pathname.length()-1])) { pathname.append(PATHSEPSTRING); }
324 pathname.append(fn);
325 if (FXStat::statFile(pathname,info)) {
326 if ((info.isFile() && PathMatch(pattern,fn,matchmode))||info.isDirectory()) {
327 list=sl_push(list,NodeNew(pathname.text()));
328 }
329 }
330 }
331 dir.close();
332 FXString label;
333 if (MakeLabelFromPath(directory, label)||(!topcasc)) {
334 count++;
335 if (list) { list=sl_mergesort(list,(SlFunc)NodeCmp); }
336 FXMenuPane*mp=new FXMenuPane(target);
337 panes.append(mp);
338 FXMenuCascade* mc;
339 if (topcasc) {
340 mc=new FXMenuCascade(parent,label,NULL,mp);
341 if (label=="Hidden") { mc->hide(); }
342 } else {
343 mc=new FXMenuCascade(parent,toplabel,NULL,mp);
344 topcasc=mc;
345 }
346 mp->setUserData(this);
347 if (list) { sl_map(list,(SlFunc)NodeCB, mp); } else { mc->hide(); }
348 mp->setUserData(strdup(directory));
349 }
350 if (list) { sl_free(list,NodeFree); }
351 }
352 level--;
353 }
354
355
356
357 class HistMnuCmd: public FXMenuCommand {
358 public:
359 HistMnuCmd( FXComposite *p,
360 const FXString &text, FXObject *tgt=NULL, FXSelector sel=0):FXMenuCommand(p,text,NULL,tgt,sel){}
361 ~HistMnuCmd() { free(getUserData()); }
362 };
363
364
365
366 FXDEFMAP(HistMenu) HistMenuMap[] = {
367 FXMAPFUNC(SEL_COMMAND,HistMenu::ID_ITEM_CLICK,HistMenu::onItemClick),
368 };
369
370 FXIMPLEMENT(HistMenu,FXMenuPane,HistMenuMap,ARRAYNUMBER(HistMenuMap))
371
372
373
374 long HistMenu::onItemClick(FXObject*o,FXSelector sel,void*p)
375 {
376 HistMnuCmd*mc = (HistMnuCmd*)o;
377 if (target&&message) { target->handle(this,FXSEL(SEL_COMMAND, message), mc->getUserData()); }
378 return 1;
379 }
380
381
382
383 void HistMenu::add_item(const FXString &txt, bool prepended)
384 {
385 if ((!prepended) && (numChildren()>=26)) { return; }
386 if (!txt.empty()) {
387 if (prepended) {
388 remove(txt);
389 } else {
390 if (find(txt)) { return; }
391 }
392 FXStat info;
393 if ( FXStat::statFile(txt,info) && info.isFile() && info.isReadable() ) {
394 HistMnuCmd*mc=new HistMnuCmd(this,txt,this,ID_ITEM_CLICK);
395 mc->setUserData((void*)(strdup( FXPath::simplify(FXPath::absolute(txt)).text() )));
396 if (created) { mc->create(); }
397 if (prepended) {
398 mc->reparent(this, this->getFirst());
399 while (numChildren()>26) { delete getLast(); }
400 }
401 }
402 }
403 }
404
405
406
407 void HistMenu::prepend(const FXString &txt)
408 {
409 add_item(txt, true);
410 }
411
412
413
414 void HistMenu::append(const FXString &txt)
415 {
416 add_item(txt, false);
417 }
418
419
420
421 void HistMenu::remove(const FXString &txt)
422 {
423 if (!txt.empty()) {
424 FXWindow*w;
425 FXString s=FXPath::simplify(FXPath::absolute(txt)).text();
426 for (w=getFirst(); w; ) {
427 if (strcmp(s.text(), (char*)w->getUserData())==0) {
428 FXWindow*w2=w->getNext();
429 delete (HistMnuCmd*) w;
430 w=w2;
431 } else {
432 w=w->getNext();
433 }
434 }
435 }
436 }
437
438
439
440 FXMenuCommand*HistMenu::find(const FXString &txt)
441 {
442 if (!txt.empty()) {
443 FXWindow*w;
444 FXString s=FXPath::simplify(FXPath::absolute(txt)).text();
445 for (w=getFirst(); w; w=w->getNext()) {
446 if (strcmp(s.text(), (char*)w->getUserData())==0) {
447 return (FXMenuCommand*)w;
448 }
449 }
450 }
451 return NULL;
452 }
453
454
455
456 HistMenu::HistMenu(FXWindow *p,
457 const FXString &caption, const FXString &groupname, FXObject *tgt, FXSelector sel):FXMenuPane(p)
458 {
459 target=tgt;
460 message=sel;
461 created=false;
462 FXchar key[2]="\0";
463 casc=new FXMenuCascade((FXComposite*)p,caption, NULL, this);
464 group=groupname.text();
465 app=p->getApp();
466 for (key[0]='a'; key[0]<='z'; key[0]++) { prepend(app->reg().readStringEntry(group.text(),key, "")); }
467 }
468
469
470
471 HistMenu::~HistMenu()
472 {
473 FXchar key[2]="\0";
474 FXWindow*w;
475 for (key[0]='a'; key[0]<='z'; key[0]++) { append(app->reg().readStringEntry(group.text(),key, "")); }
476 app->reg().deleteSection(group.text());
477 for (w=getFirst(), key[0]='z' ; w && (key[0]>='a'); w=w->getNext(), key[0]--) {
478 app->reg().writeStringEntry(group.text(),key,(char*)w->getUserData());
479 }
480 }
481
482
483
484 void HistMenu::create()
485 {
486 if (created) return;
487 created=true;
488 FXMenuPane::create();
489 casc->create();
490 }
491
492