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<"),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