1 /*
2   FXiTe - The Free eXtensIble Text Editor
3   Copyright (c) 2009-2011 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 <cerrno>
22 
23 #include <fxkeys.h>
24 #include <fx.h>
25 
26 #include "shmenu.h"
27 #include "compat.h"
28 #include "appwin_pub.h"
29 #include "histbox.h"
30 #include "tooltree.h"
31 
32 #include "intl.h"
33 #include "toolmgr.h"
34 
35 
36 
37 #define IsTop(ti) ( (!ti->getParent()) || (!ti->getParent()->getParent()) )
38 
39 #define SetPad(w,p) \
40   (w)->setPadTop(p); \
41   (w)->setPadBottom(p); \
42   (w)->setPadLeft(p); \
43   (w)->setPadRight(p);
44 
45 static const char*default_help_text = _("Configure user-defined commands for the \"Tools\" menu.");
46 
47 static const char*intro_text = _(
48 "Here you can customize the tools menu by\n"
49 "adding your own shell scripts, Lua macros,\n"
50 "and text snippets. The tools can also be\n"
51 "organized into sub-menu \"groups\".\n\n"
52 "To begin, select from one of the top-level\n"
53 "categories on the left, a brief description\n"
54 "of the category will appear at the bottom."
55 );
56 
57 
58 FXDEFMAP(ToolsDialog) ToolsDialogMap[] = {
59   FXMAPFUNC(SEL_CHANGED,  ToolsDialog::ID_TREELIST_CHANGED, ToolsDialog::onTreeListChanged),
60   FXMAPFUNC(SEL_CHORE,    ToolsDialog::ID_TREELIST_CHORE, ToolsDialog::onTreeListAfterChanged),
61   FXMAPFUNC(SEL_KEYPRESS, ToolsDialog::ID_ACCELFIELD,  ToolsDialog::onAccelField),
62   FXMAPFUNC(SEL_CHANGED,  ToolsDialog::ID_NAMEFIELD,   ToolsDialog::onNameField),
63   FXMAPFUNC(SEL_COMMAND,  ToolsDialog::ID_OPTCHOOSE,   ToolsDialog::onChooseOpt),
64   FXMAPFUNC(SEL_COMMAND,  ToolsDialog::ID_MODIFIED,    ToolsDialog::onModified),
65   FXMAPFUNCS(SEL_COMMAND, ToolsDialog::ID_APPLY_CLICK, ToolsDialog::ID_NEW_TOOL_CLICK, ToolsDialog::onButtonClick),
66   FXMAPFUNC(SEL_MIDDLEBUTTONRELEASE, ToolsDialog::ID_ACCELFIELD, ToolsDialog::onAccelField),
67   FXMAPFUNC(SEL_CHORE, ToolsDialog::ID_NEW_SCAN_CHORE,ToolsDialog::onNewScanChore),
68   FXMAPFUNC(SEL_TIMEOUT, ToolsDialog::ID_NEW_SCAN_CHORE,ToolsDialog::onNewScanChore),
69   FXMAPFUNC(SEL_COMMAND, FXDialogBox::ID_ACCEPT, ToolsDialog::onClose),
70   FXMAPFUNC(SEL_COMMAND, FXDialogBox::ID_CANCEL, ToolsDialog::onClose),
71   FXMAPFUNC(SEL_CLOSE, 0, ToolsDialog::onClose),
72   FXMAPFUNC(SEL_CHORE, FXDialogBox::ID_ACCEPT, ToolsDialog::onClose),
73   FXMAPFUNC(SEL_COMMAND, ToolsDialog::ID_MOVELIST_CHOOSE,ToolsDialog::onMoveListChoose)
74 };
75 
76 FXIMPLEMENT(ToolsDialog,FXDialogBox,ToolsDialogMap,ARRAYNUMBER(ToolsDialogMap));
77 
78 
onModified(FXObject * o,FXSelector sel,void * p)79 long ToolsDialog::onModified(FXObject*o, FXSelector sel, void*p)
80 {
81   setModified(true);
82   return 0;
83 }
84 
85 
86 
87 typedef struct {
88   const char*filepath;
89   FXString index;
90   FXString name;
91   FXString accel;
92   FXString flag;
93   FXString ext;
94 } ToolInfo;
95 
96 
97 
ParseToolInfo(const char * filepath,ToolInfo & info)98 static bool ParseToolInfo(const char *filepath, ToolInfo&info)
99 {
100   info.filepath=filepath;
101   FXString fn=FXPath::name(filepath);
102 
103   info.index=fn.section('.',0);
104   if (!((info.index.length()==2)&&isdigit(info.index[0])&&isdigit(info.index[1]))) {
105     return false;
106   }
107   fn.erase(0,3);
108   if (fn.contains('@')) {
109     info.name=fn.section('@',0);
110     info.accel=fn.section('@',1);
111     info.ext=FXPath::extension(info.accel);
112     info.accel=FXPath::stripExtension(info.accel);
113     info.flag=FXPath::extension(info.accel);
114     info.accel=FXPath::stripExtension(info.accel);
115   } else {
116     info.name=fn;
117     info.accel="";
118     info.ext=FXPath::extension(info.name);
119     info.name=FXPath::stripExtension(info.name);
120     info.flag=FXPath::extension(info.name);
121     info.name=FXPath::stripExtension(info.name);
122   }
123   return true;
124 }
125 
126 
127 
onNameField(FXObject * o,FXSelector sel,void * p)128 long ToolsDialog::onNameField(FXObject*o, FXSelector sel, void*p)
129 {
130   menukey_list->clearItems();
131   char letter[2];
132   letter[1]='\0';
133   menukey_list->appendItem(" ",NULL,(void*)(FXival)(-1)); // Leave first item empty for no menu key
134   for (int i=0; i<name_field->getText().length(); i++) {
135     letter[0]=name_field->getText()[i];
136     if ( (letter[0]!=' ')) {
137       // Save the actual char index to the item's data pointer.
138       menukey_list->appendItem(letter,NULL,(void*)(FXival)i);
139     }
140   }
141   menukey_list->setNumVisible(menukey_list->getNumItems());
142   if (o) { setModified(true); }
143   return 0;
144 }
145 
146 
147 
clear()148 void ToolsDialog::clear()
149 {
150   name_panel->hide();
151   accel_panel->hide();
152   new_panel->hide();
153   delete_btn->hide();
154   edit_btn->hide();
155   change_panel->hide();
156   move_panel->hide();
157   move_list->clearItems();
158   move_btn->disable();
159   extn_field->hide();
160   intro_lab->hide();
161   extn_field->getPrev()->hide();
162   accel_field->setText("");
163   ctrl_chk->setCheck(false);
164   alt_chk->setCheck(false);
165   shift_chk->setCheck(false);
166   index_field->setText("");
167   name_field->setText("");
168   extn_field->setText("");
169   menukey_list->clearItems();
170   if (opts_panel) {delete opts_panel; }
171   opts_panel=NULL;
172   opt_rad=NULL;
173   opt_chk=NULL;
174   setModified(false);
175 }
176 
177 
178 
create_options(FXTreeItem * item,const FXString & flag)179 void ToolsDialog::create_options(FXTreeItem*item, const FXString &flag)
180 {
181   UserMenu*um=tree->GetUserMenu(item);
182   helptext->setText(um->helptext);
183   const char**opts=um->getFlags();
184   if (opts) {
185     opts_panel=new FXGroupBox(right_box,"Options",FRAME_SUNKEN|FRAME_RAISED|FRAME_THICK|LAYOUT_FILL_X);
186     int i,n=0;
187     for (i=0; opts[i]; i++) { n++; }
188     switch (n) {
189       case 0:
190       case 1:
191       { break; }
192       case 2: {
193         opt_chk=new FXCheckButton(opts_panel,opts[1], this, ID_OPTCHOOSE);
194         opt_chk->setUserData((void*)opts[0]);
195         opt_chk->setCheck(strcmp(opts[0],flag.text())==0);
196         break;
197       }
198       default: {
199         for (i=0; opts[i]; i++) {
200           if ((i%2)==0) {
201             FXRadioButton*rb=new FXRadioButton(opts_panel,opts[i+1], this, ID_OPTCHOOSE);
202             rb->setUserData((void*)opts[i]);
203             rb->setCheck(strcmp(opts[i],flag.text())==0);
204             if (!opt_rad) { opt_rad=rb; }
205           }
206         }
207         bool have_check=false;
208         for (FXWindow*w=opt_rad; dynamic_cast<FXRadioButton*>(w); w=w->getNext()) {
209            if (((FXRadioButton*)w)->getCheck()) {
210              have_check=true;
211              break;
212            }
213         }
214         if (!have_check) {
215           opt_rad->setCheck(true);
216         }
217       }
218     }
219     opts_panel->create();
220     opts_panel->reparent(right_box,apply_btn->getParent());
221   }
222 }
223 
224 
225 
AskSaveChanges(FXWindow * w,bool hasitems,const char * name)226 static FXuint AskSaveChanges(FXWindow*w, bool hasitems,const char*name)
227 {
228   return FXMessageBox::question(w, MBOX_SAVE_CANCEL_DONTSAVE,
229     _("Tool was modified"), "%s %s \"%s\" %s\n\n%s\n",
230     _("The"),
231     hasitems?_("group"):_("tool"),
232     name,
233     _("has unsaved settings."),
234     _("Do you want to save your changes?")
235   );
236 }
237 
238 
239 
onClose(FXObject * o,FXSelector sel,void * p)240 long ToolsDialog::onClose(FXObject*o, FXSelector sel, void*p)
241 {
242   if ( (FXSELTYPE(sel)==SEL_COMMAND) || (FXSELTYPE(sel)==SEL_CLOSE) ) {
243     if (tree->PrevItem() && modified) {
244       tree->setCurrentItem(tree->PrevItem());
245       tree->selectItem(tree->PrevItem());
246       FXuint q=AskSaveChanges(this, tree->PrevItem()->hasItems(), tree->PrevItem()->getText().text());
247       switch (q) {
248         case MBOX_CLICKED_SAVE:
249         case MBOX_CLICKED_YES:{
250           if (SaveChanges()) {
251             // Calling stopModal() directly from here causes a crash, so use a chore...
252             getApp()->addChore(this,ID_ACCEPT);
253           }
254           return 1;
255         }
256         case MBOX_CLICKED_NO: {
257           // Discard changes and close...
258           break;
259         }
260         default: {
261           // User cancelled, so don't close...
262           return 1;
263         }
264       }
265     }
266   }
267   getApp()->stopModal(this,true);
268   return 1;
269 }
270 
271 
272 
onMoveListChoose(FXObject * o,FXSelector sel,void * p)273 long ToolsDialog::onMoveListChoose(FXObject*o, FXSelector sel, void*p)
274 {
275   if (move_list->getItemData(move_list->getCurrentItem())) {
276     move_btn->enable();
277   } else {
278     move_btn->disable();
279   }
280   return 1;
281 }
282 
283 
284 
SetMoveOptions(FXTreeItem * item)285 void ToolsDialog::SetMoveOptions(FXTreeItem *item)
286 {
287   move_panel->show();
288   move_list->clearItems();
289   if (item->getParent()->getParent()!=tree->Tools()) {
290     move_list->appendItem(_("^- Up one level"),NULL,item->getParent()->getParent());
291   }
292   for (FXTreeItem*ti=item->getParent()->getFirst(); ti; ti=ti->getNext()) {
293     if ( ti->hasItems() && (ti!=item) ) {
294       move_list->appendItem(ti->getText(),NULL,ti);
295     }
296   }
297   if (move_list->getNumItems()) {
298     move_list->prependItem(" ",NULL,NULL);
299     move_lab->enable();
300     move_list->enable();
301     move_list->setCurrentItem(0);
302     move_list->setNumVisible(move_list->getNumItems());
303   } else {
304     move_lab->disable();
305     move_list->disable();
306   }
307 }
308 
309 
310 
onTreeListAfterChanged(FXObject * o,FXSelector sel,void * p)311 long ToolsDialog::onTreeListAfterChanged(FXObject*o, FXSelector sel, void*p)
312 {
313   FXTreeItem*item=(FXTreeItem*)p;
314   if (tree->PrevItem() && (tree->PrevItem()!=p) && modified) {
315     tree->setCurrentItem(tree->PrevItem());
316     tree->selectItem(tree->PrevItem());
317     FXuint q=AskSaveChanges(this,tree->PrevItem()->hasItems(),tree->PrevItem()->getText().text());
318 
319     switch (q) {
320       case MBOX_CLICKED_SAVE:
321       case MBOX_CLICKED_YES:{
322         SaveChanges();
323         return 0;
324       }
325       case MBOX_CLICKED_NO: {
326         /*discard changes*/
327         break;
328       }
329       case MBOX_CLICKED_CANCEL: {
330          return 0;
331       }
332       default: {}
333     }
334   }
335   tree->setCurrentItem(item);
336   tree->selectItem(item);
337   tree->SetPrevItem(item);
338 
339   clear();
340   if (item->hasItems()) {
341     if (item==tree->Tools()) { intro_lab->show(); } else { new_panel->show(); }
342     if (!IsTop(item)) {
343       name_panel->show();
344       props_lab->setText(_("Group properties"));
345       delete_btn->show();
346       change_panel->show();
347       SetMoveOptions(item);
348       FXMenuCascade*casc=(FXMenuCascade*)item->getData();
349       FXPopup*pop=casc->getMenu();
350       ToolInfo info;
351       ParseToolInfo((const char*)pop->getUserData(),info);
352       index_field->setText(info.index);
353       name_field->setText(casc->getText());
354       onNameField(NULL,0,NULL);
355       menukey_list->setCurrentItem(
356         menukey_list->findItemByData((void*)(FXival)info.name.find_first_of('_'))
357       );
358     }
359     if (item==tree->Tools()) {
360       helptext->setText(default_help_text);
361     } else {
362       helptext->setText(tree->GetUserMenu(item)->helptext);
363     }
364   } else {
365     name_panel->show();
366     props_lab->setText(_("Tool properties"));
367     accel_panel->show();
368     delete_btn->show();
369     edit_btn->show();
370     change_panel->show();
371     SetMoveOptions(item);
372     extn_field->show();
373     extn_field->getPrev()->show();
374     FXMenuCommand*mnucmd=(FXMenuCommand*)item->getData();
375     ToolInfo info;
376     ParseToolInfo((const char*)mnucmd->getUserData(),info);
377     create_options(item, info.flag);
378     index_field->setText(info.index);
379     name_field->setText(mnucmd->getText());
380     extn_field->setText(info.ext);
381     onNameField(NULL,0,NULL);
382     menukey_list->setCurrentItem(
383       menukey_list->findItemByData((void*)(FXival)info.name.find_first_of('_'))
384     );
385     FXHotKey hk=parseAccel(info.accel);
386     FXuint mask=FXSELTYPE(hk);
387     FXuint key=FXSELID(hk);
388     ctrl_chk->setCheck(mask&CONTROLMASK);
389     alt_chk->setCheck(mask&ALTMASK);
390     shift_chk->setCheck(mask&SHIFTMASK);
391     if (key) {
392       accel_field->setText(unparseAccel(key).upper());
393     } else {
394       accel_field->setText("");
395     }
396   }
397   return 0;
398 }
399 
400 
401 
onTreeListChanged(FXObject * o,FXSelector sel,void * p)402 long ToolsDialog::onTreeListChanged(FXObject*o, FXSelector sel, void*p)
403 {
404   //  If we pop up a dialog from here it causes the treelist to
405   //  miss the next mouse-up event, so use a chore instead...
406   getApp()->addChore(this,ID_TREELIST_CHORE,p);
407   return 0;
408 }
409 
410 
411 
onChooseOpt(FXObject * o,FXSelector sel,void * p)412 long ToolsDialog::onChooseOpt(FXObject*o, FXSelector sel, void*p)
413 {
414   if (opt_rad) {
415     for (FXWindow*w=opt_rad; dynamic_cast<FXRadioButton*>(w); w=w->getNext()) {
416       ((FXRadioButton*)w)->setCheck(w==o);
417     }
418   }
419   setModified(true);
420   return 1;
421 }
422 
423 
424 
onAccelField(FXObject * o,FXSelector sel,void * p)425 long ToolsDialog::onAccelField(FXObject*o, FXSelector sel, void*p)
426 {
427   if (FXSELTYPE(sel)==SEL_MIDDLEBUTTONRELEASE) { return 1; }
428   FXEvent*ev=(FXEvent*)p;
429   if (
430     (ev->code>=KEY_F1 && ev->code<=KEY_F12) ||
431     (ev->code>=KEY_0 && ev->code<=KEY_9) ||
432     (ev->code>=KEY_A && ev->code<=KEY_Z) ||
433     (ev->code>=KEY_a && ev->code<=KEY_z)
434   ) {
435     accel_field->setText(unparseAccel(ev->code).upper());
436     if (ev->state) {
437       ctrl_chk->setCheck(ev->state&CONTROLMASK);
438       alt_chk->setCheck(ev->state&ALTMASK);
439       shift_chk->setCheck(ev->state&SHIFTMASK);
440     }
441     setModified(true);
442   } else {
443     switch (ev->code) {
444       case KEY_Tab:
445       case KEY_Up:
446       case KEY_Down:
447       case KEY_Escape: {
448         return 0;
449       }
450       case KEY_Delete:
451       case KEY_BackSpace: {
452         accel_field->setText("");
453         ctrl_chk->setCheck(false);
454         alt_chk->setCheck(false);
455         shift_chk->setCheck(false);
456         setModified(true);
457       }
458     }
459   }
460   return 1;
461 }
462 
463 
464 
setModified(bool ismod)465 void ToolsDialog::setModified(bool ismod)
466 {
467   modified=ismod;
468   if (modified) {
469     apply_btn->enable();
470     reset_btn->enable();
471     move_list->clearItems();
472     move_list->disable();
473     move_lab->disable();
474     move_btn->disable();
475   } else {
476     apply_btn->disable();
477     reset_btn->disable();
478     move_list->enable();
479     move_lab->enable();
480   }
481 }
482 
483 
484 
BuildName(FXString & path,bool isdir)485 bool ToolsDialog::BuildName(FXString &path, bool isdir)
486 {
487   if (tree->PrevItem()->hasItems()) {
488     tree->SetSavedPath((const char*)((((FXMenuCascade*)(tree->PrevItem()->getData()))->getMenu())->getUserData()));
489   } else {
490     tree->SetSavedPath((const char*)(((FXMenuCommand*)(tree->PrevItem()->getData()))->getUserData()));
491   }
492   path=FXPath::directory(tree->SavedPath());
493   path.append(PATHSEP);
494   switch (index_field->getText().length()) {
495     case 0:  {
496       path.append("00");
497       break;
498     }
499     case 1:  {
500       path.append("0");
501       path.append(index_field->getText());
502       break;
503     }
504     default: {
505       path.append(index_field->getText());
506     }
507   }
508   path.append(".");
509   FXString tmp=name_field->getText().trim().simplify();
510 
511   if (tmp.empty()|| (compare(tmp, " ")==0)) {
512       FXMessageBox::error(this, MBOX_OK, _("Invalid name"), "%s",
513         _("You must specify a name for this item."));
514       name_field->setFocus();
515       name_field->selectAll();
516       path="";
517       return false;
518   }
519   tmp=tmp.lower();
520   static const char*allowed="abcdefghijklmnopqrstuvwxyz0123456789 ";
521   for (FXint i=0; i<tmp.length(); i++) {
522     if (!strchr(allowed, tmp[i])) {
523       FXMessageBox::error(this, MBOX_OK, _("Invalid name"), "%s",
524         _("Name can only contain letters, numbers, and spaces."));
525       name_field->setFocus();
526       name_field->setSelection(i,1);
527       path="";
528       return false;
529     }
530   }
531   tmp.substitute(' ', '-', true);
532   if (menukey_list->getCurrentItem()>0) {
533     tmp.insert((FXival)(menukey_list->getItemData(menukey_list->getCurrentItem())),"_");
534   }
535   path.append(tmp);
536   if (!accel_field->getText().empty()) {
537     FXuint accel_state=0;
538     accel_state|=ctrl_chk->getCheck()?CONTROLMASK:0;
539     accel_state|=alt_chk->getCheck()?ALTMASK:0;
540     accel_state|=shift_chk->getCheck()?SHIFTMASK:0;
541     if ( ((accel_state==0)||(accel_state==SHIFTMASK)) && (accel_field->getText().length()<2) ) {
542       FXMessageBox::error(this,MBOX_OK, _("Invalid accelerator"), "%s\n\n%s",
543         _("Alphanumeric accelerators must have a [Ctrl] or [Alt] modifier."),
544         _("Only function keys [F1] - [F12] can be used without a modifier.")
545       );
546     accel_field->setFocus();
547     accel_field->selectAll();
548     path="";
549     return false;
550     }
551     FXuint accel_key=parseAccel(accel_field->getText());
552     path.append("@");
553     path.append(unparseAccel(FXSEL(accel_state,accel_key)));
554   }
555   if (opt_chk && opt_chk->getCheck()) {
556     path.append(".");
557     path.append((const char*)opt_chk->getUserData());
558   } else if (opt_rad) {
559     for (FXWindow*w=opt_rad; dynamic_cast<FXRadioButton*>(w); w=w->getNext()) {
560       if (((FXRadioButton*)w)->getCheck()) {
561         path.append(".");
562         path.append((const char*)(w->getUserData()));
563         break;
564       }
565     }
566   }
567   if (!isdir) {
568     path.append(".");
569     path.append(extn_field->getText().empty()?"txt":extn_field->getText());
570   }
571   return true;
572 }
573 
574 
575 
onNewScanChore(FXObject * o,FXSelector sel,void * p)576 long ToolsDialog::onNewScanChore(FXObject*o, FXSelector sel, void*p)
577 {
578   if (FXSELTYPE(sel)==SEL_CHORE) {
579     if (tree->DummyItem()) {
580       tree->SetPrevItem(tree->DummyItem());
581       tree->scan(true);
582       name_field->setFocus();
583       getApp()->addTimeout(this,ID_NEW_SCAN_CHORE,ONE_SECOND/10);
584     }
585   } else {
586     name_field->setFocus();
587     name_field->selectAll();
588   }
589   return 1;
590 }
591 
592 
593 
RenameItem(const FXString & oldpath,const FXString & newpath)594 bool ToolsDialog::RenameItem(const FXString &oldpath, const FXString &newpath)
595 {
596   bool isdir=FXStat::isDirectory(oldpath);
597   bool dirty=false;
598   FXString filelist=FXString::null;
599   if (isdir) {
600     FXString oldprefix=oldpath+PATHSEPSTRING;
601     FXString newprefix=newpath+PATHSEPSTRING;
602     FXString *openfiles=TopWinPub::NamedFiles();
603     for (FXString*p=openfiles; !p->empty(); p++) {
604       if (FX::compare(oldprefix,*p,oldpath.length())==0) {
605         if (TopWinPub::IsFileOpen(*p,false)==2) {
606           dirty=true;
607           break;
608         }
609         FXString filename=p->text()+oldprefix.length();
610         filelist.append(newprefix+filename);
611         filelist.append('\n');
612         TopWinPub::IsFileOpen(*p,true);
613         TopWinPub::CloseFile(false,false);
614       }
615     }
616     delete[] openfiles;
617   }
618   switch (TopWinPub::IsFileOpen(oldpath,false)) {
619     case 0: { break; }
620     case 1: {
621       TopWinPub::IsFileOpen(oldpath,true);
622       TopWinPub::CloseFile(false,false);
623       filelist.append(newpath);
624       filelist.append('\n');
625       break;
626     }
627     case 2: {
628       dirty=true;
629       break;
630     }
631   }
632 
633   if (dirty) {
634     FXMessageBox::error(this, MBOX_OK, _("Active unsaved changes"), "%s\n%s",
635     _("Cannot proceed because a file to be renamed is currently"),
636     _("open in the editor, and has unsaved changes.")
637     );
638     return false;
639   }
640 
641   bool result = isdir?FXDir::rename(oldpath,newpath):FXFile::rename(oldpath,newpath);
642   if (result) {
643     for (FXint i=0; i<filelist.contains('\n'); i++) { TopWinPub::OpenFile(filelist.section('\n',i).text(),NULL,false,false); }
644   } else {
645     FXMessageBox::error(this, MBOX_OK, _("Rename error"), "%s %s:\n%s:\n %s\n%s:\n %s\n\n%s",
646       _("Failed to rename"),
647       tree->PrevItem()->hasItems()?_("folder"):_("file"),
648       _("from"), oldpath.text(),
649       _("to"), newpath.text(),
650       SystemErrorStr());
651   }
652 
653   return result;
654 }
655 
656 
657 
SaveChanges()658 bool ToolsDialog::SaveChanges()
659 {
660   FXString newpath;
661   if (BuildName(newpath,tree->PrevItem()->hasItems())) {
662     FXString oldpath=tree->getFilePath(tree->PrevItem());
663     if (compare(oldpath,newpath)==0) { return true; }
664     if (FXStat::exists(newpath)) {
665       FXMessageBox::error(this, MBOX_OK,
666         _("Rename error"), "%s %s:\n%s:\n %s\n%s:\n %s\n\n%s",
667         _("Failed to rename"),
668         tree->PrevItem()->hasItems()?_("folder"):_("file"),
669         _("from"),  oldpath.text(),
670         _("to"), newpath.text(),
671         _("That name is already in use."));
672     } else {
673       bool success=RenameItem(oldpath,newpath);
674       if (success) {
675         tree->SetSavedPath(newpath.text());
676         FXStat::mode(newpath, GetPermsForItem(tree->PrevItem()));
677         tree->scan(true);
678         return true;
679       }
680     }
681   }
682   return false;
683 }
684 
685 #define IsExecSnippet() ( (comparecase(kind,"snippets")==0) && opt_rad && (!opt_rad->getCheck()) )
686 
687 
GetPermsForItem(FXTreeItem * item)688 FXuint ToolsDialog::GetPermsForItem(FXTreeItem *item)
689 {
690   if (item && item->hasItems()) { return FXIO::OwnerFull; }
691   FXString kind=tree->GetUserMenu(item?item:tree->getCurrentItem())->getText();
692   return ( (comparecase(kind,"commands")==0) || (comparecase(kind,"filters")==0)  || IsExecSnippet() )?
693     FXIO::OwnerFull:FXIO::OwnerReadWrite;
694 }
695 
696 
697 
onButtonClick(FXObject * o,FXSelector sel,void * p)698 long ToolsDialog::onButtonClick(FXObject*o, FXSelector sel, void*p)
699 {
700   switch (FXSELID(sel)) {
701     case ID_APPLY_CLICK: {
702       SaveChanges();
703       break;
704     }
705     case ID_RESET_CLICK: {
706       onTreeListAfterChanged(this,0,tree->PrevItem());
707       break;
708     }
709     case ID_DELETE_CLICK: {
710       FXString remove_file;
711       if (tree->PrevItem()->hasItems()) {
712         if (tree->PrevItem()->getFirst()) {
713           FXMessageBox::error(this, MBOX_OK, _("Directory not empty"),
714             _("Unable to remove a group that still contains items,\n"
715             "you should delete its tools and sub-groups first.")
716           );
717           return 1;
718         } else {
719           remove_file=tree->getFilePath(tree->PrevItem());
720           if (!FXDir::remove(remove_file)) {
721             FXMessageBox::error(this, MBOX_OK, _("Delete failed"), "%s:\n%s\n%s\n",
722               _("Unable to remove directory"),
723               remove_file.text(), SystemErrorStr()
724             );
725             return 1;
726           }
727         }
728       } else {
729         remove_file=tree->getFilePath(tree->PrevItem());
730         if ( (FXStat::size(remove_file)==0)||(FXMessageBox::warning(this,MBOX_YES_NO,
731              _("Warning!"), "%s \"%s\" %s?\n\n%s",
732              _("Are you sure you want to remove the "),
733               tree->PrevItem()->getText().text(),
734              _("tool"),
735              _("If you choose \"Yes\", the file associated with this\n"
736                "item will be permanently deleted from disk!")
737              ) == MBOX_CLICKED_YES)
738            )
739         {
740           if (!FXFile::remove(remove_file)) {
741             FXMessageBox::error(this, MBOX_OK, _("Delete failed"), "%s:\n%s\n\n%s\n",
742               _("Unable to delete file"), remove_file.text(), SystemErrorStr()
743             );
744             return 1;
745           }
746         }
747       }
748       tree->removeItem(tree->PrevItem());
749       tree->SetPrevItem(tree->getCurrentItem());
750       tree->scan(true);
751       break;
752     }
753     case ID_EDIT_CLICK: {
754       TopWinPub::OpenFile(
755         (const char*)(((FXMenuCommand*)(tree->PrevItem()->getData()))->getUserData()),
756         NULL,false,false
757       );
758       break;
759     }
760     case ID_NEW_MENU_CLICK: {
761       tree->MakeDummyMenu(tree->getCurrentItem());
762       getApp()->addChore(this, ID_NEW_SCAN_CHORE);
763       break;
764     }
765     case ID_NEW_TOOL_CLICK: {
766       tree->MakeDummyTool(tree->getCurrentItem(),GetPermsForItem());
767       getApp()->addChore(this, ID_NEW_SCAN_CHORE);
768       break;
769     }
770     case ID_MOVE_CLICK: {
771       FXTreeItem*dst=(FXTreeItem*)move_list->getItemData(move_list->getCurrentItem());
772       FXTreeItem*src=tree->getCurrentItem();
773       FXString dstname=tree->getFilePath(dst);
774       FXString srcname=tree->getFilePath(src);
775       dstname.append(PATHSEP);
776       dstname.append(FXPath::name(srcname));
777       if (FXStat::exists(dstname)) {
778         FXMessageBox::error(this, MBOX_OK, _("Naming collision"),
779           "%s \"%s\" %s \"%s\":\n\n%s \"%s/%s\" %s",
780           _("Cannot move item"),
781           src->getText().text(),
782           _("to"),
783           dst->getText().text(),
784           _("An item named"),
785           dst->getText().text(),
786           src->getText().text(),
787           _("aready exists.")
788           );
789       } else {
790         bool success=RenameItem(srcname,dstname);
791         if (success) {
792           tree->setCurrentItem(dst);
793           tree->SetPrevItem(dst);
794           tree->scan(true);
795         }
796       }
797     }
798   }
799   return 1;
800 }
801 
802 
803 
create()804 void ToolsDialog::create()
805 {
806   FXDialogBox::create();
807   FXint disp_wdt=getApp()->getRootWindow()->getWidth()*0.875;
808   FXint ctrl_wdt=split->getSplit(1)*2;
809   setWidth(disp_wdt<ctrl_wdt?disp_wdt:ctrl_wdt);
810   FXint disp_hgt=getApp()->getRootWindow()->getHeight()*0.833;
811   if (getDefaultHeight()>disp_hgt) { setHeight(disp_hgt); }
812 }
813 
814 
815 
ToolsDialog(FXTopWindow * win,UserMenu ** menus)816 ToolsDialog::ToolsDialog(FXTopWindow*win, UserMenu**menus):
817   FXDialogBox(win, _("Tools Manager"),DECOR_TITLE|DECOR_BORDER)
818 {
819   FXVerticalFrame*vbox;
820   opts_panel=NULL;
821   SetPad(this,0);
822   vbox = new FXVerticalFrame(this,LAYOUT_FILL);
823   SetPad(vbox,0);
824 
825   split=new FXSplitter(vbox,LAYOUT_FILL|SPLITTER_REVERSED);
826 
827   left_box=new FXVerticalFrame(split,LAYOUT_FILL_Y|LAYOUT_FILL_X|FRAME_SUNKEN|FRAME_THICK);
828   SetPad(left_box,0);
829   right_box=new FXVerticalFrame(split,LAYOUT_FILL_Y|LAYOUT_FILL_X|FRAME_RAISED|FRAME_SUNKEN);
830 
831   intro_lab=new FXLabel(right_box,intro_text,NULL, LABEL_NORMAL|JUSTIFY_LEFT);
832 
833   name_panel=new FXVerticalFrame(right_box,FRAME_NONE|LAYOUT_FILL_X);
834   props_lab=new FXLabel(name_panel, "",NULL, LAYOUT_FILL_X|JUSTIFY_CENTER_X);
835   FXHorizontalFrame*strip;
836 
837   strip=new FXHorizontalFrame(name_panel,LAYOUT_FILL_X);
838   new FXLabel(strip,_("&Name"));
839   name_field=new ClipTextField(strip,24,this,ID_NAMEFIELD,FRAME_SUNKEN|FRAME_THICK|LAYOUT_FILL_X);
840 
841   strip=new FXHorizontalFrame(name_panel,LAYOUT_FILL_X);
842   new FXLabel(strip,_("Menu &key"));
843   menukey_list=new FXListBox(strip,this,ID_MODIFIED);
844   menukey_list->appendItem("  ");
845   new FXLabel(strip,_("  &Index"));
846   index_field=new ClipTextField(strip,2,this,ID_MODIFIED,TEXTFIELD_LIMITED|TEXTFIELD_INTEGER|FRAME_SUNKEN|FRAME_THICK);
847   new FXLabel(strip,_("  E&xt'n"));
848   extn_field=new ClipTextField(strip,4,this,TEXTFIELD_LIMITED|FRAME_SUNKEN|FRAME_THICK);
849 
850   accel_panel=new FXGroupBox(right_box,"Shortcut",FRAME_SUNKEN|FRAME_RAISED|FRAME_THICK|LAYOUT_CENTER_X);
851 
852   strip=new FXHorizontalFrame(accel_panel,LAYOUT_FILL_X);
853   new FXLabel(strip,_("&Hot key:"));
854   accel_field=new ClipTextField(strip,3,this,ID_ACCELFIELD,TEXTFIELD_LIMITED|FRAME_SUNKEN|FRAME_THICK);
855 
856 
857   ctrl_chk  = new FXCheckButton(strip,_("C&trl"),this,ID_MODIFIED);
858   ctrl_chk->setPadLeft(22);
859   alt_chk   = new FXCheckButton(strip,_("A&lt"),this,ID_MODIFIED);
860   shift_chk = new FXCheckButton(strip,_("Shi&ft"),this,ID_MODIFIED);
861 
862   change_panel=new FXHorizontalFrame(right_box,LAYOUT_RIGHT|PACK_UNIFORM_WIDTH);
863   apply_btn=new FXButton(change_panel,_(" &Save changes "), NULL, this, ID_APPLY_CLICK);
864   reset_btn=new FXButton(change_panel,_(" &Undo changes "), NULL, this, ID_RESET_CLICK);
865 
866 
867   strip=new FXHorizontalFrame(right_box,PACK_UNIFORM_WIDTH);
868   delete_btn=new FXButton(strip, _(" &Delete Item "), NULL, this, ID_DELETE_CLICK);
869   edit_btn=new FXButton(strip, _(" Open in &editor "), NULL, this, ID_EDIT_CLICK);
870 
871   move_panel=new FXHorizontalFrame(right_box,FRAME_GROOVE|LAYOUT_FILL_X);
872   move_lab=new FXLabel(move_panel,_("Move &to:"));
873   move_list=new FXListBox(move_panel,this,ID_MOVELIST_CHOOSE,FRAME_SUNKEN|FRAME_THICK|LAYOUT_FILL_X);
874   move_btn=new FXButton(move_panel, _("Mo&ve"),NULL, this, ID_MOVE_CLICK);
875 
876 
877   new_panel=new FXVerticalFrame(right_box,FRAME_NONE|LAYOUT_FILL_X|LAYOUT_BOTTOM);
878   new_panel->setVSpacing(0);
879   strip=new FXHorizontalFrame(new_panel,LAYOUT_FILL_X|PACK_UNIFORM_WIDTH);
880   new_menu_btn=new FXButton(strip, _("New group"), NULL, this, ID_NEW_MENU_CLICK,BUTTON_NORMAL|LAYOUT_FILL_X);
881   new_tool_btn=new FXButton(strip, _("New tool"), NULL, this, ID_NEW_TOOL_CLICK,BUTTON_NORMAL|LAYOUT_FILL_X);
882 
883   strip=new FXHorizontalFrame(vbox,FRAME_GROOVE|LAYOUT_FILL_X);
884   SetPad(strip,0)
885   helptext=new FXLabel(strip, default_help_text,NULL,FRAME_SUNKEN|FRAME_RAISED|FRAME_THICK|LAYOUT_FILL_X|JUSTIFY_LEFT);
886   helptext->setPadLeft(5);
887   new FXButton(strip,_(" &Close "), NULL, this,FXDialogBox::ID_ACCEPT,BUTTON_NORMAL|LAYOUT_RIGHT);
888 
889   setModified(false);
890   tree=new ToolsTree(left_box,this,ID_TREELIST_CHANGED, menus);
891 }
892 
893