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 <cerrno>
21 
22 #include <fxkeys.h>
23 #include <fx.h>
24 
25 #include "shmenu.h"
26 #include "compat.h"
27 
28 #include "intl.h"
29 #include "tooltree.h"
30 
31 #define TREEOPTS TREELIST_BROWSESELECT|TREELIST_SHOWS_LINES|TREELIST_SHOWS_BOXES|LAYOUT_FILL
32 
33 
34 
ToolsTree(FXComposite * p,FXObject * tgt,FXSelector sel,UserMenu ** menus)35 ToolsTree::ToolsTree(FXComposite*p, FXObject*tgt, FXSelector sel, UserMenu**menus):FXTreeList(p,tgt,sel,TREEOPTS) {
36   menu_list=menus;
37   saved_path="";
38   prev_item=NULL;
39   dummy_item=NULL;
40   scan(false);
41 }
42 
43 
44 
getFilePath(FXTreeItem * item)45 const char*ToolsTree::getFilePath(FXTreeItem*item) {
46   if ( item->getParent() ) {
47     if ( item->getParent()->getParent()) {
48       if (item->hasItems()) {
49         return (const char*)((((FXMenuCascade*)(item->getData()))->getMenu())->getUserData());
50       } else {
51         return (const char*)(((FXMenuCommand*)(item->getData()))->getUserData());
52       }
53     } else {
54       return ((UserMenu*)(item->getData()))->getTopDir();
55     }
56   } else {
57     return NULL;
58   }
59 }
60 
61 
62 
build_tree(FXTreeItem * item,const FXPopup * menu)63 void ToolsTree::build_tree(FXTreeItem*item, const FXPopup*menu)
64 {
65   item->setHasItems(true);
66   if (menu) {
67     for (FXWindow*w=menu->getFirst(); w; w=w->getNext()) {
68       FXTreeItem*node=new FXTreeItem(((FXMenuCaption*)w)->getText());
69       if (!saved_path.empty()) {
70          const char*this_path;
71          this_path=(const char*)((FXMenuCommand*)w)->getUserData();
72          if (!this_path) {
73            this_path=(const char*)((((FXMenuCascade*)w)->getMenu())->getUserData());
74          }
75          if ( this_path && strcmp(saved_path.text(),this_path) == 0 ) {
76              restored_item=node;
77          }
78       }
79       insertItem(NULL,item,node);
80       node->setData(w);
81       node->setHasItems(false);
82       if (!w->getUserData()) { /* Cascading menus don't carry the path, their popup does */
83         build_tree(node,((FXMenuCascade*)w)->getMenu());
84       }
85     }
86   }
87 }
88 
89 
90 
scan(bool rebuild)91 void ToolsTree::scan(bool rebuild)
92 {
93   FXString item_name="";
94   if (prev_item) {
95     if ((prev_item==tools)||(prev_item->getParent()==tools)) {
96       item_name=prev_item->getText();
97     } else {
98       if (saved_path.empty()) { saved_path=getFilePath(prev_item); }
99     }
100   }
101   if (dummy_item) {
102     if (dummy_item->hasItems()) {
103     FXMenuCascade*casc=(FXMenuCascade*)(dummy_item->getData());
104     FXMenuPane*pane=(FXMenuPane*)casc->getMenu();
105     free(pane->getUserData());
106     delete pane;
107     delete casc;
108     dummy_item->setData(NULL);
109     } else {
110       FXMenuCommand*cmd=(FXMenuCommand*)(dummy_item->getData());
111       free(cmd->getUserData());
112       delete cmd;
113       dummy_item->setData(NULL);
114     }
115     dummy_item=NULL;
116   }
117   clearItems();
118   prev_item=NULL;
119   restored_item=NULL;
120   tools = new FXTreeItem(_("Tools"));
121   tools->setHasItems(true);
122   appendItem(NULL,tools);
123   for (UserMenu**um=menu_list; *um; um++) {
124     if (rebuild) { (*um)->rescan(); }
125     FXTreeItem *item=new FXTreeItem((*um)->getText());
126     item->setHasItems(true);
127     item->setData(*um);
128     insertItem(NULL,tools,item);
129     build_tree(item,(*um)->menu());
130   }
131   saved_path="";
132   if (item_name.empty()) {
133     for (FXTreeItem *ti=restored_item; ti; ti=ti->getParent()) { expandTree(ti); }
134   } else {
135     if (compare(tools->getText(),item_name)==0) {
136       restored_item=tools;
137     } else {
138       for (FXTreeItem *ti=tools->getFirst(); ti; ti=ti->getNext()) {
139         if (compare(ti->getText(),item_name)==0) {
140           restored_item=ti;
141           break;
142         }
143       }
144     }
145   }
146   expandTree(tools);
147   if (!restored_item) { restored_item=tools; }
148   expandTree(restored_item);
149   setCurrentItem(restored_item);
150   setFocus();
151   if (target&&message) {
152     target->handle(this,FXSEL(SEL_CHANGED,message),restored_item);
153   }
154 }
155 
156 
157 
GetUserMenu(FXTreeItem * item)158 UserMenu*ToolsTree::GetUserMenu(FXTreeItem*item) {
159   for (FXTreeItem*i=item; i; i=i->getParent()) {
160     if (i->getParent()==tools) {
161       return (UserMenu*)(i->getData());
162     }
163   }
164   return NULL;
165 }
166 
167 
MakeDummyMenu(FXTreeItem * parent_item)168 void ToolsTree::MakeDummyMenu(FXTreeItem*parent_item)
169 {
170   FXString path=getFilePath(parent_item);
171   path.append(PATHSEP);
172   FXint index=0;
173   FXString tmp;
174   while (index<100) {
175     tmp.format("%s%02d.new-group",path.text(),index);
176     if (!FXStat::exists(tmp)) { break; }
177     index++;
178   }
179   if (FXStat::exists(tmp)) {
180       FXMessageBox::error(getShell(), MBOX_OK, _("File error"), "%s:\n%s\n\n%s",
181         _("Failed to create temporary directory"),
182         tmp.text(), _("too many \"New Group\" folders")
183       );
184       dummy_item=NULL;
185       return;
186   }
187   if (!FXDir::create(tmp)) {
188     FXMessageBox::error(getShell(), MBOX_OK, _("File error"), "%s:\n%s\n\n%s",
189       _("Failed to create temporary directory"),
190       tmp.text(), SystemErrorStr()
191     );
192     dummy_item=NULL;
193     return;
194   }
195   dummy_item=new FXTreeItem(_("New Group"));
196   dummy_item->setHasItems(true);
197   appendItem(parent_item,dummy_item);
198   FXMenuPane*pane=new FXMenuPane(this);
199   pane->setUserData(strdup(tmp.text()));
200   FXMenuCascade*casc=new FXMenuCascade(this,"",NULL,pane,0);
201   dummy_item->setData(casc);
202   setCurrentItem(dummy_item);
203   selectItem(dummy_item);
204 }
205 
206 
207 
MakeDummyTool(FXTreeItem * parent_item,FXuint perm)208 void ToolsTree::MakeDummyTool(FXTreeItem*parent_item, FXuint perm)
209 {
210   FXString path=getFilePath(parent_item);
211   const char *ext;
212   UserMenu*um=GetUserMenu(parent_item);
213   switch (um?um->Tag():0) { // Suggest a file extension
214     case 'C':
215     case 'F': { // Commands and Filters use shell script extension.
216 #ifdef WIN32
217       ext="bat";
218 #else
219       ext="sh";
220 #endif
221       break;
222     }
223     case 'M': { // Macros use lua extension
224       ext="lua";
225       break;
226     }
227     default: { // Snippets (maybe just a text file)
228       ext=(perm & FXIO::OwnerExec)?"exec.txt":"read.txt";
229     }
230   }
231   path.append(PATHSEP);
232   FXint index=0;
233   FXString tmp;
234   while (index<100) {
235     tmp.format("%s%02d.new-tool.%s",path.text(),index, ext);
236     if (!FXStat::exists(tmp)) { break; }
237     index++;
238   }
239   if (FXStat::exists(tmp)) {
240       FXMessageBox::error(getShell(), MBOX_OK, _("File error"), "%s:\n%s\n\n%s",
241         _("Failed to create temporary file"),
242         tmp.text(), _("too many \"New Tool\" files")
243       );
244       dummy_item=NULL;
245       return;
246   }
247   if (!FXFile::create(tmp,perm)) {
248     FXMessageBox::error(getShell(), MBOX_OK, _("File error"), "%s:\n%s\n\n%s",
249       _("Failed to create temporary file"),
250       tmp.text(), SystemErrorStr()
251     );
252     dummy_item=NULL;
253     return;
254   }
255   dummy_item=new FXTreeItem(_("New Tool"));
256   dummy_item->setHasItems(false);
257   appendItem(parent_item,dummy_item);
258   FXMenuCommand*cmd=new FXMenuCommand(this,_("New Tool"));
259   cmd->setUserData(strdup(tmp.text()));
260   dummy_item->setData(cmd);
261   setCurrentItem(dummy_item);
262   selectItem(dummy_item);
263 }
264 
265 
266 
267 /* An OK button that is only enabled when the selected tree item is a tool (i.e. a leaf node) */
268 class OkBtn: public FXButton {
269   FXDECLARE(OkBtn);
OkBtn()270   OkBtn(){}
271 public:
onCheckEnable(FXObject * o,FXSelector sel,void * p)272   long onCheckEnable(FXObject*o, FXSelector sel, void*p)
273   {
274     FXTreeItem*item=(FXTreeItem*)p;
275     if (item && !item->hasItems()) { enable(); } else { disable(); }
276     return 1;
277   }
278   enum { ID_CHECK_ENABLE=FXButton::ID_LAST };
OkBtn(FXComposite * p)279   OkBtn(FXComposite*p):FXButton(p,_("OK"),NULL,p,FXDialogBox::ID_ACCEPT,BUTTON_NORMAL|BUTTON_INITIAL|LAYOUT_CENTER_X){}
280 };
281 
282 
283 
284 FXDEFMAP(OkBtn)OkBtnMap[]={ FXMAPFUNC(SEL_CHANGED,OkBtn::ID_CHECK_ENABLE,OkBtn::onCheckEnable) };
285 
286 FXIMPLEMENT(OkBtn,FXButton,OkBtnMap,ARRAYNUMBER(OkBtnMap));
287 
288 
289 
HasTool(FXTreeItem * item)290 static bool HasTool(FXTreeItem*item)
291 {
292   for(FXTreeItem*i=item; i; i=i->getNext()) {
293     if ((!i->hasItems())||HasTool(i->getFirst())) { return true; }
294   }
295   return false;
296 }
297 
298 
299 
300 /* Display a dialog box to let the user select a tool item */
SelectTool(FXWindow * owner,UserMenu ** menus,FXMenuCommand * & mc)301 bool ToolsTree::SelectTool(FXWindow* owner, UserMenu** menus, FXMenuCommand*&mc)
302 {
303   FXDialogBox dlg(owner,"Select tool",DECOR_TITLE|DECOR_BORDER,0,0,240,320,1,1,1,1,0,0);
304   FXVerticalFrame*vbox=new FXVerticalFrame(&dlg,FRAME_RAISED|LAYOUT_FILL);
305   OkBtn*ok=new OkBtn(&dlg);
306   ToolsTree*tree=new ToolsTree(vbox, ok, OkBtn::ID_CHECK_ENABLE, menus);
307   FXHorizontalFrame*btns=new FXHorizontalFrame(vbox,FRAME_RAISED|LAYOUT_FILL_X|LAYOUT_CENTER_X|PACK_UNIFORM_WIDTH);
308   ok->reparent(btns,NULL);
309   new FXButton(btns,_("Cancel"),NULL,&dlg,FXDialogBox::ID_CANCEL,BUTTON_NORMAL|LAYOUT_CENTER_X);
310   if (!HasTool(tree->getFirstItem())) {
311     FXMessageBox::information(owner, MBOX_OK,_("No tools defined"), "%s\n%s",
312       _("You have not defined any custom tools. To create a custom tool,"),
313       _("use the \"Customize menu\" item from the \"Tools\" menu.")
314     );
315     return false;
316   }
317   if (dlg.execute(PLACEMENT_OWNER)) {
318     mc=((FXMenuCommand*)(tree->getCurrentItem()->getData()));
319     return true;
320   } else {
321     return false;
322   }
323 }
324 
325