Main Page | Packages | Class Hierarchy | Class List | Directories | File List | Namespace Members | Class Members | File Members | Related Pages

ProjectExplorerPanel.java

Go to the documentation of this file.
00001 /*
00002  * ProjectExplorerPanel.java
00003  *
00004  * Copyright (C) 2005 Project SQUID, http://www.cs.helsinki.fi/group/squid/
00005  *
00006  * This file is part of Ikayaki.
00007  *
00008  * Ikayaki is free software; you can redistribute it and/or modify
00009  * it under the terms of the GNU General Public License as published by
00010  * the Free Software Foundation; either version 2 of the License, or
00011  * (at your option) any later version.
00012  *
00013  * Ikayaki is distributed in the hope that it will be useful,
00014  * but WITHOUT ANY WARRANTY; without even the implied warranty of
00015  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
00016  * GNU General Public License for more details.
00017  *
00018  * You should have received a copy of the GNU General Public License
00019  * along with Ikayaki; if not, write to the Free Software
00020  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
00021  */
00022 
00023 package ikayaki.gui;
00024 
00025 import ikayaki.Ikayaki;
00026 import ikayaki.Project;
00027 import ikayaki.Settings;
00028 import ikayaki.util.LastExecutor;
00029 
00030 import javax.swing.*;
00031 import javax.swing.event.PopupMenuEvent;
00032 import javax.swing.event.PopupMenuListener;
00033 import java.awt.*;
00034 import java.awt.event.ActionEvent;
00035 import java.awt.event.ActionListener;
00036 import java.awt.event.KeyAdapter;
00037 import java.awt.event.KeyEvent;
00038 import java.io.File;
00039 import java.io.FileFilter;
00040 import java.util.Arrays;
00041 
00050 public class ProjectExplorerPanel extends ProjectComponent {
00054     private final ProjectComponent parent;
00055 
00059     private final JPanel browsePanel = new JPanel();
00060 
00066     private final JComboBox browserField;
00067     private final FittedComboBoxRenderer browserFieldRenderer;
00068     private final JTextField browserFieldEditor; // WARNING: look-and-feel-dependant code
00069     private final ComponentFlasher browserFieldFlasher;
00070 
00074     boolean browserFieldPopupIsAutocomplete = false;
00075 
00079     // private boolean browserFieldNextPopupIsAutocomplete = false;
00080 
00084     boolean browserFieldUpdatingPopup = false;
00085 
00086     private final JButton browseButton;
00087 
00088     private final ProjectExplorerTable explorerTable;
00089     private final JScrollPane explorerTableScrollPane;
00090 
00091     private NewProjectPanel newProjectPanel;
00092 
00096     private final LastExecutor autocompleteExecutor = new LastExecutor(100, true);
00097 
00101     private File directory = null;
00102 
00108     public ProjectExplorerPanel(ProjectComponent parent) {
00109         this(parent, null);
00110     }
00111 
00119     public ProjectExplorerPanel(ProjectComponent parent, Project project) {
00120         this.parent = parent;
00121 
00122         // combo box / text field
00123         browserField = new JComboBox(getDirectoryHistory());
00124         browserField.setEditable(true);
00125         browserField.setBackground(Color.WHITE);
00126         browserFieldEditor = (JTextField) browserField.getEditor().getEditorComponent();
00127         browserFieldFlasher = new ComponentFlasher(browserFieldEditor);
00128         browserFieldEditor.setFocusTraversalKeysEnabled(false); // disable tab-exiting from browserField
00129 
00130         // custom renderer for browserField's items so that long path names in the popup menu are made shorter
00131         browserFieldRenderer = new FittedComboBoxRenderer(browserFieldEditor);
00132         browserField.setRenderer(browserFieldRenderer);
00133 
00134         // browse button
00135         browseButton = new JButton("Browse...");
00136 
00137         // add both into this
00138         browsePanel.setLayout(new BorderLayout());
00139         //browsePanel.setBorder(BorderFactory.createEmptyBorder(0, 0, 4, 0));
00140         browsePanel.add(browserField, BorderLayout.CENTER);
00141         browsePanel.add(browseButton, BorderLayout.EAST);
00142 
00143         // project file table and its ScrollPane
00144         explorerTable = new ProjectExplorerTable(this.parent);
00145         explorerTableScrollPane = new JScrollPane(explorerTable);
00146         // explorerTableScrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
00147         explorerTableScrollPane.getViewport().setBackground(Color.WHITE);
00148 
00149         // new project panel
00150         newProjectPanel = new NewProjectPanel();
00151 
00152         this.setLayout(new BorderLayout());
00153         this.add(browsePanel, BorderLayout.NORTH);
00154         this.add(explorerTableScrollPane, BorderLayout.CENTER);
00155         this.add(newProjectPanel, BorderLayout.SOUTH);
00156 
00157         // set current directory to project dir or latest directory history dir
00158         if (project != null) {
00159             setDirectory(project.getFile().getParentFile());
00160         } else {
00161             setDirectory(Settings.getLastDirectory());
00162         }
00163 
00164         // scroll to the end of the combo box's text field (after setting directory) -- seems to happen anyway
00165         // setBrowserFieldCursorToEnd();
00166 
00167         // ProjectExplorer events
00168 
00173         browseButton.addActionListener(new ActionListener() {
00174             public void actionPerformed(ActionEvent e) {
00175                 JFileChooser fc = new JFileChooser(directory);
00176                 fc.setDialogTitle("Change directory");
00177                 fc.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
00178 
00179                 if (fc.showOpenDialog(browseButton) == JFileChooser.APPROVE_OPTION) {
00180                     setDirectory(fc.getSelectedFile());
00181                 }
00182             }
00183         });
00184 
00189         browserField.addActionListener(new ActionListener() {
00190             public void actionPerformed(ActionEvent e) {
00191                 // check if event is caused by popup menu update
00192                 if (browserFieldUpdatingPopup) return;
00193 
00194                 // JComboBox event types:
00195                 // Edited -- pressed enter in the text field or selected an item by pressing enter
00196                 // Changed -- cycled item list with up/down, selected item with mouse or pressed enter in text field
00197                 // System.out.println(e.getActionCommand() + ": " + browserField.getSelectedItem());
00198 
00199                 // we only want changed-events, not duplicate edited-events -- actually, we want those too,
00200                 // so that multiple tries to change to an invalid directory with enter will flash text field
00201                 // if (!e.getActionCommand().equals("comboBoxChanged")) return;
00202 
00203                 // TODO: cycling through popup menu list with up/down keys changes directory;
00204                 // it shouldn't, but can't recognize those changed-events from mouse clicks
00205 
00206                 // item is File if selected from list, String if written to text field
00207                 Object item = browserField.getEditor().getItem();
00208                 File dir = item instanceof File ? (File) item : new File((String) item).getAbsoluteFile();
00209 
00210                 // try to set directory, flash browserField red if error
00211                 if (!setDirectory(dir)) browserFieldFlasher.flash();
00212             }
00213         });
00214 
00218         browserField.addPopupMenuListener(new PopupMenuListener() {
00219             public void popupMenuWillBecomeInvisible(PopupMenuEvent e) {
00220                 // set popup menu as directory history when closing popup, unless it's already dir history
00221                 if (browserFieldPopupIsAutocomplete) {
00222                     browserFieldPopupIsAutocomplete = false;
00223                     // TODO: when mouseclicking autocomplete list item, textfield gets cleared because of this
00224                     //Object item = browserField.getSelectedItem();
00225                     setBrowserFieldPopup(getDirectoryHistory(), false);
00226                     //browserField.setSelectedItem(item);
00227                 }
00228             }
00229 
00230             public void popupMenuWillBecomeVisible(PopupMenuEvent e) {
00231             }
00232 
00233             public void popupMenuCanceled(PopupMenuEvent e) {
00234             }
00235         });
00236 
00242         browserFieldEditor.addKeyListener(new KeyAdapter() {
00243             public void keyPressed(KeyEvent e) {
00244                 if (e.getKeyCode() == KeyEvent.VK_TAB) {
00245                     // change to down- or up-arrow event
00246                     if ((e.getModifiers() & KeyEvent.SHIFT_MASK) != 0) {
00247                         // this deprecation is required for shift-tab to work
00248                         e.setModifiers(e.getModifiers() ^ KeyEvent.SHIFT_MASK);
00249                         e.setKeyCode(KeyEvent.VK_UP);
00250                     } else {
00251                         e.setKeyCode(KeyEvent.VK_DOWN);
00252                     }
00253                 }
00254             }
00255 
00256             public void keyTyped(KeyEvent e) {
00257                 if (e.getKeyChar() == KeyEvent.VK_ESCAPE) {
00258                     browserField.setSelectedItem(directory);
00259                     // browserField.getEditor().selectAll();
00260                     return;
00261                 } else if (e.getKeyChar() == KeyEvent.VK_ENTER) {
00262                     return;
00263                 } else if (e.getKeyChar() == KeyEvent.VK_TAB) return;
00264 
00265                 if (e.getModifiers() == KeyEvent.CTRL_MASK && e.getKeyChar() == KeyEvent.VK_DELETE) {
00266                     // delete one directory name at a time
00267                     int pos = browserFieldEditor.getCaretPosition();
00268                     String text = browserFieldEditor.getText();
00269                     String textA = text.substring(0, pos);
00270                     String textB = text.substring(pos);
00271                     textA = textA.substring(0, Math.max(0, textA.lastIndexOf(System.getProperty("file.separator"))));
00272                     browserFieldEditor.setText(textA + textB);
00273                     browserFieldEditor.setCaretPosition(textA.length());
00274 
00275                 } else if ((e.getModifiers() & KeyEvent.ALT_MASK) != 0 || (e.getModifiers() & KeyEvent.CTRL_MASK) != 0) {
00276                     // avoid the popup menu from showing, when the Project Explorer tab is hidden with ALT+P
00277                     browserField.hidePopup();
00278                     return;
00279                 }
00280 
00281                 autocompleteExecutor.execute(new Runnable() {
00282                     public void run() {
00283                         doAutoComplete();
00284                     }
00285                 });
00286             }
00287         });
00288     }
00289 
00295     public void setProject(Project project) {
00296         super.setProject(project);
00297 
00298         // update directory history, as it might have changed
00299         browserField.hidePopup();
00300         setBrowserFieldPopup(getDirectoryHistory(), false);
00301 
00302         // change directory, if not calibration project; in that case just update selected project in explorerTable
00303         if (project != null && project.getType() != Project.Type.CALIBRATION) {
00304             setDirectory(project.getFile().getParentFile());
00305         } else {
00306             explorerTable.setDirectory(directory);
00307         }
00308 
00309         // add explorerTable as a ProjectListener so it can update current project's timestamps
00310         if (project != null) project.addProjectListener(explorerTable);
00311     }
00312 
00319     private boolean setDirectory(File directory) {
00320         if (directory == null || !directory.isDirectory()) return false;
00321 
00322         this.directory = directory;
00323 
00324         // update browserField and explorerTable with new directory
00325         browserField.setSelectedItem(this.directory);
00326         explorerTable.setDirectory(this.directory);
00327 
00328         return true;
00329     }
00330 
00336     private File[] getDirectoryHistory() {
00337         return Settings.getDirectoryHistory();
00338     }
00339 
00346     private File[] getAutocompleteFiles(String dirmatch) {
00347         File dirfile = new File(dirmatch);
00348         if (!dirfile.isAbsolute()) return File.listRoots();
00349         File dir = dirfile.isDirectory() ? dirfile : dirfile.getParentFile();
00350 
00351         // protect against no-parent-null and invalid-dir-list-null
00352         if (dir == null) {
00353             dir = directory;
00354         } else if (!dir.isDirectory()) return new File[0];
00355 
00356         final String match = dirfile.isDirectory() ? "" : dirfile.getName();
00357 
00358         File[] files = dir.listFiles(new FileFilter() {
00359             public boolean accept(File file) {
00360                 return (file.isDirectory() && file.getName().toLowerCase().startsWith(match.toLowerCase()));
00361             }
00362         });
00363 
00364         // listFiles() does not guarantee any order for the returned files, so they must be sorted first
00365         Arrays.sort(files);
00366         return files;
00367     }
00368 
00372     private void doAutoComplete() {
00373         final File[] files = getAutocompleteFiles(browserField.getEditor().getItem().toString());
00374 
00375         // gui updating must be done from event-dispatching thread
00376         SwingUtilities.invokeLater(new Runnable() {
00377             public void run() {
00378                 // when the popup is hidden before showing, it will be automatically resized
00379                 // (disable change-to-dirhistory-on-popup-hide for this)
00380                 browserFieldPopupIsAutocomplete = false;
00381                 browserField.hidePopup();
00382 
00383                 browserFieldPopupIsAutocomplete = true;
00384                 setBrowserFieldPopup(files, true);
00385                 if (files.length > 0) browserField.showPopup();
00386             }
00387         });
00388     }
00389 
00396     private void setBrowserFieldPopup(File[] files, boolean uniformFit) {
00397         // purkkaillaan -- some hardcore bubblegum stitching (the whole method)
00398         browserFieldUpdatingPopup = true;
00399 
00400         String browserFieldEditorText = browserFieldEditor.getText();
00401         int browserFieldEditorCursorPosition = browserFieldEditor.getCaretPosition();
00402 
00403         browserField.removeAllItems();
00404         for (File file : files) browserField.addItem(file);
00405 
00406         // fit the contents of the menu list
00407         if (uniformFit) {
00408             int maxFitValue = 0;
00409             for (int i = 0; i < browserField.getItemCount(); i++) {
00410                 Object item = browserField.getItemAt(i);
00411                 int fitValue = browserFieldRenderer.fitValue(item);
00412                 maxFitValue = Math.max(maxFitValue, fitValue);
00413             }
00414             browserFieldRenderer.setFitLimit(maxFitValue);
00415         } else {
00416             browserFieldRenderer.setFitLimit(-1);
00417         }
00418 
00419         browserField.setSelectedIndex(-1);
00420         browserFieldEditor.setText(browserFieldEditorText);
00421         browserFieldEditor.setCaretPosition(browserFieldEditorCursorPosition);
00422 
00423         browserFieldUpdatingPopup = false;
00424     }
00425 
00431     private void setBrowserFieldCursorToEnd() {
00432         browserFieldEditor.setCaretPosition(browserFieldEditor.getDocument().getLength());
00433     }
00434 
00438     private class NewProjectPanel extends JPanel {
00439 
00440         private final JTextField newProjectName;
00441         private final JComboBox newProjectType;
00442         private final JButton createNewProjectButton;
00443         private final JPanel flowPanel;
00444         private final ComponentFlasher newProjectNameFlasher;
00445 
00446         public NewProjectPanel() {
00447             super(new BorderLayout());
00448 
00449             newProjectName = new JTextField();
00450             newProjectType = new JComboBox(Project.Type.values());
00451             newProjectType.removeItem(Project.Type.CALIBRATION); // calibration projects are created from the File menu
00452             newProjectType.setSelectedItem(Project.Type.AF);
00453             newProjectType.setBackground(Color.WHITE);
00454             createNewProjectButton = new JButton("Create New");
00455 
00456             flowPanel = new JPanel(new BorderLayout());
00457             flowPanel.add(newProjectType, BorderLayout.WEST);
00458             flowPanel.add(createNewProjectButton, BorderLayout.EAST);
00459 
00460             this.add(newProjectName, BorderLayout.CENTER);
00461             this.add(flowPanel, BorderLayout.EAST);
00462 
00463             newProjectNameFlasher = new ComponentFlasher(newProjectName);
00464 
00471             createNewProjectButton.addActionListener(new ActionListener() {
00472                 public void actionPerformed(ActionEvent e) {
00473                     String name = newProjectName.getText();
00474                     if (name.length() > 0 && !name.toLowerCase().endsWith(Ikayaki.FILE_TYPE)) {
00475                         name += Ikayaki.FILE_TYPE;
00476                     }
00477 
00478                     Project.Type type = (Project.Type) newProjectType.getSelectedItem();
00479                     File file = new File(directory, name);
00480                     Project created = Project.createProject(file, type);
00481 
00482                     if (created == null) {
00483                         newProjectNameFlasher.flash();
00484                         newProjectName.requestFocusInWindow();
00485                     } else {
00486                         parent.setProject(created);
00487                         newProjectName.setText("");
00488                     }
00489                 }
00490             });
00491 
00495             newProjectName.addActionListener(new ActionListener() {
00496                 public void actionPerformed(ActionEvent e) {
00497                     createNewProjectButton.doClick();
00498                 }
00499             });
00500         }
00501     }
00502 }

Generated on Fri May 6 16:00:32 2005 for Squid by  doxygen 1.4.1