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 #include <fx.h>
20 #include <fxkeys.h>
21 
22 #include "intl.h"
23 #include "filedlg.h"
24 
25 
26 class MyDirBox: public FXDirBox {
27   public:
fld()28   FXButton*fld() { return field; }
btn()29   FXMenuButton*btn() { return button; }
pn()30   FXPopup*pn() { return pane; }
31 };
32 
33 
34 
35 class FileSel: public FXFileSelector {
36   public:
txtfld()37     FXTextField*txtfld() { return filename; }
fbox()38     FXFileList*fbox() { return filebox; }
nav()39     FXHorizontalFrame* nav() { return navbuttons; }
cncl()40     FXButton* cncl() { return cancel; }
dbox()41     MyDirBox* dbox() { return (MyDirBox*)dirbox; }
icobtn()42     FXButton *icobtn() {
43       for (FXWindow*w=navbuttons->getFirst(); w; w=w->getNext()) {
44         if ((dynamic_cast<FXButton*>(w)!=NULL) && (((FXButton*)w)->getIcon()==iconsicon)) {
45           return (FXButton*)w;
46         }
47       }
48       return NULL;
49     }
ToggleMulti(bool multi)50     void ToggleMulti(bool multi) {
51      // Give some indication that the user can't manually enter file names in multi mode.
52      FXString label_text;
53       if (multi) {
54         FXColor bgcolor=getApp()->getBaseColor();
55         label_text=_("Selected : ");
56         filename->setEditable(false);
57         filename->setBackColor(bgcolor);
58         filename->setHiliteColor(bgcolor);
59         filename->setShadowColor(bgcolor);
60         filename->setBorderColor(bgcolor);
61         filename->setTextColor(getApp()->getForeColor());
62       } else {
63         label_text=_("&File Name:");
64         filename->setEditable(true);
65         filename->setBackColor(getApp()->getBackColor());
66         filename->setHiliteColor(getApp()->getHiliteColor());
67         filename->setShadowColor(getApp()->getShadowColor());
68         filename->setBorderColor(getApp()->getBorderColor());
69         filename->setTextColor(getApp()->getForeColor());
70       }
71       FXLabel*label=dynamic_cast<FXLabel*>(filename->getPrev());
72       if (label) { label->setText(label_text); }
73     }
74 };
75 
76 
77 
78 
79 FXDEFMAP(FileDlg) MyFileDlgMap[]={
80   FXMAPFUNC(SEL_COMMAND,FileDlg::ID_TOGGLE_MULTI,FileDlg::onToggleMulti),
81   FXMAPFUNC(SEL_KEYPRESS,0,FileDlg::onKeyPress),
82 };
83 
84 FXIMPLEMENT(FileDlg,FXFileDialog,MyFileDlgMap,ARRAYNUMBER(MyFileDlgMap));
85 
86 
87 
88 
FileDlg(FXWindow * win,const FXString & caption,bool optmulti)89 FileDlg::FileDlg(FXWindow*win, const FXString&caption, bool optmulti):FXFileDialog(win,caption)
90 {
91 #ifdef WIN32
92   filenames=NULL;
93 #endif
94   ((FileSel*)filebox)->icobtn()->hide();
95   multi_btn=new FXToggleButton( fsel()->nav(),_(" &M "),_(" &M "),NULL,NULL,this,
96                                   ID_TOGGLE_MULTI,TOGGLEBUTTON_TOOLBAR|FRAME_RAISED );
97   multi_btn->setTipText(_("Enable selection of multiple files"));
98   multi_btn->setAltTipText(_("Enable manual entry of filename"));
99   if (!optmulti) { multi_btn->hide(); }
100 }
101 
102 
103 
create()104 void FileDlg::create()
105 {
106   FXFileDialog::create();
107   switch (getSelectMode()) {
108     case SELECTFILE_ANY: {
109       fsel()->txtfld()->setFocus();
110       break;
111     }
112     case SELECTFILE_MULTIPLE: {
113       fsel()->fbox()->setFocus();
114       fsel()->fbox()->setCurrentItem(0);
115       break;
116     }
117   }
118 }
119 
120 
121 // Make the F4 key behave like it does with some other "toolkits"
122 // where it drops down the directory selection panel.
123 // Also makes tab-key navigation feel a little more intuitive (at least for me.)
onKeyPress(FXObject * o,FXSelector sel,void * p)124 long FileDlg::onKeyPress(FXObject*o,FXSelector sel,void*p)
125 {
126   FXEvent*ev=(FXEvent*)p;
127   switch (ev->code) {
128     case KEY_F4: {
129 #ifdef FOX_1_6
130       fsel()->dbox()->btn()->handle(fsel()->dbox()->btn(),FXSEL(SEL_COMMAND,ID_POST),NULL);
131 #else
132       fsel()->dbox()->btn()->showMenu(true);
133 #endif
134       fsel()->dbox()->setFocus();
135       fsel()->dbox()->pn()->getFirst()->setFocus();
136       break;
137     }
138     case KEY_Escape: {
139 #ifdef FOX_1_6
140       if (fsel()->dbox()->isPaneShown()) {
141         fsel()->dbox()->btn()->handle(fsel()->dbox()->btn(),FXSEL(SEL_COMMAND,ID_UNPOST),NULL);
142         return 1;
143       }
144 #else
145       if (fsel()->dbox()->btn()->isMenuShown()) {
146         fsel()->dbox()->btn()->showMenu(false);
147         return 1;
148       }
149 #endif
150       break;
151     }
152     case KEY_Return: {
153 #ifdef FOX_1_6
154       if (fsel()->dbox()->isPaneShown()||fsel()->dbox()->fld()->hasFocus()) {
155         fsel()->dbox()->btn()->handle(fsel()->dbox()->btn(),FXSEL(SEL_COMMAND,ID_UNPOST),NULL);
156         setDirectory(fsel()->dbox()->getDirectory());
157         return 1;
158       }
159 #else
160       if (fsel()->dbox()->btn()->isMenuShown()||fsel()->dbox()->fld()->hasFocus()) {
161         fsel()->dbox()->btn()->showMenu(false);
162         setDirectory(fsel()->dbox()->getDirectory());
163         return 1;
164       }
165 #endif
166       break;
167     }
168     case KEY_Tab: {
169       if (fsel()->dbox()->fld()->hasFocus()) {
170         if (ev->state & SHIFTMASK) {
171           fsel()->setFocus();
172           fsel()->txtfld()->setFocus();
173         } else {
174           fsel()->dbox()->getNext()->getNext()->setFocus();
175         }
176         return 1;
177       } else {
178         if (multi_btn->hasFocus()||(multi_btn->getPrev()->hasFocus()&&!multi_btn->shown())) {
179           fsel()->fbox()->setFocus();
180           return 1;
181         } else {
182           if (fsel()->fbox()->hasFocus()) {
183             fsel()->txtfld()->setFocus();
184             return 1;
185           } else {
186             if (fsel()->cncl()->hasFocus()) {
187               fsel()->dbox()->fld()->setFocus();
188               return 1;
189             }
190           }
191         }
192       }
193       break;
194     }
195     case KEY_ISO_Left_Tab: {
196       if (ev->state & SHIFTMASK) {
197         if (fsel()->dbox()->fld()->hasFocus()) {
198           fsel()->cncl()->setFocus();
199           return 1;
200         } else {
201           if (fsel()->fbox()->hasFocus()) {
202             if (multi_btn->shown()) {
203               multi_btn->setFocus();
204             } else {
205               multi_btn->getPrev()->setFocus();
206             }
207             return 1;
208           }
209         }
210       }
211       break;
212     }
213   }
214   return FXFileDialog::handle(o,sel,p);
215 }
216 
217 
218 
onToggleMulti(FXObject * o,FXSelector sel,void * p)219 long FileDlg::onToggleMulti(FXObject*o,FXSelector sel,void*p)
220 {
221   setSelectMode(p?SELECTFILE_MULTIPLE:SELECTFILE_EXISTING);
222   fsel()->txtfld()->setText(FXString::null);
223   fsel()->fbox()->killSelection(true);
224   if (p) {
225     fsel()->fbox()->setFocus();
226     fsel()->fbox()->setCurrentItem(0);
227   } else {
228     fsel()->txtfld()->setFocus();
229   }
230   return 1;
231 }
232 
233 
234 
setSelectMode(FXuint mode)235 void FileDlg::setSelectMode(FXuint mode) {
236   FXFileDialog::setSelectMode(mode);
237   if (multi_btn->shown()) {
238     FXString label_text;
239     if ( mode == SELECTFILE_MULTIPLE ) {
240       fsel()->ToggleMulti(true);
241       multi_btn->setState(true);
242       multi_btn->setBackColor(getApp()->getSelbackColor());
243       multi_btn->setTextColor(getApp()->getSelforeColor());
244     } else {
245       fsel()->ToggleMulti(false);
246       multi_btn->setState(false);
247       multi_btn->setBackColor(getApp()->getBackColor());
248       multi_btn->setTextColor(getApp()->getForeColor());
249     }
250   }
251 }
252 
253 
254 
fsel()255 FileSel*FileDlg::fsel()
256 {
257   return (FileSel*)filebox;
258 }
259 
260 
261 
262 #ifdef WIN32
263 
264 extern "C" {
265   int ReadShortcut(char **dst, const char *src);
266 }
267 
268 
269 // True if file extension is '*.lnk'
270 #define IsLinkExt(s) (FXPath::extension(s).lower()=="lnk")
271 
272 
273 /*
274   Fox doesn't automatically handle MS-Windows shortcut files, so...
275   Iterate through each filename in the file dialog's getFilenames() list,
276   if any of them are shortcut (*.lnk) files, dereference the link and
277   make the string point to the "real" disk file. If we have multiple files,
278   remove any links that point to a directory. But if we only have one string
279   in the list, and the string is a link pointing to a directory, we will
280   dereference it so the dialog can change into that folder.
281 */
FixupShortcuts(FXWindow * w,FXString * filenames)282 static void FixupShortcuts(FXWindow*w, FXString* filenames)
283 {
284   if (!filenames) return;
285   FXString* fn;
286   FXString* tail=filenames;
287   FXuint count=0;
288   for (fn=filenames; !fn->empty(); fn++) {
289     if (IsLinkExt(*fn)) {
290       char*tmp=NULL;
291       if (ReadShortcut(&tmp, fn->text())) {
292         *fn=tmp;
293       } else {
294         FXMessageBox::error(w,MBOX_OK,_("Error in shortcut"),"%s\n%s",fn->text(),tmp);
295       }
296       free(tmp);
297     }
298     tail=fn;
299     count++;
300   }
301   if (count>1) {
302     for (fn=filenames; !fn->empty(); fn++) {
303       if (FXStat::isDirectory(*fn)) {
304         *fn=tail->text();
305         *tail="";
306         tail--;
307       }
308     }
309   }
310 }
311 
312 /*
313   Read the Windows shortcut (*.lnk) file passed in as "filename".
314   If the filename does not end with the *.lnk extension, returns
315   true, the filename parameter is unchanged.
316   If the link cannot be read (e.g. corrupted file) it displays an
317   error dialog describing the reason for the failure and returns
318   false, the filename parameter is unchanged.
319   If reading of the link is successful, it returns true and the
320   "filename" parameter is modified and will contain the name of
321   the file that the shortcut points to.
322 */
ReadShortcut(FXWindow * w,FXString & filename)323 bool FileDlg::ReadShortcut(FXWindow*w, FXString &filename)
324 {
325   bool rv=true;
326   if (IsLinkExt(filename)) {
327     char*tmp=NULL;
328      if (::ReadShortcut(&tmp, filename.text())) {
329       filename=FXPath::simplify(FXPath::absolute(tmp));
330     } else {
331       FXMessageBox::error(w,MBOX_OK,_("Error in shortcut"),"%s\n%s",filename.text(),tmp);
332       rv=false;
333     }
334     free(tmp);
335   }
336   return rv;
337 }
338 
339 
340 
DeleteFilenames()341 void FileDlg::DeleteFilenames()
342 {
343   if (filenames&&own_filenames) {
344     delete[] filenames;
345     filenames=NULL;
346   }
347 }
348 
349 
350 
~FileDlg()351 FileDlg::~FileDlg()
352 {
353   DeleteFilenames();
354 }
355 
356 
357 
getFilenames()358 FXString*FileDlg::getFilenames()
359 {
360   own_filenames=false;
361   return filenames;
362 }
363 
364 
365 
getFilename()366 FXString FileDlg::getFilename() {
367   if (getSelectMode()==SELECTFILE_ANY) {
368     FXString fn=FXFileDialog::getFilename();
369     if (ReadShortcut(getShell(), fn)) {
370       FXFileDialog::setFilename(fn);
371     }
372     return FXFileDialog::getFilename();
373   } else {
374     return filenames ? (*filenames) : FXFileDialog::getFilename();
375   }
376 }
377 
378 
379 
execute(FXuint placement)380 FXuint FileDlg::execute(FXuint placement) {
381   DeleteFilenames();
382   FXuint rv=FXFileDialog::execute(placement);
383   if (rv) {
384     filenames = FXFileDialog::getFilenames();
385     own_filenames=true;
386     if (filenames) {
387       if (getSelectMode()!=SELECTFILE_MULTIPLE) {
388         filenames[0]=FXFileDialog::getFilename();
389       }
390       FixupShortcuts(getShell(), filenames);
391       if (FXStat::isDirectory(filenames->text())) {
392         setFilename("*");
393         filenames->append("\\.");
394         setDirectory(filenames->text());
395         DeleteFilenames();
396         return execute(placement);
397       }
398     }
399   }
400   return rv;
401 }
402 #endif
403 
404