Source code: org/gjt/sp/jedit/browser/VFSBrowser.java
1 /*
2 * VFSBrowser.java - VFS browser
3 * :tabSize=8:indentSize=8:noTabs=false:
4 * :folding=explicit:collapseFolds=1:
5 *
6 * Copyright (C) 2000, 2003 Slava Pestov
7 *
8 * This program is free software; you can redistribute it and/or
9 * modify it under the terms of the GNU General Public License
10 * as published by the Free Software Foundation; either version 2
11 * of the License, or any later version.
12 *
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, write to the Free Software
20 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
21 */
22
23 package org.gjt.sp.jedit.browser;
24
25 //{{{ Imports
26 import bsh.*;
27 import gnu.regexp.*;
28 import javax.swing.border.EmptyBorder;
29 import javax.swing.event.*;
30 import javax.swing.tree.DefaultMutableTreeNode;
31 import javax.swing.*;
32 import java.awt.event.*;
33 import java.awt.*;
34 import java.io.File;
35 import java.util.*;
36 import org.gjt.sp.jedit.io.*;
37 import org.gjt.sp.jedit.gui.*;
38 import org.gjt.sp.jedit.msg.*;
39 import org.gjt.sp.jedit.search.*;
40 import org.gjt.sp.jedit.*;
41 import org.gjt.sp.util.Log;
42 //}}}
43
44 /**
45 * The main class of the VFS browser.
46 * @author Slava Pestov
47 * @version $Id: VFSBrowser.java,v 1.101 2003/11/22 20:32:28 spestov Exp $
48 */
49 public class VFSBrowser extends JPanel implements EBComponent, DefaultFocusComponent
50 {
51 public static final String NAME = "vfs.browser";
52
53 //{{{ Browser types
54 /**
55 * Open file dialog mode. Equals JFileChooser.OPEN_DIALOG for
56 * backwards compatibility.
57 */
58 public static final int OPEN_DIALOG = 0;
59
60 /**
61 * Save file dialog mode. Equals JFileChooser.SAVE_DIALOG for
62 * backwards compatibility.
63 */
64 public static final int SAVE_DIALOG = 1;
65 /**
66 * Choose directory dialog mode.
67 */
68 public static final int BROWSER_DIALOG = 4;
69 /**
70 * Choose directory dialog mode.
71 */
72 public static final int CHOOSE_DIRECTORY_DIALOG = 3;
73
74 /**
75 * Stand-alone browser mode.
76 */
77 public static final int BROWSER = 2;
78 //}}}
79
80 //{{{ browseDirectoryInNewWindow() method
81 /**
82 * Opens the specified directory in a new, floating, file system browser.
83 * @param view The view
84 * @param path The directory's path
85 * @since jEdit 4.1pre2
86 */
87 public static void browseDirectoryInNewWindow(View view, String path)
88 {
89 DockableWindowManager wm = view.getDockableWindowManager();
90 if(path != null)
91 {
92 // this is such a bad way of doing it, but oh well...
93 jEdit.setTemporaryProperty("vfs.browser.path.tmp",path);
94 }
95 wm.floatDockableWindow("vfs.browser");
96 jEdit.unsetProperty("vfs.browser.path.tmp");
97 } //}}}
98
99 //{{{ browseDirectory() method
100 /**
101 * Opens the specified directory in a file system browser.
102 * @param view The view
103 * @param path The directory's path
104 * @since jEdit 4.0pre3
105 */
106 public static void browseDirectory(View view, String path)
107 {
108 DockableWindowManager wm = view.getDockableWindowManager();
109 VFSBrowser browser = (VFSBrowser)wm.getDockable(NAME);
110 if(browser != null)
111 {
112 wm.showDockableWindow(NAME);
113 browser.setDirectory(path);
114 }
115 else
116 {
117 if(path != null)
118 {
119 // this is such a bad way of doing it, but oh well...
120 jEdit.setTemporaryProperty("vfs.browser.path.tmp",path);
121 }
122 wm.addDockableWindow("vfs.browser");
123 jEdit.unsetProperty("vfs.browser.path.tmp");
124 }
125 } //}}}
126
127 //{{{ getActionContext() method
128 /**
129 * Returns the browser action context.
130 * @since jEdit 4.2pre1
131 */
132 public static ActionContext getActionContext()
133 {
134 return actionContext;
135 } //}}}
136
137 //{{{ VFSBrowser constructor
138 /**
139 * Creates a new VFS browser.
140 * @param view The view to open buffers in by default
141 */
142 public VFSBrowser(View view, String position)
143 {
144 this(view,null,BROWSER,true,position);
145 } //}}}
146
147 //{{{ VFSBrowser constructor
148 /**
149 * Creates a new VFS browser.
150 * @param view The view to open buffers in by default
151 * @param path The path to display
152 * @param mode The browser mode
153 * @param multipleSelection True if multiple selection should be allowed
154 * @param position Where the browser is located
155 * @since jEdit 4.2pre1
156 */
157 public VFSBrowser(View view, String path, int mode,
158 boolean multipleSelection, String position)
159 {
160 super(new BorderLayout());
161
162 listenerList = new EventListenerList();
163
164 this.mode = mode;
165 this.multipleSelection = multipleSelection;
166 this.floating = floating;
167 this.view = view;
168
169 currentEncoding = jEdit.getProperty("buffer.encoding",
170 System.getProperty("file.encoding"));
171 autoDetectEncoding = jEdit.getBooleanProperty(
172 "buffer.encodingAutodetect");
173
174 ActionHandler actionHandler = new ActionHandler();
175
176 Box topBox = new Box(BoxLayout.Y_AXIS);
177
178 horizontalLayout = (mode != BROWSER
179 || DockableWindowManager.TOP.equals(position)
180 || DockableWindowManager.BOTTOM.equals(position));
181
182 toolbarBox = new Box(horizontalLayout
183 ? BoxLayout.X_AXIS
184 : BoxLayout.Y_AXIS);
185
186 topBox.add(toolbarBox);
187
188 GridBagLayout layout = new GridBagLayout();
189 JPanel pathAndFilterPanel = new JPanel(layout);
190
191 GridBagConstraints cons = new GridBagConstraints();
192 cons.gridwidth = cons.gridheight = 1;
193 cons.gridx = cons.gridy = 0;
194 cons.fill = GridBagConstraints.BOTH;
195 cons.anchor = GridBagConstraints.EAST;
196 JLabel label = new JLabel(jEdit.getProperty("vfs.browser.path"),
197 SwingConstants.RIGHT);
198 label.setBorder(new EmptyBorder(0,0,0,12));
199 layout.setConstraints(label,cons);
200 pathAndFilterPanel.add(label);
201
202 pathField = new HistoryTextField("vfs.browser.path");
203 pathField.setInstantPopups(true);
204 pathField.setEnterAddsToHistory(false);
205 pathField.setSelectAllOnFocus(true);
206
207 if(floating)
208 {
209 label.setDisplayedMnemonic(jEdit.getProperty(
210 "vfs.browser.path.mnemonic").charAt(0));
211 label.setLabelFor(pathField);
212 }
213
214 // because its preferred size can be quite wide, we
215 // don't want it to make the browser way too big,
216 // so set the preferred width to 0.
217 Dimension prefSize = pathField.getPreferredSize();
218 prefSize.width = 0;
219 pathField.setPreferredSize(prefSize);
220 pathField.addActionListener(actionHandler);
221 cons.gridx = 1;
222 cons.weightx = 1.0f;
223
224 layout.setConstraints(pathField,cons);
225 pathAndFilterPanel.add(pathField);
226
227 filterCheckbox = new JCheckBox(jEdit.getProperty("vfs.browser.filter"));
228 filterCheckbox.setMargin(new Insets(0,0,0,0));
229 filterCheckbox.setRequestFocusEnabled(false);
230 filterCheckbox.setBorder(new EmptyBorder(0,0,0,12));
231 filterCheckbox.setSelected(jEdit.getBooleanProperty(
232 "vfs.browser.filter-enabled"));
233
234 filterCheckbox.addActionListener(actionHandler);
235
236 if(mode != CHOOSE_DIRECTORY_DIALOG)
237 {
238 cons.gridx = 0;
239 cons.weightx = 0.0f;
240 cons.gridy = 1;
241 layout.setConstraints(filterCheckbox,cons);
242 pathAndFilterPanel.add(filterCheckbox);
243 }
244
245 filterField = new HistoryTextField("vfs.browser.filter");
246 filterField.setInstantPopups(true);
247 filterField.setSelectAllOnFocus(true);
248 filterField.addActionListener(actionHandler);
249
250 if(mode != CHOOSE_DIRECTORY_DIALOG)
251 {
252 cons.gridx = 1;
253 cons.weightx = 1.0f;
254 layout.setConstraints(filterField,cons);
255 pathAndFilterPanel.add(filterField);
256 }
257
258 topBox.add(pathAndFilterPanel);
259 add(BorderLayout.NORTH,topBox);
260
261 add(BorderLayout.CENTER,browserView = new BrowserView(this));
262
263 propertiesChanged();
264
265 String filter;
266 if(mode == BROWSER || !jEdit.getBooleanProperty(
267 "vfs.browser.currentBufferFilter"))
268 {
269 filter = jEdit.getProperty("vfs.browser.last-filter");
270 if(filter == null)
271 filter = jEdit.getProperty("vfs.browser.default-filter");
272 }
273 else
274 {
275 String ext = MiscUtilities.getFileExtension(
276 view.getBuffer().getName());
277 if(ext.length() == 0)
278 filter = jEdit.getProperty("vfs.browser.default-filter");
279 else
280 filter = "*" + ext;
281 }
282
283 filterField.setText(filter);
284 filterField.addCurrentToHistory();
285
286 updateFilterEnabled();
287
288 // see VFSBrowser.browseDirectory()
289 if(path == null)
290 path = jEdit.getProperty("vfs.browser.path.tmp");
291
292 if(path == null || path.length() == 0)
293 {
294 String userHome = System.getProperty("user.home");
295 String defaultPath = jEdit.getProperty("vfs.browser.defaultPath");
296 if(defaultPath.equals("home"))
297 path = userHome;
298 else if(defaultPath.equals("working"))
299 path = System.getProperty("user.dir");
300 else if(defaultPath.equals("buffer"))
301 {
302 if(view != null)
303 {
304 Buffer buffer = view.getBuffer();
305 path = buffer.getDirectory();
306 }
307 else
308 path = userHome;
309 }
310 else if(defaultPath.equals("last"))
311 {
312 HistoryModel pathModel = HistoryModel.getModel("vfs.browser.path");
313 if(pathModel.getSize() == 0)
314 path = "~";
315 else
316 path = pathModel.getItem(0);
317 }
318 else if(defaultPath.equals("favorites"))
319 path = "favorites:";
320 else
321 {
322 // unknown value??!!!
323 path = userHome;
324 }
325 }
326
327 final String _path = path;
328
329 SwingUtilities.invokeLater(new Runnable()
330 {
331 public void run()
332 {
333 setDirectory(_path);
334 }
335 });
336 } //}}}
337
338 //{{{ focusOnDefaultComponent() method
339 public void focusOnDefaultComponent()
340 {
341 browserView.focusOnFileView();
342 } //}}}
343
344 //{{{ addNotify() method
345 public void addNotify()
346 {
347 super.addNotify();
348 EditBus.addToBus(this);
349 } //}}}
350
351 //{{{ removeNotify() method
352 public void removeNotify()
353 {
354 super.removeNotify();
355 jEdit.setBooleanProperty("vfs.browser.filter-enabled",
356 filterCheckbox.isSelected());
357 if(mode == BROWSER || !jEdit.getBooleanProperty(
358 "vfs.browser.currentBufferFilter"))
359 {
360 jEdit.setProperty("vfs.browser.last-filter",
361 filterField.getText());
362 }
363 EditBus.removeFromBus(this);
364 } //}}}
365
366 //{{{ handleMessage() method
367 public void handleMessage(EBMessage msg)
368 {
369 if(msg instanceof PropertiesChanged)
370 propertiesChanged();
371 else if(msg instanceof BufferUpdate)
372 {
373 BufferUpdate bmsg = (BufferUpdate)msg;
374 if(bmsg.getWhat() == BufferUpdate.CREATED
375 || bmsg.getWhat() == BufferUpdate.CLOSED)
376 browserView.updateFileView();
377 // hacked BufferIORequest to send VFSUpdates in case
378 // two stage save is off now...
379 /* else if(bmsg.getWhat() == BufferUpdate.SAVED)
380 {
381 maybeReloadDirectory(MiscUtilities.getParentOfPath(
382 bmsg.getBuffer().getPath()));
383 } */
384 }
385 else if(msg instanceof PluginUpdate)
386 {
387 PluginUpdate pmsg = (PluginUpdate)msg;
388 if(pmsg.getWhat() == PluginUpdate.LOADED
389 || pmsg.getWhat() == PluginUpdate.UNLOADED)
390 {
391 plugins.updatePopupMenu();
392 }
393 }
394 else if(msg instanceof VFSUpdate)
395 {
396 maybeReloadDirectory(((VFSUpdate)msg).getPath());
397 }
398 } //}}}
399
400 //{{{ getView() method
401 public View getView()
402 {
403 return view;
404 } //}}}
405
406 //{{{ getMode() method
407 public int getMode()
408 {
409 return mode;
410 } //}}}
411
412 //{{{ isMultipleSelectionEnabled() method
413 public boolean isMultipleSelectionEnabled()
414 {
415 return multipleSelection;
416 } //}}}
417
418 //{{{ isHorizontalLayout() method
419 public boolean isHorizontalLayout()
420 {
421 return horizontalLayout;
422 } //}}}
423
424 //{{{ getShowHiddenFiles() method
425 public boolean getShowHiddenFiles()
426 {
427 return showHiddenFiles;
428 } //}}}
429
430 //{{{ setShowHiddenFiles() method
431 public void setShowHiddenFiles(boolean showHiddenFiles)
432 {
433 this.showHiddenFiles = showHiddenFiles;
434 } //}}}
435
436 //{{{ getFilenameFilter() method
437 /**
438 * Returns the file name filter glob.
439 * @since jEdit 3.2pre2
440 */
441 public String getFilenameFilter()
442 {
443 if(filterCheckbox.isSelected())
444 {
445 String filter = filterField.getText();
446 if(filter.length() == 0)
447 return "*";
448 else
449 return filter;
450 }
451 else
452 return "*";
453 } //}}}
454
455 //{{{ setFilenameFilter() method
456 public void setFilenameFilter(String filter)
457 {
458 if(filter == null || filter.length() == 0 || filter.equals("*"))
459 filterCheckbox.setSelected(false);
460 else
461 {
462 filterCheckbox.setSelected(true);
463 filterField.setText(filter);
464 }
465 } //}}}
466
467 //{{{ getDirectoryField() method
468 public HistoryTextField getDirectoryField()
469 {
470 return pathField;
471 } //}}}
472
473 //{{{ getDirectory() method
474 public String getDirectory()
475 {
476 return path;
477 } //}}}
478
479 //{{{ setDirectory() method
480 public void setDirectory(String path)
481 {
482 if(path.startsWith("file:"))
483 path = path.substring(5);
484
485 pathField.setText(path);
486
487 if(!startRequest())
488 return;
489
490 updateFilenameFilter();
491 browserView.saveExpansionState();
492 browserView.loadDirectory(null,path);
493 this.path = path;
494
495 VFSManager.runInAWTThread(new Runnable()
496 {
497 public void run()
498 {
499 endRequest();
500 }
501 });
502 } //}}}
503
504 //{{{ rootDirectory() method
505 /**
506 * Goes to the local drives directory.
507 * @since jEdit 4.0pre4
508 */
509 public void rootDirectory()
510 {
511 if(OperatingSystem.isMacOS() || OperatingSystem.isDOSDerived())
512 setDirectory(FileRootsVFS.PROTOCOL + ":");
513 else
514 setDirectory("/");
515 } //}}}
516
517 //{{{ reloadDirectory() method
518 public void reloadDirectory()
519 {
520 // used by FTP plugin to clear directory cache
521 VFSManager.getVFSForPath(path).reloadDirectory(path);
522
523 updateFilenameFilter();
524 browserView.saveExpansionState();
525 browserView.loadDirectory(null,path);
526 } //}}}
527
528 //{{{ delete() method
529 /**
530 * Note that all files must be on the same VFS.
531 */
532 public void delete(VFS.DirectoryEntry[] files)
533 {
534 String dialogType;
535
536 if(MiscUtilities.isURL(files[0].deletePath)
537 && FavoritesVFS.PROTOCOL.equals(
538 MiscUtilities.getProtocolOfURL(files[0].deletePath)))
539 {
540 dialogType = "vfs.browser.delete-favorites";
541 }
542 else
543 {
544 dialogType = "vfs.browser.delete-confirm";
545 }
546
547 StringBuffer buf = new StringBuffer();
548 for(int i = 0; i < files.length; i++)
549 {
550 buf.append(files[i].path);
551 buf.append('\n');
552 }
553
554 Object[] args = { buf.toString() };
555 int result = GUIUtilities.confirm(this,dialogType,args,
556 JOptionPane.YES_NO_OPTION,
557 JOptionPane.WARNING_MESSAGE);
558 if(result != JOptionPane.YES_OPTION)
559 return;
560
561 VFS vfs = VFSManager.getVFSForPath(files[0].deletePath);
562
563 if(!startRequest())
564 return;
565
566 for(int i = 0; i < files.length; i++)
567 {
568 Object session = vfs.createVFSSession(files[i].deletePath,this);
569 if(session == null)
570 continue;
571
572 VFSManager.runInWorkThread(new BrowserIORequest(
573 BrowserIORequest.DELETE,this,
574 session,vfs,files[i].deletePath,
575 null,null,null));
576 }
577
578 VFSManager.runInAWTThread(new Runnable()
579 {
580 public void run()
581 {
582 endRequest();
583 }
584 });
585 } //}}}
586
587 //{{{ rename() method
588 public void rename(String from)
589 {
590 VFS vfs = VFSManager.getVFSForPath(from);
591
592 String filename = vfs.getFileName(from);
593 String[] args = { filename };
594 String to = GUIUtilities.input(this,"vfs.browser.rename",
595 args,filename);
596 if(to == null)
597 return;
598
599 to = MiscUtilities.constructPath(vfs.getParentOfPath(from),to);
600
601 Object session = vfs.createVFSSession(from,this);
602 if(session == null)
603 return;
604
605 if(!startRequest())
606 return;
607
608 VFSManager.runInWorkThread(new BrowserIORequest(
609 BrowserIORequest.RENAME,this,
610 session,vfs,from,to,null,null));
611
612 VFSManager.runInAWTThread(new Runnable()
613 {
614 public void run()
615 {
616 endRequest();
617 }
618 });
619 } //}}}
620
621 //{{{ mkdir() method
622 public void mkdir()
623 {
624 String newDirectory = GUIUtilities.input(this,"vfs.browser.mkdir",null);
625 if(newDirectory == null)
626 return;
627
628 // if a directory is selected, create new dir in there.
629 // if a file is selected, create new dir inside its parent.
630 VFS.DirectoryEntry[] selected = getSelectedFiles();
631 String parent;
632 if(selected.length == 0)
633 parent = path;
634 else if(selected[0].type == VFS.DirectoryEntry.FILE)
635 {
636 parent = selected[0].path;
637 parent = VFSManager.getVFSForPath(parent)
638 .getParentOfPath(parent);
639 }
640 else
641 parent = selected[0].path;
642
643 VFS vfs = VFSManager.getVFSForPath(parent);
644
645 // path is the currently viewed directory in the browser
646 newDirectory = MiscUtilities.constructPath(parent,newDirectory);
647
648 Object session = vfs.createVFSSession(newDirectory,this);
649 if(session == null)
650 return;
651
652 if(!startRequest())
653 return;
654
655 VFSManager.runInWorkThread(new BrowserIORequest(
656 BrowserIORequest.MKDIR,this,
657 session,vfs,newDirectory,null,null,null));
658
659 VFSManager.runInAWTThread(new Runnable()
660 {
661 public void run()
662 {
663 endRequest();
664 }
665 });
666 } //}}}
667
668 //{{{ newFile() method
669 /**
670 * Creates a new file in the current directory.
671 * @since jEdit 4.0pre2
672 */
673 public void newFile()
674 {
675 VFS.DirectoryEntry[] selected = getSelectedFiles();
676 if(selected.length >= 1)
677 {
678 VFS.DirectoryEntry file = selected[0];
679 if(file.type == VFS.DirectoryEntry.DIRECTORY)
680 jEdit.newFile(view,file.path);
681 else
682 {
683 VFS vfs = VFSManager.getVFSForPath(file.path);
684 jEdit.newFile(view,vfs.getParentOfPath(file.path));
685 }
686 }
687 else
688 jEdit.newFile(view,path);
689 } //}}}
690
691 //{{{ searchInDirectory() method
692 /**
693 * Opens a directory search in the current directory.
694 * @since jEdit 4.0pre2
695 */
696 public void searchInDirectory()
697 {
698 VFS.DirectoryEntry[] selected = getSelectedFiles();
699 if(selected.length >= 1)
700 {
701 VFS.DirectoryEntry file = selected[0];
702 searchInDirectory(file.path,file.type != VFS.DirectoryEntry.FILE);
703 }
704 else
705 {
706 searchInDirectory(this.path,true);
707 }
708 } //}}}
709
710 //{{{ searchInDirectory() method
711 /**
712 * Opens a directory search in the specified directory.
713 * @param path The path name
714 * @param directory True if the path is a directory, false if it is a file
715 * @since jEdit 4.2pre1
716 */
717 public void searchInDirectory(String path, boolean directory)
718 {
719 String filter;
720
721 if(directory)
722 {
723 filter = getFilenameFilter();
724 }
725 else
726 {
727 String name = MiscUtilities.getFileName(path);
728 String ext = MiscUtilities.getFileExtension(name);
729 filter = (ext == null || ext.length() == 0
730 ? getFilenameFilter()
731 : "*" + ext);
732 path = MiscUtilities.getParentOfPath(path);
733 }
734
735 SearchAndReplace.setSearchFileSet(new DirectoryListSet(
736 path,filter,true));
737 SearchDialog.showSearchDialog(view,null,SearchDialog.DIRECTORY);
738 } //}}}
739
740 //{{{ getBrowserView() method
741 public BrowserView getBrowserView()
742 {
743 return browserView;
744 } //}}}
745
746 //{{{ getSelectedFiles() method
747 public VFS.DirectoryEntry[] getSelectedFiles()
748 {
749 return browserView.getSelectedFiles();
750 } //}}}
751
752 //{{{ locateFile() method
753 /**
754 * Goes to the given file's directory and selects the file in the list.
755 * @param path The file
756 * @since jEdit 4.2pre2
757 */
758 public void locateFile(final String path)
759 {
760 if(!filenameFilter.isMatch(MiscUtilities.getFileName(path)))
761 setFilenameFilter(null);
762
763 setDirectory(MiscUtilities.getParentOfPath(path));
764 VFSManager.runInAWTThread(new Runnable()
765 {
766 public void run()
767 {
768 browserView.getTable().selectFile(path);
769 }
770 });
771 } //}}}
772
773 //{{{ addBrowserListener() method
774 public void addBrowserListener(BrowserListener l)
775 {
776 listenerList.add(BrowserListener.class,l);
777 } //}}}
778
779 //{{{ removeBrowserListener() method
780 public void removeBrowserListener(BrowserListener l)
781 {
782 listenerList.remove(BrowserListener.class,l);
783 } //}}}
784
785 //{{{ filesActivated() method
786 // canDoubleClickClose set to false when ENTER pressed
787 public static final int M_OPEN = 0;
788 public static final int M_OPEN_NEW_VIEW = 1;
789 public static final int M_OPEN_NEW_PLAIN_VIEW = 2;
790 public static final int M_OPEN_NEW_SPLIT = 3;
791 public static final int M_INSERT = 4;
792
793 /**
794 * This method does the "double-click" handling. It is public so that
795 * <code>browser.actions.xml</code> can bind to it.
796 * @since jEdit 4.2pre2
797 */
798 public void filesActivated(int mode, boolean canDoubleClickClose)
799 {
800 VFS.DirectoryEntry[] selectedFiles = browserView.getSelectedFiles();
801
802 Buffer buffer = null;
803
804 check_selected: for(int i = 0; i < selectedFiles.length; i++)
805 {
806 VFS.DirectoryEntry file = selectedFiles[i];
807
808 if(file.type == VFS.DirectoryEntry.DIRECTORY
809 || file.type == VFS.DirectoryEntry.FILESYSTEM)
810 {
811 if(mode == M_OPEN_NEW_VIEW && this.mode == BROWSER)
812 browseDirectoryInNewWindow(view,file.path);
813 else
814 setDirectory(file.path);
815 }
816 else if(this.mode == BROWSER || this.mode == BROWSER_DIALOG)
817 {
818 if(mode == M_INSERT)
819 {
820 view.getBuffer().insertFile(view,
821 file.path);
822 continue check_selected;
823 }
824
825 Buffer _buffer = jEdit.getBuffer(file.path);
826 if(_buffer == null)
827 {
828 Hashtable props = new Hashtable();
829 props.put(Buffer.ENCODING,currentEncoding);
830 props.put(Buffer.ENCODING_AUTODETECT,
831 Boolean.valueOf(autoDetectEncoding));
832 _buffer = jEdit.openFile(null,null,
833 file.path,false,props);
834 }
835 else if(doubleClickClose && canDoubleClickClose
836 && this.mode != BROWSER_DIALOG
837 && selectedFiles.length == 1)
838 {
839 // close if this buffer is currently
840 // visible in the view.
841 EditPane[] editPanes = view.getEditPanes();
842 for(int j = 0; j < editPanes.length; j++)
843 {
844 if(editPanes[j].getBuffer() == _buffer)
845 {
846 jEdit.closeBuffer(view,_buffer);
847 return;
848 }
849 }
850 }
851
852 if(_buffer != null)
853 buffer = _buffer;
854 }
855 else
856 {
857 // if a file is selected in OPEN_DIALOG or
858 // SAVE_DIALOG mode, just let the listener(s)
859 // handle it
860 }
861 }
862
863 if(buffer != null)
864 {
865 switch(mode)
866 {
867 case M_OPEN:
868 view.setBuffer(buffer);
869 break;
870 case M_OPEN_NEW_VIEW:
871 jEdit.newView(view,buffer,false);
872 break;
873 case M_OPEN_NEW_PLAIN_VIEW:
874 jEdit.newView(view,buffer,true);
875 break;
876 case M_OPEN_NEW_SPLIT:
877 view.splitHorizontally().setBuffer(buffer);
878 break;
879 }
880 }
881
882 Object[] listeners = listenerList.getListenerList();
883 for(int i = 0; i < listeners.length; i++)
884 {
885 if(listeners[i] == BrowserListener.class)
886 {
887 BrowserListener l = (BrowserListener)listeners[i+1];
888 l.filesActivated(this,selectedFiles);
889 }
890 }
891 } //}}}
892
893 //{{{ Package-private members
894 String currentEncoding;
895 boolean autoDetectEncoding;
896
897 //{{{ pathsEqual() method
898 /**
899 * This will be made public at some stage, in the io package, but not
900 * yet.
901 */
902 static boolean pathsEqual(String p1, String p2)
903 {
904 if(p1.endsWith("/") || p1.endsWith(File.separator))
905 p1 = p1.substring(0,p1.length() - 1);
906 if(p2.endsWith("/") || p2.endsWith(File.separator))
907 p2 = p2.substring(0,p2.length() - 1);
908 return p1.equals(p2);
909 } //}}}
910
911 //{{{ updateFilenameFilter() method
912 void updateFilenameFilter()
913 {
914 try
915 {
916 String filter = filterField.getText();
917 if(filter.length() == 0)
918 filter = "*";
919 filenameFilter = new RE(MiscUtilities.globToRE(filter),RE.REG_ICASE);
920 }
921 catch(Exception e)
922 {
923 Log.log(Log.ERROR,VFSBrowser.this,e);
924 String[] args = { filterField.getText(),
925 e.getMessage() };
926 GUIUtilities.error(this,"vfs.browser.bad-filter",args);
927 }
928 } //}}}
929
930 //{{{ directoryLoaded() method
931 void directoryLoaded(Object node, Object[] loadInfo)
932 {
933 VFSManager.runInAWTThread(new DirectoryLoadedAWTRequest(
934 node,loadInfo));
935 } //}}}
936
937 //{{{ filesSelected() method
938 void filesSelected()
939 {
940 VFS.DirectoryEntry[] selectedFiles = browserView.getSelectedFiles();
941
942 if(mode == BROWSER)
943 {
944 for(int i = 0; i < selectedFiles.length; i++)
945 {
946 VFS.DirectoryEntry file = selectedFiles[i];
947 Buffer buffer = jEdit.getBuffer(file.path);
948 if(buffer != null && view != null)
949 view.setBuffer(buffer);
950 }
951 }
952
953 Object[] listeners = listenerList.getListenerList();
954 for(int i = 0; i < listeners.length; i++)
955 {
956 if(listeners[i] == BrowserListener.class)
957 {
958 BrowserListener l = (BrowserListener)listeners[i+1];
959 l.filesSelected(this,selectedFiles);
960 }
961 }
962 } //}}}
963
964 //{{{ endRequest() method
965 void endRequest()
966 {
967 requestRunning = false;
968 } //}}}
969
970 //}}}
971
972 //{{{ Private members
973
974 private static ActionContext actionContext;
975
976 static
977 {
978 actionContext = new ActionContext()
979 {
980 public void invokeAction(EventObject evt,
981 EditAction action)
982 {
983 VFSBrowser browser = (VFSBrowser)
984 GUIUtilities.getComponentParent(
985 (Component)evt.getSource(),
986 VFSBrowser.class);
987
988 // in the future we will want something better,
989 // eg. having an 'evt' object passed to
990 // EditAction.invoke().
991
992 // for now, since all browser actions are
993 // written in beanshell we set the 'browser'
994 // variable directly.
995 NameSpace global = BeanShell.getNameSpace();
996 try
997 {
998 global.setVariable("browser",
999 browser);
1000 global.setVariable("files",
1001 browser.getSelectedFiles());
1002
1003 View view = browser.getView();
1004 // I guess ideally all browsers
1005 // should have views, but since they
1006 // don't, we just use the active view
1007 // in that case, since some actions
1008 // depend on a view being there and
1009 // I don't want to add checks to
1010 // them all
1011 if(view == null)
1012 view = jEdit.getActiveView();
1013 action.invoke(view);
1014 }
1015 catch(UtilEvalError err)
1016 {
1017 Log.log(Log.ERROR,this,err);
1018 }
1019 finally
1020 {
1021 try
1022 {
1023 global.setVariable("browser",null);
1024 global.setVariable("files",null);
1025 }
1026 catch(UtilEvalError err)
1027 {
1028 Log.log(Log.ERROR,this,err);
1029 }
1030 }
1031 }
1032 };
1033
1034 ActionSet builtInActionSet = new ActionSet(null,null,null,
1035 jEdit.class.getResource("browser.actions.xml"));
1036 builtInActionSet.load();
1037 actionContext.addActionSet(builtInActionSet);
1038 }
1039
1040 //{{{ Instance variables
1041 private EventListenerList listenerList;
1042 private View view;
1043 private boolean floating;
1044 private boolean horizontalLayout;
1045 private String path;
1046 private HistoryTextField pathField;
1047 private JCheckBox filterCheckbox;
1048 private HistoryTextField filterField;
1049 private Box toolbarBox;
1050 private FavoritesMenuButton favorites;
1051 private PluginsMenuButton plugins;
1052 private BrowserView browserView;
1053 private RE filenameFilter;
1054 private int mode;
1055 private boolean multipleSelection;
1056
1057 private boolean showHiddenFiles;
1058 private boolean sortMixFilesAndDirs;
1059 private boolean sortIgnoreCase;
1060 private boolean doubleClickClose;
1061
1062 private boolean requestRunning;
1063 private boolean maybeReloadRequestRunning;
1064 //}}}
1065
1066 //{{{ createMenuBar() method
1067 private JPanel createMenuBar()
1068 {
1069 JPanel menuBar = new JPanel();
1070 menuBar.setLayout(new BoxLayout(menuBar,BoxLayout.X_AXIS));
1071 menuBar.setBorder(new EmptyBorder(0,3,1,0));
1072
1073 menuBar.add(new CommandsMenuButton());
1074 menuBar.add(Box.createHorizontalStrut(3));
1075 menuBar.add(plugins = new PluginsMenuButton());
1076 menuBar.add(Box.createHorizontalStrut(3));
1077 menuBar.add(favorites = new FavoritesMenuButton());
1078
1079 return menuBar;
1080 } //}}}
1081
1082 //{{{ createToolBar() method
1083 private Box createToolBar()
1084 {
1085 if(mode == BROWSER)
1086 return GUIUtilities.loadToolBar(actionContext,
1087 "vfs.browser.toolbar-browser");
1088 else
1089 return GUIUtilities.loadToolBar(actionContext,
1090 "vfs.browser.toolbar-dialog");
1091 } //}}}
1092
1093 //{{{ propertiesChanged() method
1094 private void propertiesChanged()
1095 {
1096 showHiddenFiles = jEdit.getBooleanProperty("vfs.browser.showHiddenFiles");
1097 sortMixFilesAndDirs = jEdit.getBooleanProperty("vfs.browser.sortMixFilesAndDirs");
1098 sortIgnoreCase = jEdit.getBooleanProperty("vfs.browser.sortIgnoreCase");
1099 doubleClickClose = jEdit.getBooleanProperty("vfs.browser.doubleClickClose");
1100
1101 browserView.propertiesChanged();
1102
1103 toolbarBox.removeAll();
1104
1105 if(jEdit.getBooleanProperty("vfs.browser.showToolbar"))
1106 {
1107 Box toolbar = createToolBar();
1108 if(horizontalLayout)
1109 toolbarBox.add(toolbar);
1110 else
1111 {
1112 toolbar.add(Box.createGlue());
1113 toolbarBox.add(toolbar);
1114 }
1115 }
1116
1117 if(jEdit.getBooleanProperty("vfs.browser.showMenubar"))
1118 {
1119 JPanel menubar = createMenuBar();
1120 if(horizontalLayout)
1121 {
1122 toolbarBox.add(Box.createHorizontalStrut(6));
1123 toolbarBox.add(menubar,0);
1124 }
1125 else
1126 {
1127 menubar.add(Box.createGlue());
1128 toolbarBox.add(menubar);
1129 }
1130 }
1131 else
1132 favorites = null;
1133
1134 toolbarBox.add(Box.createGlue());
1135
1136 revalidate();
1137
1138 if(path != null)
1139 reloadDirectory();
1140 } //}}}
1141
1142 /* We do this stuff because the browser is not able to handle
1143 * more than one request yet */
1144
1145 //{{{ startRequest() method
1146 private boolean startRequest()
1147 {
1148 if(requestRunning)
1149 {
1150 // dump stack trace for debugging purposes
1151 Log.log(Log.DEBUG,this,new Throwable("For debugging purposes"));
1152
1153 GUIUtilities.error(this,"browser-multiple-io",null);
1154 return false;
1155 }
1156 else
1157 {
1158 requestRunning = true;
1159 return true;
1160 }
1161 } //}}}
1162
1163 //{{{ updateFilterEnabled() method
1164 private void updateFilterEnabled()
1165 {
1166 filterField.setEnabled(filterCheckbox.isSelected());
1167 } //}}}
1168
1169 //{{{ maybeReloadDirectory() method
1170 private void maybeReloadDirectory(String dir)
1171 {
1172 if(MiscUtilities.isURL(dir)
1173 && MiscUtilities.getProtocolOfURL(dir).equals(
1174 FavoritesVFS.PROTOCOL))
1175 {
1176 if(favorites != null)
1177 favorites.popup = null;
1178 }
1179
1180 // this is a dirty hack and it relies on the fact
1181 // that updates for parents are sent before updates
1182 // for the changed nodes themselves (if this was not
1183 // the case, the browser wouldn't be updated properly
1184 // on delete, etc).
1185 //
1186 // to avoid causing '> 1 request' errors, don't reload
1187 // directory if request already active
1188 if(maybeReloadRequestRunning)
1189 {
1190 Log.log(Log.WARNING,this,"VFS update: request already in progress");
1191 return;
1192 }
1193
1194 // save a file -> sends vfs update. if a VFS file dialog box
1195 // is shown from the same event frame as the save, the
1196 // VFSUpdate will be delivered before the directory is loaded,
1197 // and before the path is set.
1198 if(path != null)
1199 {
1200 try
1201 {
1202 maybeReloadRequestRunning = true;
1203
1204 browserView.maybeReloadDirectory(dir);
1205 }
1206 finally
1207 {
1208 VFSManager.runInAWTThread(new Runnable()
1209 {
1210 public void run()
1211 {
1212 maybeReloadRequestRunning = false;
1213 }
1214 });
1215 }
1216 }
1217 } //}}}
1218
1219 //}}}
1220
1221 //{{{ Inner classes
1222
1223 //{{{ ActionHandler class
1224 class ActionHandler implements ActionListener
1225 {
1226 public void actionPerformed(ActionEvent evt)
1227 {
1228 Object source = evt.getSource();
1229 if(source == pathField || source == filterField
1230 || source == filterCheckbox)
1231 {
1232 updateFilterEnabled();
1233
1234 String path = pathField.getText();
1235 if(path != null)
1236 setDirectory(path);
1237
1238 browserView.focusOnFileView();
1239 }
1240 }
1241 } //}}}
1242
1243 //{{{ CommandsMenuButton class
1244 class CommandsMenuButton extends JButton
1245 {
1246 //{{{ CommandsMenuButton constructor
1247 CommandsMenuButton()
1248 {
1249 setText(jEdit.getProperty("vfs.browser.commands.label"));
1250 setIcon(GUIUtilities.loadIcon("ToolbarMenu.gif"));
1251 setHorizontalTextPosition(SwingConstants.LEADING);
1252
1253 popup = new BrowserCommandsMenu(VFSBrowser.this,null);
1254
1255 CommandsMenuButton.this.setRequestFocusEnabled(false);
1256 setMargin(new Insets(1,1,1,1));
1257 CommandsMenuButton.this.addMouseListener(new MouseHandler());
1258
1259 if(OperatingSystem.isMacOSLF())
1260 CommandsMenuButton.this.putClientProperty("JButton.buttonType","toolbar");
1261 } //}}}
1262
1263 BrowserCommandsMenu popup;
1264
1265 //{{{ MouseHandler class
1266 class MouseHandler extends MouseAdapter
1267 {
1268 public void mousePressed(MouseEvent evt)
1269 {
1270 if(!popup.isVisible())
1271 {
1272 popup.update();
1273
1274 GUIUtilities.showPopupMenu(
1275 popup,CommandsMenuButton.this,0,
1276 CommandsMenuButton.this.getHeight(),
1277 false);
1278 }
1279 else
1280 {
1281 popup.setVisible(false);
1282 }
1283 }
1284 } //}}}
1285 } //}}}
1286
1287 //{{{ PluginsMenuButton class
1288 class PluginsMenuButton extends JButton
1289 {
1290 //{{{ PluginsMenuButton constructor
1291 PluginsMenuButton()
1292 {
1293 setText(jEdit.getProperty("vfs.browser.plugins.label"));
1294 setIcon(GUIUtilities.loadIcon("ToolbarMenu.gif"));
1295 setHorizontalTextPosition(SwingConstants.LEADING);
1296
1297 PluginsMenuButton.this.setRequestFocusEnabled(false);
1298 setMargin(new Insets(1,1,1,1));
1299 PluginsMenuButton.this.addMouseListener(new MouseHandler());
1300
1301 if(OperatingSystem.isMacOSLF())
1302 PluginsMenuButton.this.putClientProperty("JButton.buttonType","toolbar");
1303 } //}}}
1304
1305 JPopupMenu popup;
1306
1307 //{{{ updatePopupMenu() method
1308 void updatePopupMenu()
1309 {
1310 popup = null;
1311 } //}}}
1312
1313 //{{{ createPopupMenu() method
1314 private void createPopupMenu()
1315 {
1316 if(popup != null)
1317 return;
1318
1319 popup = new JPopupMenu();
1320 ActionHandler actionHandler = new ActionHandler();
1321
1322 if(getMode() == BROWSER)
1323 {
1324 popup.add(GUIUtilities.loadMenuItem("plugin-manager",false));
1325 popup.add(GUIUtilities.loadMenuItem("plugin-options",false));
1326 popup.addSeparator();
1327 }
1328 else
1329 /* we're in a modal dialog */;
1330
1331 ArrayList vec = new ArrayList();
1332
1333 //{{{ old API
1334 Enumeration enum = VFSManager.getFilesystems();
1335
1336 while(enum.hasMoreElements())
1337 {
1338 VFS vfs = (VFS)enum.nextElement();
1339 if((vfs.getCapabilities() & VFS.BROWSE_CAP) == 0)
1340 continue;
1341
1342 JMenuItem menuItem = new JMenuItem(jEdit.getProperty(
1343 "vfs." + vfs.getName() + ".label"));
1344 menuItem.setActionCommand(vfs.getName());
1345 menuItem.addActionListener(actionHandler);
1346 vec.add(menuItem);
1347 } //}}}
1348
1349 //{{{ new API
1350 EditPlugin[] plugins = jEdit.getPlugins();
1351 for(int i = 0; i < plugins.length; i++)
1352 {
1353 JMenuItem menuItem = plugins[i].createBrowserMenuItems();
1354 if(menuItem != null)
1355 vec.add(menuItem);
1356 } //}}}
1357
1358 if(vec.size() != 0)
1359 {
1360 MiscUtilities.quicksort(vec,new MiscUtilities.MenuItemCompare());
1361 for(int i = 0; i < vec.size(); i++)
1362 popup.add((JMenuItem)vec.get(i));
1363 }
1364 else
1365 {
1366 JMenuItem mi = new JMenuItem(jEdit.getProperty(
1367 "vfs.browser.plugins.no-plugins.label"));
1368 mi.setEnabled(false);
1369 popup.add(mi);
1370 }
1371 } //}}}
1372
1373 //{{{ ActionHandler class
1374 class ActionHandler implements ActionListener
1375 {
1376 public void actionPerformed(ActionEvent evt)
1377 {
1378 VFS vfs = VFSManager.getVFSByName(evt.getActionCommand());
1379 String directory = vfs.showBrowseDialog(null,
1380 VFSBrowser.this);
1381 if(directory != null)
1382 setDirectory(directory);
1383 }
1384 } //}}}
1385
1386 //{{{ MouseHandler class
1387 class MouseHandler extends MouseAdapter
1388 {
1389 public void mousePressed(MouseEvent evt)
1390 {
1391 createPopupMenu();
1392
1393 if(!popup.isVisible())
1394 {
1395 GUIUtilities.showPopupMenu(
1396 popup,PluginsMenuButton.this,0,
1397 PluginsMenuButton.this.getHeight(),
1398 false);
1399 }
1400 else
1401 {
1402 popup.setVisible(false