001: /*
002: * BrowserView.java
003: * :tabSize=8:indentSize=8:noTabs=false:
004: * :folding=explicit:collapseFolds=1:
005: *
006: * Copyright (C) 2000, 2003 Slava Pestov
007: *
008: * This program is free software; you can redistribute it and/or
009: * modify it under the terms of the GNU General Public License
010: * as published by the Free Software Foundation; either version 2
011: * of the License, or any later version.
012: *
013: * This program is distributed in the hope that it will be useful,
014: * but WITHOUT ANY WARRANTY; without even the implied warranty of
015: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
016: * GNU General Public License for more details.
017: *
018: * You should have received a copy of the GNU General Public License
019: * along with this program; if not, write to the Free Software
020: * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
021: */
022:
023: package org.gjt.sp.jedit.browser;
024:
025: //{{{ Imports
026: import javax.swing.border.EmptyBorder;
027: import javax.swing.event.*;
028: import javax.swing.*;
029:
030: import java.awt.event.*;
031: import java.awt.*;
032: import java.io.File;
033: import java.io.IOException;
034: import java.util.*;
035:
036: import org.gjt.sp.jedit.gui.DockableWindowManager;
037: import org.gjt.sp.jedit.io.*;
038: import org.gjt.sp.jedit.*;
039: import org.gjt.sp.util.Log;
040:
041: //}}}
042:
043: /**
044: * VFS browser tree view.
045: * @author Slava Pestov
046: * @version $Id: BrowserView.java 10789 2007-10-03 01:18:48Z Vampire0 $
047: */
048: class BrowserView extends JPanel {
049: //{{{ BrowserView constructor
050: BrowserView(final VFSBrowser browser) {
051: this .browser = browser;
052:
053: tmpExpanded = new HashSet<String>();
054: DockableWindowManager dwm = jEdit.getActiveView()
055: .getDockableWindowManager();
056: KeyListener keyListener = dwm.closeListener(VFSBrowser.NAME);
057:
058: parentDirectories = new ParentDirectoryList();
059: parentDirectories.addKeyListener(keyListener);
060:
061: parentDirectories.getSelectionModel().setSelectionMode(
062: ListSelectionModel.SINGLE_SELECTION);
063: parentDirectories
064: .setCellRenderer(new ParentDirectoryRenderer());
065: parentDirectories.setVisibleRowCount(5);
066: parentDirectories.addMouseListener(new ParentMouseHandler());
067:
068: final JScrollPane parentScroller = new JScrollPane(
069: parentDirectories);
070: parentScroller.setMinimumSize(new Dimension(0, 0));
071:
072: table = new VFSDirectoryEntryTable(this );
073: table.addMouseListener(new TableMouseHandler());
074: JScrollPane tableScroller = new JScrollPane(table);
075: tableScroller.setMinimumSize(new Dimension(0, 0));
076: tableScroller.getViewport()
077: .setBackground(table.getBackground());
078: tableScroller.getViewport().addMouseListener(
079: new TableMouseHandler());
080: splitPane = new JSplitPane(
081: browser.isHorizontalLayout() ? JSplitPane.HORIZONTAL_SPLIT
082: : JSplitPane.VERTICAL_SPLIT,
083: jEdit.getBooleanProperty("appearance.continuousLayout"),
084: parentScroller, tableScroller);
085: splitPane.setOneTouchExpandable(true);
086:
087: SwingUtilities.invokeLater(new Runnable() {
088: public void run() {
089: String prop = browser.isHorizontalLayout() ? "vfs.browser.horizontalSplitter"
090: : "vfs.browser.splitter";
091: int loc = jEdit.getIntegerProperty(prop, -1);
092: if (loc == -1)
093: loc = parentScroller.getPreferredSize().height;
094:
095: splitPane.setDividerLocation(loc);
096: parentDirectories
097: .ensureIndexIsVisible(parentDirectories
098: .getModel().getSize());
099: }
100: });
101:
102: if (browser.isMultipleSelectionEnabled())
103: table.getSelectionModel().setSelectionMode(
104: ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
105: else
106: table.getSelectionModel().setSelectionMode(
107: ListSelectionModel.SINGLE_SELECTION);
108:
109: setLayout(new BorderLayout());
110:
111: add(BorderLayout.CENTER, splitPane);
112:
113: propertiesChanged();
114: } //}}}
115:
116: //{{{ focusOnFileView() method
117: public void focusOnFileView() {
118: table.requestFocus();
119: } //}}}
120:
121: //{{{ removeNotify() method
122: public void removeNotify() {
123: String prop = browser.isHorizontalLayout() ? "vfs.browser.horizontalSplitter"
124: : "vfs.browser.splitter";
125: jEdit.setIntegerProperty(prop, splitPane.getDividerLocation());
126:
127: super .removeNotify();
128: } //}}}
129:
130: //{{{ getSelectedFiles() method
131: public VFSFile[] getSelectedFiles() {
132: return table.getSelectedFiles();
133: } //}}}
134:
135: //{{{ selectNone() method
136: public void selectNone() {
137: table.clearSelection();
138: } //}}}
139:
140: //{{{ saveExpansionState() method
141: public void saveExpansionState() {
142: tmpExpanded.clear();
143: table.getExpandedDirectories(tmpExpanded);
144: } //}}}
145:
146: //{{{ clearExpansionState() method
147: public void clearExpansionState() {
148: tmpExpanded.clear();
149: } //}}}
150:
151: //{{{ loadDirectory() method
152: public void loadDirectory(Object node, String path,
153: boolean addToHistory) {
154: path = MiscUtilities
155: .constructPath(browser.getDirectory(), path);
156: VFS vfs = VFSManager.getVFSForPath(path);
157:
158: Object session = vfs.createVFSSession(path, this );
159: if (session == null)
160: return;
161:
162: if (node == null) {
163: parentDirectories
164: .setListData(new Object[] { new LoadingPlaceholder() });
165: }
166:
167: Object[] loadInfo = new Object[2];
168:
169: VFSManager.runInWorkThread(new BrowserIORequest(
170: BrowserIORequest.LIST_DIRECTORY, browser, session, vfs,
171: path, null, loadInfo));
172: browser.directoryLoaded(node, loadInfo, addToHistory);
173: } //}}}
174:
175: //{{{ directoryLoaded() method
176: /**
177: * Rebuild the parent view after a directory has been loaded.
178: *
179: * @param node
180: * @param path
181: * @param directory
182: */
183: public void directoryLoaded(Object node, String path,
184: java.util.List<VFSFile> directory) {
185: //{{{ If reloading root, update parent directory list
186: if (node == null) {
187: DefaultListModel parentList = new DefaultListModel();
188:
189: String parent = path;
190:
191: for (;;) {
192: VFS _vfs = VFSManager.getVFSForPath(parent);
193: VFSFile file = null;
194: if (_vfs instanceof FileVFS) {
195: Object session = _vfs.createVFSSession(path,
196: browser);
197: try {
198: file = _vfs._getFile(session, parent, browser);
199: if (file != null) {
200: file.setName(_vfs.getFileName(parent));
201: }
202: } catch (IOException e) {
203: Log.log(Log.ERROR, this , e, e);
204: }
205: }
206: if (file == null) {
207: // create a DirectoryEntry manually
208: // instead of using _vfs._getFile()
209: // since so many VFS's have broken
210: // implementations of this method
211: file = new VFSFile(_vfs.getFileName(parent),
212: parent, parent, VFSFile.DIRECTORY, 0L,
213: false);
214: }
215:
216: /*parentList.insertElementAt(new VFSFile(
217: _vfs.getFileName(parent),
218: parent,parent,
219: VFSFile.DIRECTORY,
220: 0L,false),0);*/
221: parentList.insertElementAt(file, 0);
222: String newParent = _vfs.getParentOfPath(parent);
223:
224: if (newParent == null
225: || MiscUtilities.pathsEqual(parent, newParent))
226: break;
227: else
228: parent = newParent;
229: }
230:
231: parentDirectories.setModel(parentList);
232: int index = parentList.getSize() - 1;
233: parentDirectories.setSelectedIndex(index);
234: parentDirectories.ensureIndexIsVisible(index);
235: } //}}}
236:
237: table.setDirectory(VFSManager.getVFSForPath(path), node,
238: directory, tmpExpanded);
239: } //}}}
240:
241: //{{{ updateFileView() method
242: public void updateFileView() {
243: table.repaint();
244: } //}}}
245:
246: //{{{ maybeReloadDirectory() method
247: public void maybeReloadDirectory(String path) {
248: String browserDir = browser.getDirectory();
249: String symlinkBrowserDir;
250: if (MiscUtilities.isURL(browserDir)) {
251: symlinkBrowserDir = browserDir;
252: } else {
253: symlinkBrowserDir = MiscUtilities
254: .resolveSymlinks(browserDir);
255: }
256:
257: if (MiscUtilities.pathsEqual(path, symlinkBrowserDir)) {
258: saveExpansionState();
259: loadDirectory(null, browserDir, false);
260: }
261:
262: // because this method is called for *every* VFS update,
263: // we don't want to scan the tree all the time. So we
264: // use the following algorithm to determine if the path
265: // might be part of the tree:
266: // - if the path starts with the browser's current directory,
267: // we do the tree scan
268: // - if the browser's directory is 'favorites:' -- we have to
269: // do the tree scan, as every path can appear under the
270: // favorites list
271: // - if the browser's directory is 'roots:' and path is on
272: // the local filesystem, do a tree scan
273:
274: if (!browserDir.startsWith(FavoritesVFS.PROTOCOL)
275: && !browserDir.startsWith(FileRootsVFS.PROTOCOL)
276: && !path.startsWith(symlinkBrowserDir))
277: return;
278:
279: if (browserDir.startsWith(FileRootsVFS.PROTOCOL)
280: && MiscUtilities.isURL(path)
281: && !MiscUtilities.getProtocolOfURL(path).equals("file"))
282: return;
283:
284: table.maybeReloadDirectory(path);
285: } //}}}
286:
287: //{{{ propertiesChanged() method
288: public void propertiesChanged() {
289: showIcons = jEdit.getBooleanProperty("vfs.browser.showIcons");
290: table.propertiesChanged();
291: GUIUtilities.initContinuousLayout(splitPane);
292: splitPane.setBorder(null);
293: } //}}}
294:
295: //{{{ getBrowser() method
296: /**
297: * Returns the associated <code>VFSBrowser</code> instance.
298: * @since jEdit 4.2pre1
299: */
300: public VFSBrowser getBrowser() {
301: return browser;
302: } //}}}
303:
304: //{{{ getTable() method
305: public VFSDirectoryEntryTable getTable() {
306: return table;
307: } //}}}
308:
309: //{{{ getParentDirectoryList() method
310: public JList getParentDirectoryList() {
311: return parentDirectories;
312: } //}}}
313:
314: //{{{ Private members
315:
316: //{{{ Instance variables
317: private VFSBrowser browser;
318:
319: private JSplitPane splitPane;
320: private JList parentDirectories;
321: private VFSDirectoryEntryTable table;
322: private Set<String> tmpExpanded;
323: private BrowserCommandsMenu popup;
324: private boolean showIcons;
325:
326: //}}}
327:
328: //{{{ showFilePopup() method
329: private void showFilePopup(VFSFile[] files, Component comp,
330: Point point) {
331: popup = new BrowserCommandsMenu(browser, files);
332: // for the parent directory right-click; on the click we select
333: // the clicked item, but when the popup goes away we select the
334: // currently showing directory.
335: popup.addPopupMenuListener(new PopupMenuListener() {
336: public void popupMenuCanceled(PopupMenuEvent e) {
337: }
338:
339: public void popupMenuWillBecomeVisible(PopupMenuEvent e) {
340: }
341:
342: public void popupMenuWillBecomeInvisible(PopupMenuEvent e) {
343: // we use SwingUtilities.invokeLater()
344: // so that the action is executed before
345: // the popup is hidden.
346: SwingUtilities.invokeLater(new Runnable() {
347: public void run() {
348: int index = parentDirectories.getModel()
349: .getSize() - 1;
350: parentDirectories.setSelectedIndex(index);
351: }
352: });
353: }
354: });
355: GUIUtilities.showPopupMenu(popup, comp, point.x, point.y);
356: } //}}}
357:
358: //}}}
359:
360: //{{{ Inner classes
361:
362: //{{{ ParentDirectoryRenderer class
363: class ParentDirectoryRenderer extends DefaultListCellRenderer {
364: Font plainFont, boldFont;
365:
366: ParentDirectoryRenderer() {
367: plainFont = UIManager.getFont("Tree.font");
368: if (plainFont == null)
369: plainFont = jEdit
370: .getFontProperty("metal.secondary.font");
371: boldFont = new Font(plainFont.getName(), Font.BOLD,
372: plainFont.getSize());
373: }
374:
375: public Component getListCellRendererComponent(JList list,
376: Object value, int index, boolean isSelected,
377: boolean cellHasFocus) {
378: super .getListCellRendererComponent(list, value, index,
379: isSelected, cellHasFocus);
380:
381: ParentDirectoryRenderer.this .setBorder(new EmptyBorder(1,
382: index * 5 + 1, 1, 1));
383:
384: if (value instanceof LoadingPlaceholder) {
385: ParentDirectoryRenderer.this .setFont(plainFont);
386:
387: setIcon(showIcons ? FileCellRenderer.loadingIcon : null);
388: setText(jEdit.getProperty("vfs.browser.tree.loading"));
389: } else if (value instanceof VFSFile) {
390: VFSFile dirEntry = (VFSFile) value;
391: ParentDirectoryRenderer.this .setFont(boldFont);
392:
393: setIcon(showIcons ? FileCellRenderer.getIconForFile(
394: dirEntry, true) : null);
395: setText(dirEntry.getName());
396: } else if (value == null)
397: setText("VFS does not follow VFS API");
398:
399: return this ;
400: }
401: } //}}}
402:
403: //{{{ ParentMouseHandler class
404: class ParentMouseHandler extends MouseAdapter {
405: public void mousePressed(MouseEvent evt) {
406: int row = parentDirectories.locationToIndex(evt.getPoint());
407: if (row != -1) {
408: Object obj = parentDirectories.getModel().getElementAt(
409: row);
410: if (obj instanceof VFSFile) {
411: VFSFile dirEntry = ((VFSFile) obj);
412: if (GUIUtilities.isPopupTrigger(evt)) {
413: if (popup != null && popup.isVisible()) {
414: popup.setVisible(false);
415: popup = null;
416: } else {
417: parentDirectories.setSelectedIndex(row);
418: showFilePopup(new VFSFile[] { dirEntry },
419: parentDirectories, evt.getPoint());
420: }
421: }
422: }
423: }
424: }
425:
426: public void mouseReleased(MouseEvent evt) {
427: if (evt.getClickCount() % 2 != 0
428: && !GUIUtilities.isMiddleButton(evt.getModifiers()))
429: return;
430:
431: int row = parentDirectories.locationToIndex(evt.getPoint());
432: if (row != -1) {
433: Object obj = parentDirectories.getModel().getElementAt(
434: row);
435: if (obj instanceof VFSFile) {
436: VFSFile dirEntry = ((VFSFile) obj);
437: if (!GUIUtilities.isPopupTrigger(evt)) {
438: browser.setDirectory(dirEntry.getPath());
439: if (browser.getMode() == VFSBrowser.BROWSER)
440: focusOnFileView();
441: }
442: }
443: }
444: }
445: } //}}}
446:
447: //{{{ TableMouseHandler class
448: class TableMouseHandler extends MouseAdapter {
449: //{{{ mouseClicked() method
450: public void mouseClicked(MouseEvent evt) {
451: Point p = evt.getPoint();
452: int row = table.rowAtPoint(p);
453: int column = table.columnAtPoint(p);
454: if (row == -1)
455: return;
456: if (column == 0) {
457: VFSDirectoryEntryTableModel.Entry entry = (VFSDirectoryEntryTableModel.Entry) table
458: .getModel().getValueAt(row, 0);
459: if (FileCellRenderer.ExpansionToggleBorder
460: .isExpansionToggle(entry.level, p.x)) {
461: return;
462: }
463: }
464:
465: if ((evt.getModifiers() & InputEvent.BUTTON1_MASK) != 0
466: && evt.getClickCount() % 2 == 0) {
467: browser.filesActivated(
468: (evt.isShiftDown() ? VFSBrowser.M_OPEN_NEW_VIEW
469: : VFSBrowser.M_OPEN), true);
470: } else if (GUIUtilities.isMiddleButton(evt.getModifiers())) {
471: if (evt.isShiftDown())
472: table.getSelectionModel().addSelectionInterval(row,
473: row);
474: else
475: table.getSelectionModel().setSelectionInterval(row,
476: row);
477: browser.filesActivated(
478: (evt.isShiftDown() ? VFSBrowser.M_OPEN_NEW_VIEW
479: : VFSBrowser.M_OPEN), true);
480: }
481: } //}}}
482:
483: //{{{ mousePressed() method
484: public void mousePressed(MouseEvent evt) {
485: Point p = evt.getPoint();
486: if (evt.getSource() != table) {
487: p.x -= table.getX();
488: p.y -= table.getY();
489: }
490:
491: int row = table.rowAtPoint(p);
492: int column = table.columnAtPoint(p);
493: if (column == 0 && row != -1) {
494: VFSDirectoryEntryTableModel.Entry entry = (VFSDirectoryEntryTableModel.Entry) table
495: .getModel().getValueAt(row, 0);
496: if (FileCellRenderer.ExpansionToggleBorder
497: .isExpansionToggle(entry.level, p.x)) {
498: table.toggleExpanded(row);
499: return;
500: }
501: }
502:
503: if (GUIUtilities.isMiddleButton(evt.getModifiers())) {
504: if (row == -1)
505: /* nothing */;
506: else if (evt.isShiftDown())
507: table.getSelectionModel().addSelectionInterval(row,
508: row);
509: else
510: table.getSelectionModel().setSelectionInterval(row,
511: row);
512: } else if (GUIUtilities.isPopupTrigger(evt)) {
513: if (popup != null && popup.isVisible()) {
514: popup.setVisible(false);
515: popup = null;
516: return;
517: }
518:
519: if (row == -1)
520: showFilePopup(null, table, evt.getPoint());
521: else {
522: if (!table.getSelectionModel().isSelectedIndex(row))
523: table.getSelectionModel().setSelectionInterval(
524: row, row);
525: showFilePopup(getSelectedFiles(), table, evt
526: .getPoint());
527: }
528: }
529: } //}}}
530:
531: //{{{ mouseReleased() method
532: public void mouseReleased(MouseEvent evt) {
533: if (!GUIUtilities.isPopupTrigger(evt)
534: && table.getSelectedRow() != -1) {
535: browser.filesSelected();
536: }
537: } //}}}
538: } //}}}
539:
540: static class LoadingPlaceholder {
541: }
542:
543: //}}}
544:
545: class ParentDirectoryList extends JList {
546:
547: public String getPath(int row) {
548: LinkedList<String> components = new LinkedList<String>();
549: for (int i = 1; i <= row; ++i)
550: components.add(getModel().getElementAt(i).toString());
551: return getModel().getElementAt(0)
552: + TextUtilities.join(components, File.separator);
553: }
554:
555: protected void processKeyEvent(KeyEvent evt) {
556: if (evt.getID() == KeyEvent.KEY_PRESSED) {
557: ActionContext ac = VFSBrowser.getActionContext();
558: int row = parentDirectories.getSelectedIndex();
559: switch (evt.getKeyCode()) {
560: case KeyEvent.VK_DOWN:
561: evt.consume();
562: if (row < parentDirectories.getSize().height - 1)
563: parentDirectories.setSelectedIndex(++row);
564: break;
565: case KeyEvent.VK_TAB:
566: table.requestFocus();
567: evt.consume();
568: break;
569: case KeyEvent.VK_UP:
570: evt.consume();
571: if (row > 0)
572: parentDirectories.setSelectedIndex(--row);
573: break;
574: case KeyEvent.VK_BACK_SPACE:
575: evt.consume();
576: EditAction ea = ac.getAction("vfs.browser.up");
577: ac.invokeAction(evt, ea);
578: break;
579: case KeyEvent.VK_F5:
580: evt.consume();
581: ac = VFSBrowser.getActionContext();
582: ea = ac.getAction("vfs.browser.reload");
583: ac.invokeAction(evt, ea);
584: break;
585: case KeyEvent.VK_ENTER:
586: evt.consume();
587: String path = getPath(row);
588: getBrowser().setDirectory(path);
589: table.requestFocus();
590: break;
591: /* These actions don't work because they look at the EntryTable for the current selected
592: * item. We need actions that look at the parentDirectoryList item instead.
593: *
594: case KeyEvent.VK_DELETE:
595: evt.consume();
596: ea = ac.getAction("vfs.browser.delete");
597: ac.invokeAction(evt, ea);
598: break;
599: case KeyEvent.CTRL_MASK | KeyEvent.VK_N:
600: evt.consume();
601: ea = ac.getAction("vfs.browser.new-file");
602: ac.invokeAction(evt, ea);
603: break;
604: case KeyEvent.VK_INSERT:
605: evt.consume();
606: ea = ac.getAction("vfs.browser.new-directory");
607: ac.invokeAction(evt, ea);
608: break; */
609: }
610: } else if (evt.getID() == KeyEvent.KEY_TYPED) {
611: if (evt.isControlDown() || evt.isAltDown()
612: || evt.isMetaDown()) {
613: evt.consume();
614: return;
615: }
616: switch (evt.getKeyChar()) {
617: case '~':
618: evt.consume();
619: if (browser.getMode() == VFSBrowser.BROWSER)
620: browser.setDirectory(System
621: .getProperty("user.home"));
622: break;
623: case '/':
624: evt.consume();
625: if (browser.getMode() == VFSBrowser.BROWSER)
626: browser.rootDirectory();
627: break;
628: case '-':
629: evt.consume();
630: if (browser.getMode() == VFSBrowser.BROWSER) {
631: browser.setDirectory(browser.getView()
632: .getBuffer().getDirectory());
633: }
634: break;
635: }
636: }
637: if (!evt.isConsumed())
638: super.processKeyEvent(evt);
639: }
640: }
641:
642: }
|