/*
* Copyright (c) 2007 BUSINESS OBJECTS SOFTWARE LIMITED
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* * Neither the name of Business Objects nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
/*
* GemBrowser.java
* Creation date: Mar 4, 2003
* By: David Mosimann
*/
package org.openquark.gems.client.browser;
import java.awt.BorderLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.prefs.Preferences;
import javax.swing.BorderFactory;
import javax.swing.Box;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextField;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
import javax.swing.border.EtchedBorder;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.tree.TreeModel;
import javax.swing.tree.TreePath;
import org.openquark.cal.services.Perspective;
import org.openquark.gems.client.ModuleNameDisplayUtilities.TreeViewDisplayMode;
/**
* This class encapsulates the functionality of the gem browser and a set of buttons to manipulate the
* tree sorting. After constructing the object the initialize method must be called to fill the tree.
* The UI component can then be retrieved via the getGemBrowserPanel() method.
*/
public class GemBrowser {
/** The BrowserTree that displays the browser tree model */
private final BrowserTree browserTree;
/** If true the control buttons at the bottom of the tree are shown. */
private final boolean showControlPanel;
/** The JPanel that contains the tree and control buttons */
private JPanel gemBrowserPanel;
/** The timer used for updating the search results if the user stopped typing */
private Timer searchUpdateTimer;
/* Preference key names. */
private static final String BROWSER_MODULE_TREE_DISPLAY_MODE = "browserModuleTreeDisplayMode";
/**
* Constructor for a GemBrowser
* Note that the browser tree is initially empty and will need to be filled with data from the current Perspective
*/
public GemBrowser() {
this(true);
}
/**
* Constructor for a GemBrowser
* Note that the browser tree is initially empty and will need to be filled with data from the current Perspective
* @param showControlPanel whether to show the control panel (buttons to control sorting/categorization)
*/
public GemBrowser(boolean showControlPanel) {
// Note that initialize() MUST be called before this object will work correctly
this.showControlPanel = showControlPanel;
browserTree = new BrowserTree();
browserTree.setToolTipText("BrowserTreeToolTip");
}
/**
* Initializes the tree based on the perspective passed in. If this GemBrowser needs to be
* initialized again, use {@link #reinitialize} instead.
*
* @param perspective
* @param showAllModules whether to show modules not visible from the given perspective.
* @param highlightCurrentModule whether the current module should be highlighted.
*/
public void initialize(Perspective perspective, boolean showAllModules, boolean highlightCurrentModule) {
final TreeViewDisplayMode preferredModuleTreeDisplayMode = getPreferredModuleTreeDisplayMode();
// Fill in the gem browser with the available gems
BrowserTreeModel browserTreeModel = (BrowserTreeModel)getBrowserTree().getModel();
browserTreeModel.clearDrawers();
browserTreeModel.populate(perspective, showAllModules, preferredModuleTreeDisplayMode);
// Clean up the default display a little
browserTree.setHighlightCurrentModule(highlightCurrentModule);
browserTreeModel.arrangeAsDefaultTreeStructure(preferredModuleTreeDisplayMode);
// expandLevel modifies the view of the tree and thus needs to run on the AWT event-handler thread.
SwingUtilities.invokeLater(new Runnable() {
public void run() {
browserTree.expandLevel(1);
}
});
// Update the state of the buttons
browserTree.getBrowserTreeActions().getDisplayUnimportedGemsAction().putValue(GemBrowserActionKeys.ACTION_SELECTED_KEY, Boolean.valueOf(showAllModules));
if (preferredModuleTreeDisplayMode == TreeViewDisplayMode.FLAT_ABBREVIATED) {
browserTree.getBrowserTreeActions().getCategorizeByModuleFlatAbbreviatedButton().getModel().setSelected(true);
} else if (preferredModuleTreeDisplayMode == TreeViewDisplayMode.FLAT_FULLY_QUALIFIED) {
browserTree.getBrowserTreeActions().getCategorizeByModuleFlatFullyQualifiedButton().getModel().setSelected(true);
} else if (preferredModuleTreeDisplayMode == TreeViewDisplayMode.HIERARCHICAL) {
browserTree.getBrowserTreeActions().getCategorizeByModuleHierarchicalButton().getModel().setSelected(true);
} else {
throw new IllegalStateException("Unexpected: " + preferredModuleTreeDisplayMode);
}
}
/**
* Refreshes the tree using the perspective and other parameters it was originally
* initialized with. This will remember the tree expansion & selection state and
* restore it after reloading the tree model.
*/
public void refresh() {
final BrowserTree browserTree = getBrowserTree();
final BrowserTreeModel browserTreeModel = (BrowserTreeModel) browserTree.getModel();
SwingUtilities.invokeLater(new Runnable() {
public void run() {
// Save the tree state
browserTree.saveState();
// Reload the model
browserTreeModel.reload();
// Restore the tree state
browserTree.restoreSavedState();
}
});
}
/**
* Reinitializes the tree based on the perspective passed in. This will remember the tree expansion & selection state and
* restore it after reloading the tree model.
*
* This is very similar to {@link #refresh}, which uses the existing perspective when reloading the tree model.
*
* @param perspective
*/
public void reinitialize(final Perspective perspective) {
final BrowserTreeModel browserTreeModel = (BrowserTreeModel)getBrowserTree().getModel();
SwingUtilities.invokeLater(new Runnable() {
public void run() {
browserTree.saveState();
// Reload the model
browserTreeModel.setPerspectiveAndReload(perspective);
browserTree.restoreSavedState();
}
});
}
/**
* Return the GemBrowserPanel property value.
* @return JPanel
*/
public JPanel getGemBrowserPanel() {
if (gemBrowserPanel == null) {
// First wrap the gem browser tree in a scoll pane
JScrollPane scrollableBrowserTree = new JScrollPane(getBrowserTree());
gemBrowserPanel = new JPanel();
gemBrowserPanel.setName("GemBrowserPanel");
gemBrowserPanel.setLayout(new BorderLayout());
gemBrowserPanel.setBorder(BorderFactory.createEtchedBorder(EtchedBorder.LOWERED));
gemBrowserPanel.add(scrollableBrowserTree, BorderLayout.CENTER);
if (showControlPanel) {
// Add the main vbox
Box vbox = Box.createVerticalBox();
gemBrowserPanel.add(vbox, BorderLayout.SOUTH);
// Add an hbox for the search text field right below the tree
Box hbox = Box.createHorizontalBox();
vbox.add(hbox);
hbox.add(getSearchTextField());
// Add an hbox for the buttons below the search text field
hbox = Box.createHorizontalBox();
vbox.add(Box.createVerticalStrut(5));
vbox.add(hbox);
hbox.add(Box.createHorizontalGlue());
hbox.add(browserTree.getBrowserTreeActions().getBrowserTreeButtonPanel());
hbox.add(Box.createHorizontalGlue());
vbox.add(Box.createVerticalStrut(5));
}
}
return gemBrowserPanel;
}
/**
* Returns the search text field the use can use to search for gems.
* @return JTextField
*/
private JTextField getSearchTextField () {
final JTextField searchField = new JTextField();
searchField.setColumns(15);
searchField.setToolTipText(BrowserMessages.getString("GB_SearchFieldToolTip"));
// A timer that runs once the user stops typing.
// This will execute the model search with the text the user has entered.
searchUpdateTimer = new Timer(250, new ActionListener() {
BrowserTreeModel browserTreeModel = (BrowserTreeModel) browserTree.getModel();
public void actionPerformed(ActionEvent e) {
if (e.getSource() == searchUpdateTimer) {
browserTree.saveState();
browserTreeModel.doSearch(searchField.getText());
browserTree.restoreSavedState();
if (searchField.getText().trim().equals("")) {
// If we didn't search for anything scroll to the workspace node.
BrowserTreeNode workspaceNode = browserTreeModel.getWorkspaceNode();
TreePath workspaceNodePath = new TreePath(workspaceNode.getPath());
browserTree.scrollPathToTop(workspaceNodePath);
browserTree.setSelectionPath(workspaceNodePath);
} else {
// Invoke this later once the tree has synced up with the model.
SwingUtilities.invokeLater(new Runnable() {
public void run() {
TreePath searchNodePath = new TreePath(browserTreeModel.getSearchResultsNode().getPath());
browserTree.expandPath(searchNodePath);
browserTree.setSelectionPath(searchNodePath);
browserTree.scrollPathToTop(searchNodePath);
}
});
}
}
}
});
searchUpdateTimer.setRepeats(false);
// Add a document listener so we can update the search results if the text changes.
searchField.getDocument().addDocumentListener(new DocumentListener () {
public void insertUpdate(DocumentEvent e) {
searchUpdateTimer.setInitialDelay(searchUpdateDelay());
searchUpdateTimer.restart();
}
public void removeUpdate(DocumentEvent e) {
searchUpdateTimer.setInitialDelay(searchUpdateDelay());
searchUpdateTimer.restart();
}
public void changedUpdate(DocumentEvent e) {
}
/**
* Return the number of milliseconds to wait until we start searching. This number varies depending
* upon the amount of text that has been entered by the user. If the user has entered only one or
* two characters, then we use a long delay, otherwise a standard one. This makes it less likely that
* we will do a costly search that returns thousands of results.
* @return int
*/
private int searchUpdateDelay() {
if (searchField.getText().length() > 0 && searchField.getText().length() < 3) {
return 750;
} else {
return 250;
}
}
});
return searchField;
}
/**
* Return the browser tree from this browser.
* @return BrowserTree
*/
public BrowserTree getBrowserTree() {
return browserTree;
}
/**
* Sets the name of the workspace visible in square brackets in the
* Workspace node of the Gem Browser tree.
* @param name the name to display
*/
public void setWorkspaceNodeName(String name) {
TreeModel treeModel = getBrowserTree().getModel();
if (treeModel instanceof BrowserTreeModel) {
BrowserTreeModel browserTreeModel = (BrowserTreeModel)treeModel;
browserTreeModel.setWorkspaceNodeName(name);
}
}
/**
* Marks the current workspace as being "dirty" (changed from the saved
* version) by adding an asterisk in front of the name of the workspace in
* the Gem Browser tree
*/
public void markWorkspaceDirty() {
TreeModel treeModel = getBrowserTree().getModel();
if (treeModel instanceof BrowserTreeModel) {
BrowserTreeModel model = (BrowserTreeModel)treeModel;
BrowserTreeNode workspaceNode = model.getWorkspaceNode();
String name = workspaceNode.getDisplayedString();
// find where start of workspace name is
int workspaceNameLoc = name.indexOf('[') + 1;
// check if already dirty
if (name.charAt(workspaceNameLoc) != '*') {
// mane new node name string containing '*'
String newName = name.substring(0, workspaceNameLoc) + "*" + name.substring(workspaceNameLoc);
// set node name
workspaceNode.setUserObject(newName);
}
}
}
/**
* @return the preferences instance for this class.
*/
private static Preferences getPreferences() {
return Preferences.userNodeForPackage(GemBrowser.class);
}
static TreeViewDisplayMode getPreferredModuleTreeDisplayMode() {
return TreeViewDisplayMode.fromName(getPreferences().get(BROWSER_MODULE_TREE_DISPLAY_MODE, TreeViewDisplayMode.FLAT_ABBREVIATED.getName()));
}
static void setPreferredModuleTreeDisplayMode(TreeViewDisplayMode preferredModuleTreeDisplayMode) {
getPreferences().put(BROWSER_MODULE_TREE_DISPLAY_MODE, preferredModuleTreeDisplayMode.getName());
}
}