/*
* Open Source Physics software is free software as described near the bottom of this code file.
*
* For additional information and documentation on Open Source Physics please see:
* <http://www.opensourcephysics.org/>
*/
package org.opensourcephysics.tools;
import java.awt.*;
import java.awt.event.*;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.File;
import java.io.FileFilter;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;
import java.util.regex.Pattern;
import javax.swing.*;
import javax.swing.border.Border;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.tree.TreePath;
import org.opensourcephysics.controls.OSPLog;
import org.opensourcephysics.controls.XML;
import org.opensourcephysics.controls.XMLControl;
import org.opensourcephysics.controls.XMLControlElement;
import org.opensourcephysics.desktop.OSPDesktop;
import org.opensourcephysics.display.GUIUtils;
import org.opensourcephysics.display.OSPRuntime;
import org.opensourcephysics.display.TextFrame;
import org.opensourcephysics.media.core.VideoIO;
import org.opensourcephysics.tools.LibraryCollection;
import org.opensourcephysics.tools.LibraryTreePanel;
import org.opensourcephysics.tools.LibraryResource.Metadata;
/**
* A GUI for browsing OSP digital library collections.
*
* @author Douglas Brown
*/
public class LibraryBrowser extends JPanel {
// static constants
@SuppressWarnings("javadoc")
public static final String TRACKER_LIBRARY = "http://physlets.org/tracker/library/tracker_library.xml"; //$NON-NLS-1$
@SuppressWarnings("javadoc")
public static final String SHARED_LIBRARY = "http://physlets.org/tracker/library/shared_library.xml"; //$NON-NLS-1$
protected static final String AND = " AND "; //$NON-NLS-1$
protected static final String OR = " OR "; //$NON-NLS-1$
protected static final String OPENING = "("; //$NON-NLS-1$
protected static final String CLOSING = ")"; //$NON-NLS-1$
protected static final String MY_LIBRARY_NAME = "my_library.xml"; //$NON-NLS-1$
protected static final String MY_COLLECTION_NAME = "my_collection.xml"; //$NON-NLS-1$
protected static final String LIBRARY_HELP_NAME = "library_browser_help.html"; //$NON-NLS-1$
protected static final String LIBRARY_HELP_BASE = "http://www.opensourcephysics.org/online_help/tools/"; //$NON-NLS-1$
protected static final String WINDOWS_OSP_DIRECTORY = "/My Documents/OSP/"; //$NON-NLS-1$
protected static final String OSP_DIRECTORY = "/Documents/OSP/"; //$NON-NLS-1$
// static fields
private static LibraryBrowser browser;
protected static Border buttonBorder;
protected static boolean webConnected;
protected static JFrame frame;
protected static JDialog externalDialog;
protected static JMenuBar menubar;
protected static Icon expandIcon, contractIcon, heavyExpandIcon, heavyContractIcon, refreshIcon;
protected static final FileFilter TRACKER_FILTER = new TrackerDLFilter();
protected static javax.swing.filechooser.FileFilter filesAndFoldersFilter = new FilesAndFoldersFilter();
protected static Timer searchTimer;
protected static String searchTerm;
public static boolean fireHelpEvent = false;
static {
buttonBorder = BorderFactory.createEtchedBorder();
Border space = BorderFactory.createEmptyBorder(1,2,2,2);
buttonBorder = BorderFactory.createCompoundBorder(buttonBorder, space);
space = BorderFactory.createEmptyBorder(0,1,0,1);
buttonBorder = BorderFactory.createCompoundBorder(space, buttonBorder);
menubar = new JMenuBar();
String imageFile = "/org/opensourcephysics/resources/tools/images/expand.png"; //$NON-NLS-1$
expandIcon = new ImageIcon(LibraryTreePanel.class.getResource(imageFile));
imageFile = "/org/opensourcephysics/resources/tools/images/contract.png"; //$NON-NLS-1$
contractIcon = new ImageIcon(LibraryTreePanel.class.getResource(imageFile));
imageFile = "/org/opensourcephysics/resources/tools/images/expand_bold.png"; //$NON-NLS-1$
heavyExpandIcon = new ImageIcon(LibraryTreePanel.class.getResource(imageFile));
imageFile = "/org/opensourcephysics/resources/tools/images/contract_bold.png"; //$NON-NLS-1$
heavyContractIcon = new ImageIcon(LibraryTreePanel.class.getResource(imageFile));
imageFile = "/org/opensourcephysics/resources/tools/images/refresh.gif"; //$NON-NLS-1$
refreshIcon = new ImageIcon(LibraryTreePanel.class.getResource(imageFile));
}
// instance fields
protected Library library = new Library();
protected String libraryPath;
protected JToolBar toolbar;
protected Action commandAction, searchAction, openRecentAction;
protected JLabel commandLabel, searchLabel;
protected JTextField commandField, searchField;
protected JMenu fileMenu, recentMenu, collectionsMenu, manageMenu, helpMenu;
protected JMenuItem newItem, openItem, saveItem, saveAsItem, closeItem, closeAllItem,
exitItem, deleteItem, collectionsItem, searchItem, cacheItem, aboutItem, logItem, helpItem;
protected JButton commandButton, editButton, refreshButton;
protected ActionListener loadCollectionAction;
protected boolean exitOnClose;
protected JTabbedPane tabbedPane;
protected JScrollPane htmlScroller;
protected PropertyChangeListener treePanelListener;
protected boolean keyPressed, textChanged;
protected TextFrame helpFrame;
protected JEditorPane htmlAboutPane;
protected FileFilter dlFileFilter = TRACKER_FILTER;
protected boolean isRecentPathXML;
protected LibraryManager libraryManager;
/**
* Gets the shared singleton browser.
*
* @return the shared LibraryBrowser
*/
public static LibraryBrowser getBrowser() {
if (browser==null) {
browser = getBrowser(null);
}
return browser;
}
/**
* Gets the shared singleton browser in a JDialog or, if none, in a shared JFrame.
*
* @param dialog a JDialog (if null, browser is returned in a JFrame)
* @return the shared LibraryBrowser
*/
public static LibraryBrowser getBrowser(JDialog dialog) {
boolean newFrame = false;
if (frame==null && dialog==null) {
newFrame = true;
frame = new JFrame();
}
externalDialog = dialog;
if (externalDialog!=null)
externalDialog.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
if (browser==null) {
String userHome = System.getProperty("user.home").replace('\\', '/'); //$NON-NLS-1$
String ospFolder = OSPRuntime.isWindows()? WINDOWS_OSP_DIRECTORY: OSP_DIRECTORY;
String ospPath = userHome+ospFolder;
// if OSP folder doesn't exist in user home, then look
// in default OSPRuntime search directory
if (!new File(ospPath).exists()) {
ArrayList<String> dirs = OSPRuntime.getDefaultSearchPaths();
ospPath = XML.forwardSlash(dirs.get(0));
}
if (!ospPath.endsWith("/")) { //$NON-NLS-1$
ospPath += "/"; //$NON-NLS-1$
}
String libraryPath = ospPath+MY_LIBRARY_NAME;
File libraryFile = new File(libraryPath);
// create new library if none exists
boolean libraryExists = libraryFile.exists();
if (!libraryExists) {
String collectionPath = ospPath+MY_COLLECTION_NAME;
File collectionFile = new File(collectionPath);
// create new collection if none exists
if (!collectionFile.exists()) {
String name = ToolsRes.getString("LibraryCollection.Name.Local"); //$NON-NLS-1$
LibraryCollection collection = new LibraryCollection(name);
String base = XML.getDirectoryPath(collectionPath);
collection.setBasePath(XML.forwardSlash(base));
// save new collection
XMLControl control = new XMLControlElement(collection);
control.write(collectionPath);
}
Library library = new Library();
String name = ToolsRes.getString("LibraryCollection.Name.Local"); //$NON-NLS-1$
library.addCollection(collectionPath, name);
library.save(libraryPath);
}
browser = new LibraryBrowser(libraryPath);
LibraryTreePanel treePanel = browser.getSelectedTreePanel();
if (treePanel!=null) {
treePanel.setSelectedNode(treePanel.rootNode);
treePanel.showInfo(treePanel.rootNode);
}
OSPLog.getOSPLog(); // instantiate log in case of exceptions, etc
}
browser.setTitle(ToolsRes.getString("LibraryBrowser.Title")); //$NON-NLS-1$
if (externalDialog!=null) {
externalDialog.setContentPane(browser);
externalDialog.setJMenuBar(menubar);
externalDialog.addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent e) {
browser.exit();
}
});
externalDialog.pack();
}
else {
frame.setContentPane(browser);
frame.setJMenuBar(menubar);
frame.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
// add window listener to exit
frame.addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent e) {
browser.exit();
}
});
try {
java.net.URL url = LibraryBrowser.class.getResource(OSPRuntime.OSP_ICON_FILE);
ImageIcon icon = new ImageIcon(url);
frame.setIconImage(icon.getImage());
} catch(Exception ex) {}
frame.pack();
if (newFrame) {
// center on screen
Dimension dim = Toolkit.getDefaultToolkit().getScreenSize();
int x = (dim.width - frame.getBounds().width) / 2;
int y = (dim.height - frame.getBounds().height) / 2;
frame.setLocation(x, y);
}
}
return browser;
}
/**
* Sets the font level.
*
* @param level the desired font level
*/
public void setFontLevel(int level) {
FontSizer.setFonts(this.getTopLevelAncestor(), level);
Font font = tabbedPane.getFont();
tabbedPane.setFont(FontSizer.getResizedFont(font, level));
for (int i=0; i<tabbedPane.getTabCount(); i++) {
LibraryTreePanel treePanel = getTreePanel(i);
treePanel.setFontLevel(level);
}
if (libraryManager!=null) {
libraryManager.setFontLevel(level);
}
FontSizer.setFonts(OSPLog.getOSPLog(), level);
}
/**
* Imports a library with a specified path.
*
* @param path the path to the Library xml file
*/
public void importLibrary(final String path) {
Runnable runner = new Runnable() {
public void run() {
library.importLibrary(path);
refreshCollectionsMenu();
}
};
new Thread(runner).start();
}
/**
* Adds an OSP-sponsored library with a specified path.
*
* @param path the path to the Library xml file
*/
public void addOSPLibrary(final String path) {
Runnable runner = new Runnable() {
public void run() {
library.addOSPLibrary(path);
refreshCollectionsMenu();
}
};
new Thread(runner).start();
}
/**
* Adds a ComPADRE collection with a specified path.
*
* @param path the ComPADRE query
*/
public void addComPADRECollection(String path) {
library.addComPADRECollection(path, LibraryComPADRE.getCollectionName(path));
}
/**
* Refreshes the Collections menu.
*/
synchronized public void refreshCollectionsMenu() {
JMenu menu = collectionsMenu;
menu.removeAll();
JMenu myLibraryMenu = new JMenu(ToolsRes.getString("Library.Name.Local")); //$NON-NLS-1$
menu.add(myLibraryMenu);
if (!library.pathList.isEmpty()) {
for (String path: library.pathList) {
String name = library.pathToNameMap.get(path);
JMenuItem item = new JMenuItem(name);
myLibraryMenu.add(item);
item.addActionListener(loadCollectionAction);
item.setToolTipText(path);
item.setActionCommand(path);
}
}
if (!library.comPADREPathList.isEmpty()) {
JMenu submenu = new JMenu(ToolsRes.getString("Library.Name.ComPADRE")); //$NON-NLS-1$
menu.add(submenu);
for (String path: library.comPADREPathList) {
String name = library.comPADREPathToNameMap.get(path);
JMenuItem item = new JMenuItem(name);
submenu.add(item);
item.addActionListener(loadCollectionAction);
// if (LibraryComPADRE.primary_only)
// path += LibraryComPADRE.PRIMARY_ONLY;
item.setToolTipText(path);
item.setActionCommand(path);
}
}
if (!library.ospPathList.isEmpty()) {
for (String path: library.ospPathList) {
Library lib = library.ospPathToLibraryMap.get(path);
JMenu submenu = new JMenu(lib.getName());
menu.add(submenu);
populateSubMenu(submenu, lib);
}
}
if (!library.importedPathList.isEmpty()) {
menu.addSeparator();
for (String path: library.importedPathList) {
Library lib = library.importedPathToLibraryMap.get(path);
JMenu submenu = new JMenu(lib.getName());
menu.add(submenu);
for (String next: lib.pathList) {
String name = lib.pathToNameMap.get(next);
JMenuItem item = new JMenuItem(name);
submenu.add(item);
item.addActionListener(loadCollectionAction);
item.setToolTipText(next);
item.setActionCommand(next);
}
}
}
FontSizer.setFonts(collectionsMenu, FontSizer.getLevel());
}
/**
* Populates a submenu.
*
* @param menu the menu to populate
* @param lib the library with collections for the submenu
*/
private void populateSubMenu(JMenu menu, Library lib) {
for (String next: lib.pathList) {
String name = lib.pathToNameMap.get(next);
JMenuItem item = new JMenuItem(name);
menu.add(item);
item.addActionListener(loadCollectionAction);
item.setToolTipText(next);
item.setActionCommand(next);
}
if (!lib.subPathList.isEmpty()) {
for (String path: lib.subPathList) {
if (library.ospPathList.contains(path))
continue;
Library sublib = lib.subPathToLibraryMap.get(path);
JMenu submenu = new JMenu(sublib.getName());
menu.add(submenu);
populateSubMenu(submenu, sublib);
}
}
}
/**
* Sets the title of this DL browser.
* @param title the title
*/
public void setTitle(String title) {
if (frame!=null) {
frame.setTitle(title);
}
else if (externalDialog!=null) {
externalDialog.setTitle(title);
}
}
/**
* Gets the fileFilter used to determine which files are DL resources.
*
* @return the file filter
*/
public FileFilter getDLFileFilter() {
return dlFileFilter;
}
/**
* Sets the fileFilter used to determine which files are DL resources.
* @param filter the file filter (may be null)
*/
public void setDLFileFilter(FileFilter filter) {
dlFileFilter = filter;
}
/**
* Sets the visibility of this browser
* @param vis true to show, false to hide
*/
@Override
public void setVisible(boolean vis) {
super.setVisible(vis);
if (externalDialog!=null) {
externalDialog.setVisible(vis);
}
else frame.setVisible(vis);
}
/**
* Exits this browser.
* @return true if exited, false if cancelled by user
*/
public boolean exit() {
// request focus?
LibraryTreePanel selected = getSelectedTreePanel();
if (selected!=null)
selected.refreshEntryFields();
for (int i=0; i < tabbedPane.getTabCount(); i++) {
LibraryTreePanel treePanel = getTreePanel(i);
if (!treePanel.saveChanges(getTabTitle(i))) return false; // true unless the user cancels
}
// determine which open tabs to save
ArrayList<String> tabsToSave = new ArrayList<String>();
int n = tabbedPane.getTabCount();
for (int i=0; i<n; i++) {
String path = getTreePanel(i).pathToRoot;
if (path.equals("")) continue; //$NON-NLS-1$
tabsToSave.add(path);
}
library.openTabPaths = tabsToSave.isEmpty()? null: tabsToSave.toArray(new String[tabsToSave.size()]);
// save library
library.save(libraryPath);
if (exitOnClose) {
System.exit(0);
} else {
refreshGUI();
setVisible(false);
}
return true;
}
//____________________ private and protected methods ____________________________
/**
* Private constructor to prevent instantiation except for singleton.
*
* @param libraryPath the path to a Library xml file
*/
private LibraryBrowser(String libraryPath) {
super(new BorderLayout());
this.libraryPath = libraryPath;
library.browser = this;
createGUI();
refreshGUI();
refreshCollectionsMenu();
editButton.requestFocusInWindow();
ToolsRes.addPropertyChangeListener("locale", new PropertyChangeListener() { //$NON-NLS-1$
public void propertyChange(PropertyChangeEvent e) {
refreshGUI();
refreshCollectionsMenu();
if (libraryManager!=null)
libraryManager.refreshGUI();
LibraryTreePanel.htmlPanesByNode.clear();
LibraryTreePanel treePanel = getSelectedTreePanel();
if (treePanel!=null)
treePanel.showInfo(treePanel.getSelectedNode());
}
});
}
/**
* Gets the library manager for this browser.
*
* @return the collections manager
*/
protected LibraryManager getManager() {
if (libraryManager==null) {
if (externalDialog!=null)
libraryManager = new LibraryManager(this, LibraryBrowser.externalDialog);
else
libraryManager = new LibraryManager(this, LibraryBrowser.frame);
// center on screen
Dimension dim = Toolkit.getDefaultToolkit().getScreenSize();
int x = (dim.width-libraryManager.getBounds().width)/2;
int y = (dim.height-libraryManager.getBounds().height)/2;
libraryManager.setLocation(x, y);
}
if (library.pathList.size()>0 && libraryManager.collectionList.getSelectedIndex()==-1) {
libraryManager.collectionList.setSelectedIndex(0);
}
if (library.importedPathList.size()>0 && libraryManager.guestList.getSelectedIndex()==-1) {
libraryManager.guestList.setSelectedIndex(0);
}
libraryManager.setFontLevel(FontSizer.getLevel());
return libraryManager;
}
/**
* Gets the selected LibraryTreePanel, if any.
*
* @return the selected treePanel, or null if none
*/
protected LibraryTreePanel getSelectedTreePanel() {
return (LibraryTreePanel)tabbedPane.getSelectedComponent();
}
/**
* Gets the LibraryTreePanel at a specified tab index.
*
* @param index the tab index
* @return the treePanel
*/
protected LibraryTreePanel getTreePanel(int index) {
return (LibraryTreePanel)tabbedPane.getComponentAt(index);
}
/**
* Gets the title of the tab associated with a given path.
*
* @param path the collection path
* @return the tab title
*/
protected String getTabTitle(String path) {
int i = getTabIndexFromPath(path);
return i>-1? getTabTitle(i): null;
}
/**
* Gets the title of the tab at a given index.
*
* @param index the tab index
* @return the tab title
*/
protected String getTabTitle(int index) {
String title = tabbedPane.getTitleAt(index);
if (title.endsWith("*")) //$NON-NLS-1$
title = title.substring(0, title.length()-1);
return title;
}
/**
* Gets the index of the tab associated with a given path.
*
* @param path the collection path
* @return the tab index
*/
protected int getTabIndexFromPath(String path) {
for (int i=0; i<tabbedPane.getTabCount(); i++) {
LibraryTreePanel next = getTreePanel(i);
if (next.pathToRoot.equals(path))
return i;
}
return -1;
}
/**
* Gets the index of the tab associated with a given title.
*
* @param title the tab title
* @return the tab index
*/
protected int getTabIndexFromTitle(String title) {
for (int i=0; i<tabbedPane.getTabCount(); i++) {
String next = tabbedPane.getTitleAt(i);
if (next.equals(title))
return i;
}
return -1;
}
/**
* Loads a tab from a given path. If the tab is already loaded, this selects it.
* if not yet loaded, this adds a new tab and selects it.
* If a treePath is specified, the node it points to will be selected
*
* @param path the path
* @param treePath tree path to select in root-first order (may be null)
*/
protected void loadTab(String path, List<String> treePath) {
path = XML.forwardSlash(path);
library.addRecent(path, false);
refreshRecentMenu();
// select tab and treePath if path is already loaded
int i = getTabIndexFromPath(path);
if (i>-1) {
tabbedPane.setSelectedIndex(i);
LibraryTreePanel treePanel = getTreePanel(i);
treePanel.setSelectionPath(treePath);
return;
}
// otherwise add new tab
TabLoader tabAdder = addTab(path, treePath);
if (tabAdder==null) return;
tabAdder.addPropertyChangeListener(new PropertyChangeListener() {
public void propertyChange(PropertyChangeEvent e) {
if ("progress".equals(e.getPropertyName())) { //$NON-NLS-1$
Integer n = (Integer)e.getNewValue();
if (n>-1) {
tabbedPane.setSelectedIndex(n);
}
}
}
});
tabAdder.execute();
}
/**
* Loads a library resource from a given path.
*
* @param path the path
* @return the resource, or null if failed
*/
protected LibraryResource loadResource(String path) {
isRecentPathXML = false;
File targetFile = new File(path);
if (targetFile.isDirectory()) {
return createCollection(targetFile, targetFile, dlFileFilter);
}
if (LibraryComPADRE.isComPADREPath(path)) {
return LibraryComPADRE.getCollection(path);
}
XMLControlElement control = new XMLControlElement(path);
if (!control.failedToRead()
&& control.getObjectClass()!=null
&& LibraryResource.class.isAssignableFrom(control.getObjectClass())) {
isRecentPathXML = true;
return (LibraryResource)control.loadObject(null);
}
return createResource(targetFile, targetFile.getParentFile(), dlFileFilter);
}
/**
* Creates a LibraryResource that describes and targets a file.
* @param targetFile the target file
* @param baseDir the base directory for relative paths
* @param filter a FileFilter to determine if the file is a DL library resource
* @return a LibraryResource that describes and targets the file
*/
protected LibraryResource createResource(File targetFile, File baseDir, FileFilter filter) {
if (targetFile==null || !targetFile.exists()) return null;
if (!filter.accept(targetFile)) return null;
String fileName = targetFile.getName();
String path = XML.forwardSlash(targetFile.getAbsolutePath());
String base = XML.forwardSlash(baseDir.getAbsolutePath());
String relPath = XML.getPathRelativeTo(path, base);
LibraryResource record = new LibraryResource(fileName);
record.setBasePath(base);
record.setTarget(relPath);
fileName = fileName.toLowerCase();
if (fileName.indexOf(".htm")>-1) { //$NON-NLS-1$
record.setHTMLPath(relPath);
record.setType(LibraryResource.HTML_TYPE);
}
if (fileName.endsWith(".zip")) { //$NON-NLS-1$
if (filter==TRACKER_FILTER) {
record.setType(LibraryResource.TRACKER_TYPE);
}
}
if (fileName.endsWith(".trz")) { //$NON-NLS-1$
record.setType(LibraryResource.TRACKER_TYPE);
}
return record;
}
/**
* Creates a LibraryCollection containing all DL resources in a target directory.
* @param targetDir the target directory
* @param base the base directory for relative paths
* @param filter a FileFilter to determine which files are DL resources
* @return the collection
*/
protected LibraryCollection createCollection(File targetDir, File base, FileFilter filter) {
// find HTML files in this folder
FileFilter htmlFilter = new HTMLFilter();
File[] htmlFiles = targetDir.listFiles(htmlFilter);
HashSet<File> matchedNames = new HashSet<File>();
String name = targetDir.getName();
LibraryCollection collection = new LibraryCollection(name);
if (base==targetDir) { // set base path ONLY for the root directory
collection.setBasePath(XML.forwardSlash(base.getAbsolutePath()));
}
// look for HTML with name = folder name + "_info"
for (File htmlFile: htmlFiles) {
if (XML.stripExtension(htmlFile.getName()).equals(name+"_info")) { //$NON-NLS-1$
String relPath = XML.getPathRelativeTo(htmlFile.getAbsolutePath(), base.getAbsolutePath());
collection.setHTMLPath(relPath);
String htmlCode = ResourceLoader.getHTMLCode(htmlFile.getAbsolutePath());
String title = ResourceLoader.getTitleFromHTMLCode(htmlCode);
if (title!=null) {
collection.setName(title);
}
matchedNames.add(htmlFile);
}
}
// find subfolders
File[] subdirs = targetDir.listFiles(new DirectoryFilter());
for (File dir: subdirs) {
LibraryCollection subCollection = createCollection(dir, base, filter);
if (subCollection.getResources().length>0)
collection.addResource(subCollection);
}
// find filtered DL resources
File[] resourceFiles = filter==null? targetDir.listFiles(): targetDir.listFiles(filter);
for (File next: resourceFiles) {
if (htmlFilter.accept(next)) continue;
String relPath = XML.getPathRelativeTo(next.getAbsolutePath(), base.getAbsolutePath());
String fileName = next.getName();
String baseName = XML.stripExtension(fileName);
LibraryResource record = new LibraryResource(fileName);
collection.addResource(record);
record.setTarget(relPath);
// assign resource type to zip files
if (fileName.toLowerCase().endsWith(".zip")) { //$NON-NLS-1$
if (filter==TRACKER_FILTER) {
record.setType(LibraryResource.TRACKER_TYPE);
}
}
if (fileName.toLowerCase().endsWith(".trz")) { //$NON-NLS-1$
record.setType(LibraryResource.TRACKER_TYPE);
}
// look for HTML with base name + "_info"
for (File htmlFile: htmlFiles) {
String htmlName = XML.stripExtension(htmlFile.getName());
if (htmlName.equals(baseName+"_info")) { //$NON-NLS-1$
if ("".equals(record.getHTMLPath())) { //$NON-NLS-1$
relPath = XML.getPathRelativeTo(htmlFile.getAbsolutePath(), base.getAbsolutePath());
record.setHTMLPath(relPath);
String htmlCode = ResourceLoader.getHTMLCode(htmlFile.getAbsolutePath());
String title = ResourceLoader.getTitleFromHTMLCode(htmlCode);
if (title!=null) {
record.setName(title);
}
}
matchedNames.add(htmlFile);
break;
}
}
}
// insert unmatched HTML files at top of the collection
int i = 0;
for (File html: htmlFiles) {
if (matchedNames.contains(html) || !filter.accept(html)) continue;
String fileName = html.getName();
LibraryResource record = new LibraryResource(fileName);
String relPath = XML.getPathRelativeTo(html.getAbsolutePath(), base.getAbsolutePath());
record.setHTMLPath(relPath);
record.setType(LibraryResource.HTML_TYPE);
collection.insertResource(record, i++);
}
return collection;
}
/**
* Adds a tab displaying a library resource with a given path.
* If a treePath is specified, the node it points to will be selected
*
* @param path the path to the resource
* @param treePath tree path to select in root-first order (may be null)
* @return the TabLoader that adds the tab
*/
protected TabLoader addTab(String path, List<String> treePath) {
if (path==null) return null;
File cachedFile = ResourceLoader.getSearchCacheFile(path);
boolean isCachePath = cachedFile.exists();
if (!isCachePath && !isWebConnected() && path.startsWith("http:")) { //$NON-NLS-1$
JOptionPane.showMessageDialog(this,
ToolsRes.getString("LibraryBrowser.Dialog.ServerUnavailable.Message"), //$NON-NLS-1$
ToolsRes.getString("LibraryBrowser.Dialog.ServerUnavailable.Title"), //$NON-NLS-1$
JOptionPane.WARNING_MESSAGE);
return null;
}
TabLoader tabAdder = new TabLoader(path, -1, treePath);
return tabAdder;
}
/**
* Refreshes the title of a tab based on the properties of a LibraryCollection
* and the path associated with that collection.
*
* @param path the collection path
* @param collection the LibraryCollection itself
*/
protected void refreshTabTitle(String path, LibraryResource collection) {
int n = getTabIndexFromPath(path);
if (n==-1) return;
String title = collection.getTitle(path);
// add a TabTitle with expand and contract icons to ComPADRE tab
if (path.contains(LibraryComPADRE.TRACKER_SERVER_TREE) && tabbedPane.getTabComponentAt(n)==null) {
boolean primary = path.contains("OSPPrimary"); //$NON-NLS-1$
Icon icon = primary? expandIcon: contractIcon;
Icon heavyIcon = primary? heavyExpandIcon: heavyContractIcon;
final TabTitle tabTitle = new TabTitle(icon, heavyIcon);
FontSizer.setFonts(tabTitle, FontSizer.getLevel());
tabTitle.iconLabel.setToolTipText(primary? ToolsRes.getString("LibraryBrowser.Tooltip.Expand"): //$NON-NLS-1$
ToolsRes.getString("LibraryBrowser.Tooltip.Contract")); //$NON-NLS-1$
Action action = new AbstractAction() {
public void actionPerformed(ActionEvent e) {
boolean primaryOnly = tabTitle.normalIcon==contractIcon;
int index = getTabIndexFromTitle(tabTitle.titleLabel.getText());
if (index>-1) {
LibraryTreePanel treePanel = getTreePanel(index);
String path = LibraryComPADRE.getCollectionPath(treePanel.pathToRoot, primaryOnly);
new TabLoader(path, index, null).execute();
tabTitle.setIcons(primaryOnly? expandIcon: contractIcon, primaryOnly? heavyExpandIcon: heavyContractIcon);
tabTitle.iconLabel.setToolTipText(primaryOnly? ToolsRes.getString("LibraryBrowser.Tooltip.Expand"): //$NON-NLS-1$
ToolsRes.getString("LibraryBrowser.Tooltip.Contract")); //$NON-NLS-1$
}
}
};
tabTitle.setAction(action);
tabbedPane.setTabComponentAt(n, tabTitle);
}
boolean changed = getTreePanel(n).isChanged();
tabbedPane.setTitleAt(n, changed? title+"*": title); //$NON-NLS-1$
library.getNameMap().put(path, title);
if (n==tabbedPane.getSelectedIndex()) {
String tabname = " '"+title+"'"; //$NON-NLS-1$ //$NON-NLS-2$
closeItem.setText(ToolsRes.getString("LibraryBrowser.MenuItem.CloseTab")+tabname); //$NON-NLS-1$
}
}
/**
* Creates the visible components of this panel.
*/
protected void createGUI() {
double factor = 1+ FontSizer.getLevel()*0.25;
int w = (int)(factor*800);
int h = (int)(factor*440);
setPreferredSize(new Dimension(w, h));
loadCollectionAction = new ActionListener() {
public void actionPerformed(ActionEvent e) {
loadTab(e.getActionCommand(), null);
}
};
// create command action, label, field and button
commandAction = new AbstractAction() {
public void actionPerformed(ActionEvent e) {
if (e==null) return;
commandField.setBackground(Color.white);
commandField.setForeground(LibraryTreePanel.defaultForeground);
if (!commandButton.isEnabled()) return;
String path = commandField.getText().trim();
if (path.equals("")) return; //$NON-NLS-1$
path = XML.forwardSlash(path);
path = ResourceLoader.getNonURIPath(path);
Resource res = null;
String xmlPath = path;
// if path has no extension, look for xml file with same name
if (!path.startsWith("http://www.compadre.org/OSP/") //$NON-NLS-1$
&& XML.getExtension(path)==null) {
while (xmlPath.endsWith("/")) //$NON-NLS-1$
xmlPath = xmlPath.substring(0, xmlPath.length()-1);
if (!xmlPath.equals("")) { //$NON-NLS-1$
String name = XML.getName(xmlPath);
xmlPath += "/"+name+".xml"; //$NON-NLS-1$ //$NON-NLS-2$
res = ResourceLoader.getResource(xmlPath);
}
}
if (res!=null)
path = xmlPath;
else
res = ResourceLoader.getResourceZipURLsOK(path);
if (res==null) {
commandField.setForeground(LibraryTreePanel.darkRed);
return;
}
boolean isCollection = res.getFile()!=null && res.getFile().isDirectory();
if (!isCollection) {
XMLControl control = new XMLControlElement(path);
isCollection = !control.failedToRead() && control.getObjectClass()==LibraryCollection.class;
}
if (isCollection) {
loadTab(path, null);
refreshGUI();
LibraryTreePanel treePanel = getSelectedTreePanel();
if (treePanel!=null && treePanel.pathToRoot.equals(path)) {
treePanel.setSelectedNode(treePanel.rootNode);
commandField.setBackground(Color.white);
commandField.repaint();
}
return;
}
// send command
LibraryResource record = null;
LibraryTreePanel treePanel = getSelectedTreePanel();
if (treePanel!=null && treePanel.getSelectedNode()!=null) {
record = treePanel.getSelectedNode().record.getClone();
record.setBasePath(treePanel.getSelectedNode().getBasePath());
}
else {
record = new LibraryResource(""); //$NON-NLS-1$
record.setTarget(path);
}
LibraryBrowser.this.firePropertyChange("target", null, record); //$NON-NLS-1$
}
};
commandLabel = new JLabel();
commandLabel.setAlignmentX(CENTER_ALIGNMENT);
commandLabel.setBorder(BorderFactory.createEmptyBorder(0, 3, 0, 2));
commandField = new JTextField() {
public Dimension getPreferredSize() {
Dimension dim = super.getPreferredSize();
dim.width = Math.max(dim.width, 400);
return dim;
}
};
LibraryTreePanel.defaultForeground = commandField.getForeground();
commandField.addActionListener(commandAction);
commandField.getDocument().addDocumentListener(new DocumentListener() {
public void insertUpdate(DocumentEvent e) {
String text = commandField.getText();
commandButton.setEnabled(!"".equals(text)); //$NON-NLS-1$
textChanged = keyPressed;
LibraryTreePanel treePanel = getSelectedTreePanel();
if (treePanel!=null) {
treePanel.command = text;
LibraryTreeNode node = treePanel.getSelectedNode();
if (node!=null && node.isRoot() && node.record instanceof LibraryCollection && treePanel.pathToRoot.equals(text))
commandButton.setEnabled(false);
}
else {
commandField.setBackground(Color.yellow);
commandField.setForeground(LibraryTreePanel.defaultForeground);
}
}
public void removeUpdate(DocumentEvent e) {
commandButton.setEnabled(!"".equals(commandField.getText())); //$NON-NLS-1$
textChanged = keyPressed;
LibraryTreePanel treePanel = getSelectedTreePanel();
if (treePanel!=null) {
treePanel.command = commandField.getText();
}
else {
commandField.setBackground(Color.yellow);
commandField.setForeground(LibraryTreePanel.defaultForeground);
}
}
public void changedUpdate(DocumentEvent e) {}
});
commandField.addKeyListener(new KeyAdapter() {
public void keyPressed(KeyEvent e) {
keyPressed = true;
}
public void keyReleased(KeyEvent e) {
LibraryTreePanel treePanel = getSelectedTreePanel();
if (treePanel!=null && textChanged && e.getKeyCode()!=KeyEvent.VK_ENTER) {
commandField.setBackground(Color.yellow);
commandField.setForeground(LibraryTreePanel.defaultForeground);
treePanel.setSelectedNode(null);
}
textChanged = keyPressed = false;
}
});
commandField.addFocusListener(new FocusAdapter() {
public void focusGained(FocusEvent e) {
commandField.selectAll();
}
});
commandButton = new JButton(commandAction);
commandButton.setOpaque(false);
commandButton.setBorder(buttonBorder);
// create search action, label, field and button
searchAction = new AbstractAction() {
public void actionPerformed(ActionEvent e) {
searchTerm = searchField.getText();
if ("".equals(searchTerm.trim())) return; //$NON-NLS-1$
searchField.selectAll();
searchField.setBackground(Color.white);
// do actual search in separate swingworker thread
class Searcher extends SwingWorker<LibraryTreePanel, Object> {
@Override
public LibraryTreePanel doInBackground() {
// search all cache targets except those in the library no_search set
Set<LibraryResource> searchTargets = getSearchCacheTargets();
for (Iterator<LibraryResource> it = searchTargets.iterator(); it.hasNext();) {
LibraryResource next = it.next();
if (library.noSearchSet.contains(next.collectionPath))
it.remove();
}
return searchFor(searchTerm.trim(), searchTargets);
}
@Override
protected void done() {
try {
LibraryTreePanel results = get();
if (results==null) {
Toolkit.getDefaultToolkit().beep();
// give visual cue, too
final Color color = searchField.getForeground();
searchField.setText(ToolsRes.getString("LibraryBrowser.Search.NotFound")); //$NON-NLS-1$
searchField.setForeground(Color.RED);
searchField.setBackground(Color.white);
if (searchTimer==null) {
searchTimer = new Timer(1000, new ActionListener() {
public void actionPerformed(ActionEvent e) {
searchField.setText(searchTerm);
searchField.setForeground(color);
searchField.selectAll();
searchField.setBackground(Color.white);
}
});
searchTimer.setRepeats(false);
searchTimer.start();
}
else {
searchTimer.restart();
}
return;
}
String title = "'"+searchTerm.trim()+"'"; //$NON-NLS-1$ //$NON-NLS-2$
int i = getTabIndexFromTitle(title);
synchronized (tabbedPane) {
if (i > -1) {
// replace existing tab
tabbedPane.setComponentAt(i, results);
}
else {
tabbedPane.addTab(title, results);
}
tabbedPane.setSelectedComponent(results);
}
LibraryTreePanel.htmlPanesByNode.remove(results.rootNode);
results.showInfo(results.rootNode);
refreshGUI();
} catch (Exception e) {
Toolkit.getDefaultToolkit().beep();
}
}
}
new Searcher().execute();
}
};
searchLabel = new JLabel();
searchLabel.setAlignmentX(CENTER_ALIGNMENT);
searchLabel.setBorder(BorderFactory.createEmptyBorder(0, 3, 0, 2));
searchField = new LibraryTreePanel.EntryField() {
public Dimension getMaximumSize() {
Dimension dim = super.getMaximumSize();
dim.width = (int)(120*(1+FontSizer.getLevel()*0.25));
return dim;
}
public Dimension getPreferredSize() {
Dimension dim = super.getPreferredSize();
dim.width = (int)(120*(1+FontSizer.getLevel()*0.25));
return dim;
}
};
searchField.addActionListener(searchAction);
refreshButton = new JButton(refreshIcon);
refreshButton.setBorder(buttonBorder);
refreshButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
LibraryTreePanel treePanel = getSelectedTreePanel();
if (treePanel!=null) {
LibraryTreeNode node = treePanel.getSelectedNode();
// if node is root, delete the cache file, if any, and reload the entire collection
if (node==treePanel.rootNode) {
File cachedFile = ResourceLoader.getSearchCacheFile(treePanel.pathToRoot);
if (cachedFile.exists()) {
cachedFile.delete();
}
// reload the root resource or directory
LibraryResource resource = loadResource(treePanel.pathToRoot);
if (resource!=null) {
treePanel.setRootResource(resource, treePanel.pathToRoot, treePanel.rootNode.isEditable(), false);
refreshTabTitle(treePanel.pathToRoot, treePanel.rootResource);
// start background SwingWorker to load metadata and set up search database
if (treePanel.metadataLoader!=null) {
treePanel.metadataLoader.cancel();
}
treePanel.metadataLoader = treePanel.new MetadataLoader(true, null);
treePanel.metadataLoader.execute();
return;
}
}
// for other nodes delete cached files and reload the node
else if (node!=null) {
LibraryTreePanel.HTMLPane pane = new LibraryTreePanel.HTMLPane();
pane.setText("<h2>"+ToolsRes.getString("LibraryBrowser.Info.Refreshing")+" '"+node+"'</h2>"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
treePanel.htmlScroller.setViewportView(pane);
URL url = node.getHTMLURL(); // returns cached file URL, if any
if (url!=null) {
File cachedFile = ResourceLoader.getOSPCacheFile(url.toExternalForm());
if (cachedFile.exists()) {
cachedFile.delete();
}
LibraryTreePanel.htmlPanesByURL.remove(url);
}
// delete thumbnail image, if any
String target = node.getAbsoluteTarget();
if (target!=null) {
File thumb = node.getThumbnailFile();
if (thumb.exists()) {
thumb.delete();
node.record.setThumbnail(null);
}
}
// clear metadata and description
node.record.setMetadata(null);
node.record.setDescription(null);
treePanel.new NodeLoader(node).execute();
}
}
}
});
tabbedPane = new JTabbedPane(SwingConstants.TOP) {
@Override
public void setTitleAt(int i, String title) {
super.setTitleAt(i, title);
Component c = tabbedPane.getTabComponentAt(i);
if (c!=null) {
TabTitle tabTitle = (TabTitle)c;
tabTitle.setTitle(title);
}
}
};
tabbedPane.addChangeListener(new ChangeListener() {
public void stateChanged(ChangeEvent e) {
refreshGUI();
LibraryTreePanel treePanel = getSelectedTreePanel();
if (treePanel!=null) {
LibraryTreeNode node = treePanel.getSelectedNode();
if (node!=null) {
String path = node.isRoot()? treePanel.pathToRoot: node.getAbsoluteTarget();
commandField.setText(path);
treePanel.showInfo(node);
}
else {
commandField.setText(treePanel.command);
commandField.setCaretPosition(0);
}
}
commandField.setBackground(Color.white);
commandField.setForeground(LibraryTreePanel.defaultForeground);
if (libraryManager!=null && libraryManager.isVisible())
libraryManager.refreshGUI();
}
});
tabbedPane.addMouseListener(new MouseAdapter() {
public void mousePressed(MouseEvent e) {
if(OSPRuntime.isPopupTrigger(e)) {
// make popup and add items
JPopupMenu popup = new JPopupMenu();
// close this tab
JMenuItem item = new JMenuItem(ToolsRes.getString("MenuItem.Close")); //$NON-NLS-1$
item.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
int i = tabbedPane.getSelectedIndex();
closeTab(i);
}
});
popup.add(item);
// add tab to Collections menu
final LibraryTreePanel treePanel = getSelectedTreePanel();
if (!"".equals(treePanel.pathToRoot) && !library.containsPath(treePanel.pathToRoot, false)) { //$NON-NLS-1$
item = new JMenuItem(ToolsRes.getString("LibraryBrowser.MenuItem.AddToLibrary")); //$NON-NLS-1$
item.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
addToCollections(treePanel.pathToRoot);
}
});
popup.addSeparator();
popup.add(item);
}
FontSizer.setFonts(popup, FontSizer.getLevel());
popup.show(tabbedPane, e.getX(), e.getY()+8);
}
}
});
// create property change listener for treePanels
treePanelListener = new PropertyChangeListener() {
public void propertyChange(PropertyChangeEvent e) {
String propertyName = e.getPropertyName();
if (propertyName.equals("collection_edit")) { //$NON-NLS-1$
refreshGUI();
}
else if (propertyName.equals("target")) { //$NON-NLS-1$
LibraryResource record = null;
if (e.getNewValue() instanceof LibraryTreeNode) {
LibraryTreeNode node = (LibraryTreeNode)e.getNewValue();
if (node.record instanceof LibraryCollection) {
setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
if (!LibraryComPADRE.loadResources(node)) {
setCursor(Cursor.getDefaultCursor());
JOptionPane.showMessageDialog(LibraryBrowser.this,
ToolsRes.getString("LibraryBrowser.Dialog.NoResources.Message"), //$NON-NLS-1$
ToolsRes.getString("LibraryBrowser.Dialog.NoResources.Title"), //$NON-NLS-1$
JOptionPane.PLAIN_MESSAGE);
return;
}
node.createChildNodes();
LibraryTreePanel.htmlPanesByNode.remove(node);
LibraryTreeNode lastChild = (LibraryTreeNode)node.getLastChild();
TreePath path = new TreePath(lastChild.getPath());
getSelectedTreePanel().tree.scrollPathToVisible(path);
getSelectedTreePanel().showInfo(node);
setCursor(Cursor.getDefaultCursor());
return;
}
record = node.record.getClone();
record.setBasePath(node.getBasePath());
}
else record = (LibraryResource)e.getNewValue();
String target = record.getTarget();
if (target!=null && (target.toLowerCase().endsWith(".pdf") //$NON-NLS-1$
|| target.toLowerCase().endsWith(".html") //$NON-NLS-1$
|| target.toLowerCase().endsWith(".htm"))) { //$NON-NLS-1$
target = XML.getResolvedPath(target, record.getBasePath());
target = ResourceLoader.getURIPath(target);
setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
OSPDesktop.displayURL(target);
setCursor(Cursor.getDefaultCursor());
}
else {
// forward the event to browser listeners
firePropertyChange("target", e.getOldValue(), record); //$NON-NLS-1$
}
}
}
};
// create edit button
editButton = new JButton();
editButton.setOpaque(false);
editButton.setBorder(buttonBorder);
editButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
final LibraryTreePanel treePanel = getSelectedTreePanel();
if (!treePanel.isEditing()) {
treePanel.setEditing(true);
refreshGUI();
}
else if (!treePanel.isChanged()) {
treePanel.setEditing(false);
refreshGUI();
}
else {
JPopupMenu popup = new JPopupMenu();
JMenuItem item = new JMenuItem(ToolsRes.getString("LibraryBrowser.MenuItem.SaveEdits")); //$NON-NLS-1$
popup.add(item);
item.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
String path = save();
if (path==null) return;
treePanel.setEditing(false);
refreshGUI();
}
});
item = new JMenuItem(ToolsRes.getString("LibraryBrowser.MenuItem.Discard")); //$NON-NLS-1$
popup.add(item);
item.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
treePanel.setEditing(false);
treePanel.revert();
refreshGUI();
}
});
FontSizer.setFonts(popup, FontSizer.getLevel());
popup.show(editButton, 0, editButton.getHeight());
}
}
});
// assemble toolbar
toolbar = new JToolBar();
toolbar.setFloatable(false);
Border empty = BorderFactory.createEmptyBorder(1, 2, 1, 2);
Border etched = BorderFactory.createEtchedBorder();
toolbar.setBorder(BorderFactory.createCompoundBorder(etched, empty));
toolbar.add(commandLabel);
toolbar.add(commandField);
toolbar.add(commandButton);
toolbar.addSeparator();
toolbar.add(searchLabel);
toolbar.add(searchField);
toolbar.addSeparator();
toolbar.add(editButton);
toolbar.addSeparator();
toolbar.add(refreshButton);
add(toolbar, BorderLayout.NORTH);
// menu items
fileMenu = new JMenu();
menubar.add(fileMenu);
newItem = new JMenuItem();
int mask = Toolkit.getDefaultToolkit().getMenuShortcutKeyMask();
newItem.setAccelerator(KeyStroke.getKeyStroke('N', mask));
newItem.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
String path = createNewCollection();
library.addRecent(path, false);
refreshRecentMenu();
}
});
openItem = new JMenuItem();
openItem.setAccelerator(KeyStroke.getKeyStroke('O', mask));
openItem.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
open();
}
});
closeItem = new JMenuItem();
closeItem.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
int i = tabbedPane.getSelectedIndex();
if (closeTab(i)) {
refreshGUI();
}
}
});
closeAllItem = new JMenuItem();
closeAllItem.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
for (int i=tabbedPane.getTabCount()-1; i>=0; i--) {
if (!closeTab(i)) break;
}
refreshGUI();
}
});
recentMenu = new JMenu();
fileMenu.add(recentMenu);
saveItem = new JMenuItem();
saveItem.setAccelerator(KeyStroke.getKeyStroke('S', mask));
saveItem.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
save();
}
});
saveAsItem = new JMenuItem();
saveAsItem.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
String path = saveAs();
library.addRecent(path, false);
refreshRecentMenu();
}
});
exitItem = new JMenuItem();
exitItem.setAccelerator(KeyStroke.getKeyStroke('Q', mask));
exitItem.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
exit();
}
});
collectionsMenu = new JMenu();
menubar.add(collectionsMenu);
manageMenu = new JMenu();
menubar.add(manageMenu);
collectionsItem = new JMenuItem();
manageMenu.add(collectionsItem);
collectionsItem.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
LibraryManager manager = browser.getManager();
manager.tabbedPane.setSelectedComponent(manager.collectionsPanel);
manager.setVisible(true);
}
});
searchItem = new JMenuItem();
manageMenu.add(searchItem);
searchItem.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
LibraryManager manager = browser.getManager();
manager.tabbedPane.setSelectedComponent(manager.searchPanel);
manager.setVisible(true);
}
});
cacheItem = new JMenuItem();
manageMenu.add(cacheItem);
cacheItem.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
LibraryManager manager = browser.getManager();
manager.tabbedPane.setSelectedComponent(manager.cachePanel);
manager.setVisible(true);
}
});
helpMenu = new JMenu();
menubar.add(helpMenu);
helpItem = new JMenuItem();
helpItem.setAccelerator(KeyStroke.getKeyStroke('H', mask));
helpItem.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
showHelp();
}
});
helpMenu.add(helpItem);
helpMenu.addSeparator();
logItem = new JMenuItem();
logItem.setAccelerator(KeyStroke.getKeyStroke('L', mask));
logItem.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
Point p0 = new Frame().getLocation();
JFrame frame = OSPLog.getOSPLog();
if((frame.getLocation().x==p0.x)&&(frame.getLocation().y==p0.y)) {
frame.setLocationRelativeTo(LibraryBrowser.this);
}
frame.setVisible(true);
}
});
helpMenu.add(logItem);
helpMenu.addSeparator();
aboutItem = new JMenuItem();
aboutItem.setAccelerator(KeyStroke.getKeyStroke('A', mask));
aboutItem.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
showAboutDialog();
}
});
helpMenu.add(aboutItem);
// create html about-browser pane
htmlAboutPane = new LibraryTreePanel.HTMLPane();
htmlScroller = new JScrollPane(htmlAboutPane);
htmlAboutPane.setText(getAboutLibraryBrowserText());
htmlAboutPane.setCaretPosition(0);
if (externalDialog!=null) {
externalDialog.addWindowListener(new java.awt.event.WindowAdapter() {
public void windowOpened(WindowEvent e) {
new LibraryLoader().execute();
}
});
}
else {
frame.addWindowListener(new java.awt.event.WindowAdapter() {
public void windowOpened(WindowEvent e) {
new LibraryLoader().execute();
}
});
}
}
/**
* Refreshes the GUI, including locale-dependent resources strings.
*/
protected void refreshGUI() {
if (tabbedPane.getTabCount()==0) {
remove(tabbedPane);
add(htmlScroller, BorderLayout.CENTER);
validate();
}
else {
remove(htmlScroller);
add(tabbedPane, BorderLayout.CENTER);
}
// set text strings
setTitle(ToolsRes.getString("LibraryBrowser.Title")); //$NON-NLS-1$
fileMenu.setText(ToolsRes.getString("Menu.File")); //$NON-NLS-1$
newItem.setText(ToolsRes.getString("LibraryBrowser.MenuItem.New")); //$NON-NLS-1$
openItem.setText(ToolsRes.getString("LibraryBrowser.MenuItem.Open")); //$NON-NLS-1$
closeAllItem.setText(ToolsRes.getString("LibraryBrowser.MenuItem.CloseAll")); //$NON-NLS-1$
saveItem.setText(ToolsRes.getString("LibraryBrowser.MenuItem.Save")); //$NON-NLS-1$
saveAsItem.setText(ToolsRes.getString("LibraryBrowser.MenuItem.SaveAs")); //$NON-NLS-1$
exitItem.setText(ToolsRes.getString("MenuItem.Exit")); //$NON-NLS-1$
collectionsMenu.setText(ToolsRes.getString("LibraryBrowser.Menu.Collections")); //$NON-NLS-1$
manageMenu.setText(ToolsRes.getString("LibraryBrowser.Menu.Manage")); //$NON-NLS-1$
collectionsItem.setText(ToolsRes.getString("LibraryManager.Tab.MyLibrary")+"..."); //$NON-NLS-1$ //$NON-NLS-2$
searchItem.setText(ToolsRes.getString("LibraryManager.Tab.Search")+"..."); //$NON-NLS-1$ //$NON-NLS-2$
cacheItem.setText(ToolsRes.getString("LibraryManager.Tab.Cache")+"..."); //$NON-NLS-1$ //$NON-NLS-2$
helpMenu.setText(ToolsRes.getString("Menu.Help")); //$NON-NLS-1$
helpItem.setText(ToolsRes.getString("LibraryBrowser.MenuItem.Help")); //$NON-NLS-1$
logItem.setText(ToolsRes.getString("MenuItem.Log")); //$NON-NLS-1$
aboutItem.setText(ToolsRes.getString("MenuItem.About")); //$NON-NLS-1$
commandLabel.setText(ToolsRes.getString("LibraryTreePanel.Label.Target")); //$NON-NLS-1$
commandButton.setText(ToolsRes.getString("LibraryTreePanel.Button.Load")); //$NON-NLS-1$
commandField.setToolTipText(ToolsRes.getString("LibraryBrowser.Field.Command.Tooltip")); //$NON-NLS-1$
searchLabel.setText(ToolsRes.getString("LibraryBrowser.Label.Search")); //$NON-NLS-1$
searchField.setToolTipText(ToolsRes.getString("LibraryBrowser.Field.Search.Tooltip")); //$NON-NLS-1$
saveAsItem.setEnabled(true);
refreshRecentMenu();
// rebuild file menu
fileMenu.removeAll();
fileMenu.add(newItem);
fileMenu.add(openItem);
fileMenu.add(recentMenu);
fileMenu.addSeparator();
fileMenu.add(closeItem);
fileMenu.add(closeAllItem);
fileMenu.addSeparator();
fileMenu.add(saveItem);
fileMenu.add(saveAsItem);
fileMenu.addSeparator();
fileMenu.add(exitItem);
LibraryTreePanel treePanel = getSelectedTreePanel();
if (treePanel!=null) {
editButton.setText(!treePanel.isEditing()?
ToolsRes.getString("LibraryBrowser.Button.OpenEditor"): //$NON-NLS-1$
ToolsRes.getString("LibraryBrowser.Button.CloseEditor")); //$NON-NLS-1$
editButton.setEnabled(treePanel.isEditable());
String tabname = " '"+getTabTitle(treePanel.pathToRoot)+"'"; //$NON-NLS-1$ //$NON-NLS-2$
closeItem.setText(ToolsRes.getString("LibraryBrowser.MenuItem.CloseTab")+tabname); //$NON-NLS-1$
closeItem.setEnabled(true);
closeAllItem.setEnabled(true);
saveItem.setEnabled(treePanel.isChanged());
int i = tabbedPane.getSelectedIndex();
String title = tabbedPane.getTitleAt(i);
if (treePanel.isChanged() && !title.endsWith("*")) { //$NON-NLS-1$
tabbedPane.setTitleAt(i, title+"*"); //$NON-NLS-1$
}
else if (!treePanel.isChanged() && title.endsWith("*")) { //$NON-NLS-1$
tabbedPane.setTitleAt(i, title.substring(0, title.length()-1));
}
treePanel.refreshGUI();
}
else {
refreshButton.setToolTipText(ToolsRes.getString("LibraryBrowser.Tooltip.Refresh")); //$NON-NLS-1$
editButton.setText(ToolsRes.getString("LibraryBrowser.Button.OpenEditor")); //$NON-NLS-1$
saveItem.setEnabled(false);
closeItem.setText(ToolsRes.getString("LibraryBrowser.MenuItem.CloseTab")); //$NON-NLS-1$
closeItem.setEnabled(false);
closeAllItem.setEnabled(false);
editButton.setEnabled(false);
refreshButton.setEnabled(false);
commandField.setText(null);
commandButton.setEnabled(false);
saveAsItem.setEnabled(false);
}
repaint();
}
/**
* Refreshes the open recent files menu.
*
* @param menu the menu to refresh
*/
public void refreshRecentMenu() {
synchronized(library.recentTabs) {
recentMenu.setText(ToolsRes.getString("LibraryBrowser.Menu.OpenRecent")); //$NON-NLS-1$
recentMenu.setEnabled(!library.recentTabs.isEmpty());
if (openRecentAction==null) {
openRecentAction = new AbstractAction() {
public void actionPerformed(ActionEvent e) {
String path = e.getActionCommand();
library.addRecent(path, false);
// select tab if path is already loaded
int i = getTabIndexFromPath(path);
if (i>-1) {
tabbedPane.setSelectedIndex(i);
return;
}
TabLoader tabAdder = addTab(path, null);
if (tabAdder!=null) {
tabAdder.addPropertyChangeListener(new PropertyChangeListener() {
public void propertyChange(PropertyChangeEvent e) {
if ("progress".equals(e.getPropertyName())) { //$NON-NLS-1$
Integer n = (Integer)e.getNewValue();
if (n>-1) {
tabbedPane.setSelectedIndex(n);
refreshGUI();
}
}
}
});
tabAdder.execute();
}
else {
library.recentTabs.remove(path);
refreshRecentMenu();
JOptionPane.showMessageDialog(LibraryBrowser.this,
ToolsRes.getString("LibraryBrowser.Dialog.FileNotFound.Message") //$NON-NLS-1$
+": "+path, //$NON-NLS-1$
ToolsRes.getString("LibraryBrowser.Dialog.FileNotFound.Title"), //$NON-NLS-1$
JOptionPane.WARNING_MESSAGE);
}
}
};
}
recentMenu.removeAll();
recentMenu.setEnabled(!library.recentTabs.isEmpty());
for (String next: library.recentTabs) {
String text = library.getNameMap().get(next);
if (text==null) text = XML.getName(next);
JMenuItem item = new JMenuItem(text);
item.setActionCommand(next);
item.setToolTipText(next);
item.addActionListener(openRecentAction);
recentMenu.add(item);
}
}
FontSizer.setFonts(recentMenu, FontSizer.getLevel());
}
/**
* Opens a file using a file chooser.
*/
protected void open() {
JFileChooser fileChooser = OSPRuntime.getChooser();
for (javax.swing.filechooser.FileFilter filter: fileChooser.getChoosableFileFilters()) {
fileChooser.removeChoosableFileFilter(filter);
}
fileChooser.addChoosableFileFilter(filesAndFoldersFilter);
fileChooser.addChoosableFileFilter(Launcher.getXMLFilter());
fileChooser.setAcceptAllFileFilterUsed(false);
fileChooser.setFileSelectionMode(JFileChooser.FILES_AND_DIRECTORIES);
fileChooser.setFileFilter(filesAndFoldersFilter);
File file = GUIUtils.showOpenDialog(this);
// reset chooser to original state
fileChooser.removeChoosableFileFilter(filesAndFoldersFilter);
fileChooser.removeChoosableFileFilter(Launcher.getXMLFilter());
fileChooser.setAcceptAllFileFilterUsed(true);
fileChooser.setFileSelectionMode(JFileChooser.FILES_ONLY);
if (file!=null) {
open(file.getAbsolutePath());
}
}
/**
* Opens a file with a specified path.
* @param path the path to the file
*/
protected void open(String path) {
if (path==null) return;
loadTab(path, null);
}
/**
* Closes a tab.
* @param index the tab number
* @return true unless cancelled by user
*/
protected boolean closeTab(int index) {
if (index<0 || index>=tabbedPane.getTabCount()) return true;
LibraryTreePanel treePanel = getTreePanel(index);
if (!treePanel.saveChanges(getTabTitle(index))) return false;
tabbedPane.removeTabAt(index);
return true;
}
/**
* Saves the selected LibraryTreePanel collection.
* @return the path to the saved file, or null if not saved
*/
protected String save() {
LibraryTreePanel treePanel = getSelectedTreePanel();
String path = treePanel.save();
refreshGUI();
return path;
}
/**
* Saves the current root resource as a new xml file.
* @return the path to the saved file, or null if not saved
*/
protected String saveAs() {
String title = ToolsRes.getString("LibraryBrowser.FileChooser.Title.SaveAs"); //$NON-NLS-1$
String path = getChooserSavePath(title);
if (path!=null) {
path = XML.forwardSlash(path);
LibraryTreePanel treePanel = getSelectedTreePanel();
treePanel.setRootResource(treePanel.rootResource, path, true, true);
path = save();
treePanel.setEditing(true);
refreshTabTitle(path, treePanel.rootResource);
refreshGUI();
commandField.setForeground(LibraryTreePanel.defaultForeground);
}
return path;
}
/**
* Uses a file chooser to define a path to which a library or resource file (xml) can be saved.
* This adds the extension ".xml", if none, and checks for duplicates.
* @param chooserTitle the title of the file chooser
* @return the path, or null if canceled by the user
*/
protected String getChooserSavePath(String chooserTitle) {
File file = GUIUtils.showSaveDialog(this, chooserTitle);
if (file ==null) return null;
String path = file.getAbsolutePath();
String extension = XML.getExtension(path);
if (extension==null) {
path = XML.stripExtension(path)+".xml"; //$NON-NLS-1$
file = new File(path);
if(file.exists()) {
int response = JOptionPane.showConfirmDialog(this,
ToolsRes.getString("Tool.Dialog.ReplaceFile.Message") //$NON-NLS-1$
+" "+file.getName()+"?", //$NON-NLS-1$ //$NON-NLS-2$
ToolsRes.getString("Tool.Dialog.ReplaceFile.Title"), //$NON-NLS-1$
JOptionPane.YES_NO_CANCEL_OPTION);
if(response!=JOptionPane.YES_OPTION) {
return null;
}
}
}
return path;
}
/**
* Returns the set of all searchable cache resources.
*
* @return a set of searchable resources
*/
protected Set<LibraryResource> getSearchCacheTargets() {
// set up search targets
Set<LibraryResource> searchTargets = new TreeSet<LibraryResource>();
File cache = ResourceLoader.getSearchCache();
FileFilter xmlFilter = new XMLFilter();
List<File> xmlFiles = ResourceLoader.getFiles(cache, xmlFilter);
for (File file: xmlFiles) {
XMLControl control = new XMLControlElement(file.getAbsolutePath());
if (!control.failedToRead() && LibraryResource.class.isAssignableFrom(control.getObjectClass())) {
LibraryResource resource = (LibraryResource)control.loadObject(null);
resource.collectionPath = control.getString("real_path"); //$NON-NLS-1$
searchTargets.add(resource);
}
}
return searchTargets;
}
/**
* Searches a set of LibraryResources for resources matching a search phrase.
* @param searchPhrase the phrase to match
* @param searchTargets a set of LibraryResources to search
* @return a LibraryTreePanel containing the search results, or null if no nodes found
*/
protected LibraryTreePanel searchFor(String searchPhrase, Set<LibraryResource> searchTargets) {
if (searchPhrase==null || searchPhrase.trim().equals("")) //$NON-NLS-1$
return null;
Map<LibraryResource, List<String[]>> found = new TreeMap<LibraryResource, List<String[]>>();
for (LibraryResource target: searchTargets) {
if (target==null) continue;
if (target instanceof LibraryCollection) {
Map<LibraryResource, List<String[]>> map = searchCollectionFor(searchPhrase, (LibraryCollection)target);
for (LibraryResource next: map.keySet()) {
next.collectionPath = target.collectionPath;
found.put(next, map.get(next));
}
}
else {
List<String[]> results = searchResourceFor(searchPhrase, target);
if (results!=null) {
found.put(target, results);
}
}
}
if (found.isEmpty()) return null;
// create a LibraryCollection for the search results
String title = "'"+searchPhrase+"'"; //$NON-NLS-1$ //$NON-NLS-2$
LibraryTreePanel treePanel = createLibraryTreePanel();
String name = ToolsRes.getString("LibraryBrowser.SearchResults")+": "+title; //$NON-NLS-1$ //$NON-NLS-2$
LibraryCollection results = new LibraryCollection(name);
treePanel.setRootResource(results, "", false, false); //$NON-NLS-1$
LibraryTreeNode root = treePanel.rootNode;
for (LibraryResource next: found.keySet()) {
LibraryResource clone = next.getClone();
results.addResource(clone);
LibraryTreeNode newNode = new LibraryTreeNode(clone, treePanel);
newNode.setBasePath(next.getInheritedBasePath());
treePanel.insertChildAt(newNode, root, root.getChildCount());
}
LibraryTreeNode last = (LibraryTreeNode)root.getLastChild();
TreePath path = new TreePath(last.getPath());
treePanel.tree.scrollPathToVisible(path);
treePanel.isChanged = false;
return treePanel;
}
/**
* Searches a LibraryCollection for matches to a search phrase.
* @param searchPhrase the phrase
* @param collection the LibraryResource
* @return a List of String[] {where match was found, value in which match was found}, or null if no match found
*/
protected Map<LibraryResource, List<String[]>> searchCollectionFor(String searchPhrase, LibraryCollection collection) {
// deal with AND and OR requests
String[] toAND = searchPhrase.split(AND);
String[] toOR = searchPhrase.split(OR);
if (toAND.length>1 && toOR.length==1) {
Map<LibraryResource, List<String[]>> results = searchCollectionFor(toAND[0], collection);
OSPLog.finer("AND '"+toAND[0]+"' (found: "+results.size()+")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
for (int i=1; i<toAND.length; i++) {
Map<LibraryResource, List<String[]>> next = searchCollectionFor(toAND[i], collection);
OSPLog.finer("AND '"+toAND[i]+"' (found: "+results.size()+")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
results = applyAND(results, next);
}
OSPLog.finer("AND found: "+results.size()); //$NON-NLS-1$
return results;
}
if (toOR.length>1 && toAND.length==1) {
Map<LibraryResource, List<String[]>> results = searchCollectionFor(toOR[0], collection);
OSPLog.finer("OR '"+toOR[0]+"' (found: "+results.size()+")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
for (int i=1; i<toOR.length; i++) {
Map<LibraryResource, List<String[]>> next = searchCollectionFor(toOR[i], collection);
OSPLog.finer("OR '"+toOR[i]+"' (found: "+results.size()+")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
results = applyOR(results, next);
}
OSPLog.finer("OR found: "+results.size()); //$NON-NLS-1$
return results;
}
if (toOR.length>1 && toAND.length>1) {
// apply operations in left-to-right order but give precedence to parentheses
String[] split = getNextSplit(searchPhrase);
Map<LibraryResource, List<String[]>> results = searchCollectionFor(split[0], collection);
while (split.length>2) {
String operator = split[1];
String remainder = split[2];
split = getNextSplit(remainder);
Map<LibraryResource, List<String[]>> next = searchCollectionFor(split[0], collection);
if (operator.equals(AND)) {
results = applyAND(results, next);
}
else if (operator.equals(OR)) {
results = applyOR(results, next);
}
}
return results;
}
// do actual searching
Map<LibraryResource, List<String[]>> found = new TreeMap<LibraryResource, List<String[]>>();
List<String[]> results = searchResourceFor(searchPhrase, collection);
if (results!=null) {
found.put(collection, results);
}
for (LibraryResource record: collection.getResources()) {
if (record==null) continue;
if (record instanceof LibraryCollection) {
Map<LibraryResource, List<String[]>> map = searchCollectionFor(searchPhrase, (LibraryCollection)record);
for (LibraryResource next: map.keySet()) {
found.put(next, map.get(next));
}
}
else {
results = searchResourceFor(searchPhrase, record);
if (results!=null) {
found.put(record, results);
}
}
}
return found;
}
/**
* Searches a LibraryResource for matches to a search phrase.
* @param searchPhrase the phrase
* @param record the LibraryResource
* @return a List of String[] {category where match found, value where match found}, or null if no match found
*/
protected List<String[]> searchResourceFor(String searchPhrase, LibraryResource record) {
String toMatch = searchPhrase.toLowerCase();
ArrayList<String[]> foundData = new ArrayList<String[]>();
// search node name
String name = record.getName();
if (name.toLowerCase().contains(toMatch)) {
foundData.add(new String[] {"name", name}); //$NON-NLS-1$
}
// search node type
String type = record.getType();
if (type.toLowerCase().contains(toMatch)) {
foundData.add(new String[] {"type", type}); //$NON-NLS-1$
}
// search metadata
Set<Metadata> metadata = record.getMetadata();
if (metadata!=null) {
for (Metadata next: metadata) {
String key = next.getData()[0];
String value = next.getData()[1];
if (value.toLowerCase().indexOf(toMatch)>-1) {
foundData.add(new String[] {key, value});
}
}
}
return foundData.isEmpty()? null: foundData;
}
/**
* Returns the phrase before the next AND or OR operator, the operator itself, and the remainder of the phrase.
*
* @param phrase a search phrase
* @return String[]
*/
protected String[] getNextSplit(String phrase) {
String[] and = phrase.split(AND, 2);
String[] or = phrase.split(OR, 2);
String[] open = phrase.split(Pattern.quote(OPENING), 2);
int which = and[0].length()<=or[0].length()?
and[0].length()<=open[0].length()? 0: 2:
or[0].length()<=open[0].length()? 1: 2;
if (which==2 && open.length>1) { // found opening parentheses
// split remainder into parenthesis contents and remainder
String[] split = getParenthesisSplit(open[1]);
if (split.length==1) {
return new String[] {split[0]};
}
int n = split[1].indexOf(AND);
int m = split[1].indexOf(OR);
if (n==-1 && m==-1) {
return new String[] {open[1]};
}
if (n>-1 && (m==-1 || n<m)) { // AND
return new String[] {split[0], AND, split[1].substring(n+AND.length())};
}
if (m>-1 && (n==-1 || m<n)) { // OR
return new String[] {split[0], OR, split[1].substring(m+OR.length())};
}
}
switch(which) {
case 0:
if (and.length==1) return new String[] {and[0]};
return new String[] {and[0], AND, and[1]};
case 1:
if (or.length==1) return new String[] {or[0]};
return new String[] {or[0], OR, or[1]};
}
return new String[] {phrase};
}
/**
* Returns the phrase enclosed in parentheses along with the remainder of a phrase.
*
* @param phrase a phrase that starts immediately AFTER an opening parenthesis
* @return String[] {the enclosed phrase, the remainder}
*/
protected String[] getParenthesisSplit(String phrase) {
int index = 1; // index of closing parenthesis
int n = 1; // number of unpaired opening parentheses
int opening = phrase.indexOf(OPENING, index);
int closing = phrase.indexOf(CLOSING, index);
while (n>0) {
if (opening>-1 && opening<closing) {
n++;
index = opening+1;
opening = phrase.indexOf(OPENING, index);
}
else if (closing>-1) {
n--;
index = closing+1;
closing = phrase.indexOf(CLOSING, index);
}
else return new String[] {phrase};
}
String token = phrase.substring(0, index-1);
String remainder = phrase.substring(index);
return remainder.trim().equals("")? new String[] {token}: new String[] {token, remainder}; //$NON-NLS-1$
}
/**
* Returns the resources that are contained in the keysets of both of two input maps.
* @param results1
* @param results2
* @return map of resources found in both keysets
*/
protected Map<LibraryResource, List<String[]>> applyAND(Map<LibraryResource, List<String[]>> results1,
Map<LibraryResource, List<String[]>> results2) {
Map<LibraryResource, List<String[]>> resultsAND = new TreeMap<LibraryResource, List<String[]>>();
Set<LibraryResource> keys1 = results1.keySet();
for (LibraryResource node: results2.keySet()) {
if (keys1.contains(node)) { // node is in both keysets
List<String[]> matchedTerms = new ArrayList<String[]>();
matchedTerms.addAll(results1.get(node));
matchedTerms.addAll(results2.get(node));
resultsAND.put(node, matchedTerms);
}
}
return resultsAND;
}
/**
* Returns the resources that are contained in the keysets of either of two input maps.
* @param results1
* @param results2
* @return map of resources found in either keyset
*/
protected Map<LibraryResource, List<String[]>> applyOR(Map<LibraryResource, List<String[]>> results1,
Map<LibraryResource, List<String[]>> results2) {
Map<LibraryResource, List<String[]>> resultsOR = new TreeMap<LibraryResource, List<String[]>>();
// add nodes in results1
for (LibraryResource node: results1.keySet()) {
List<String[]> matchedTerms = new ArrayList<String[]>();
matchedTerms.addAll(results1.get(node));
resultsOR.put(node, matchedTerms);
}
// add nodes in results2
for (LibraryResource node: results2.keySet()) {
if (resultsOR.keySet().contains(node)) {
resultsOR.get(node).addAll(results2.get(node));
continue;
}
List<String[]> matchedTerms = new ArrayList<String[]>();
matchedTerms.addAll(results2.get(node));
resultsOR.put(node, matchedTerms);
}
return resultsOR;
}
/**
* Adds a collection to this browser's library after prompting the user to
* assign it a name.
*
* @param path the path to the collection
*/
protected void addToCollections(String path) {
if (library.containsPath(path, true)) {
return;
}
String proposed = getTabTitle(path);
if (proposed==null) {
LibraryResource collection = loadResource(path);
if (collection!=null) proposed = collection.getName();
}
if (proposed.equals("")) { //$NON-NLS-1$
proposed = XML.getName(path); // filename
}
library.addCollection(path, proposed);
refreshCollectionsMenu();
refreshGUI();
}
/**
* Creates a new LibraryCollection file.
* @return the path to the new collection
*/
protected String createNewCollection() {
String title = ToolsRes.getString("LibraryBrowser.FileChooser.Title.SaveCollectionAs"); //$NON-NLS-1$
String path = getChooserSavePath(title);
if (path!=null) {
LibraryCollection collection = new LibraryCollection(null);
// save new collection
XMLControl control = new XMLControlElement(collection);
control.write(path);
path = XML.forwardSlash(path);
TabLoader tabAdder = addTab(path, null);
if (tabAdder==null) return null;
tabAdder.addPropertyChangeListener(new PropertyChangeListener() {
public void propertyChange(PropertyChangeEvent e) {
if ("progress".equals(e.getPropertyName())) { //$NON-NLS-1$
Integer n = (Integer)e.getNewValue();
if (n>-1) {
tabbedPane.setSelectedIndex(n);
LibraryTreePanel treePanel = getSelectedTreePanel();
treePanel.setEditing(true);
refreshGUI();
}
}
}
});
tabAdder.execute();
}
return path;
}
/**
* Returns a name that is not a duplicate of an existing name.
*
* @param proposed a proposed name
* @param nameToIgnore a name that is ignored when comparing
* @return a unique name that is the proposed name plus a possible suffix
*/
protected String getUniqueName(String proposed, String nameToIgnore) {
proposed = proposed.trim();
if (isDuplicateName(proposed, nameToIgnore)) {
int i = 2;
String s = proposed+" ("+i+")"; //$NON-NLS-1$ //$NON-NLS-2$
while (isDuplicateName(s, nameToIgnore)) {
i++;
s = proposed+" ("+i+")"; //$NON-NLS-1$ //$NON-NLS-2$
}
return s;
}
return proposed;
}
/**
* Determines if a name duplicates an existing name.
*
* @param name the proposed name
* @param nameToIgnore a name that is ignored when comparing
* @return true if name is a duplicate
*/
protected boolean isDuplicateName(String name, String nameToIgnore) {
// compare with existing names in library and tabbedPane
for (String next: library.getNames()) {
if (next.equals(nameToIgnore)) continue;
if (name.equals(next)) return true;
}
for (int i=0; i<tabbedPane.getTabCount(); i++) {
String title = tabbedPane.getTitleAt(i);
if (title.endsWith("*")) //$NON-NLS-1$
title = title.substring(0, title.length()-1);
if (title.equals(nameToIgnore)) continue;
if (name.equals(title)) return true;
}
return false;
}
/**
* Creates a new empty LibraryTreePanel.
* @return the library tree panel
*/
protected LibraryTreePanel createLibraryTreePanel() {
LibraryTreePanel treePanel = new LibraryTreePanel(this);
treePanel.addPropertyChangeListener(treePanelListener);
return treePanel;
}
/**
* Shows the about dialog.
*/
protected void showAboutDialog() {
String aboutString = ToolsRes.getString("LibraryBrowser.Title")+" 2.0, Dec 2012\n" //$NON-NLS-1$ //$NON-NLS-2$
+"Open Source Physics Project\n" //$NON-NLS-1$
+"www.opensourcephysics.org"; //$NON-NLS-1$
JOptionPane.showMessageDialog(this, aboutString, ToolsRes.getString("Dialog.About.Title") //$NON-NLS-1$
+" "+ToolsRes.getString("LibraryBrowser.Title"), //$NON-NLS-1$ //$NON-NLS-2$
JOptionPane.INFORMATION_MESSAGE);
}
/**
* Shows the help frame and displays a help HTML page.
*/
protected void showHelp() {
if (fireHelpEvent) {
firePropertyChange("help", null, null); //$NON-NLS-1$
return;
}
String helpPath = XML.getResolvedPath(LIBRARY_HELP_NAME, LIBRARY_HELP_BASE);
if(ResourceLoader.getResource(helpPath)==null) {
String classBase = "/org/opensourcephysics/resources/tools/html/"; //$NON-NLS-1$
helpPath = XML.getResolvedPath(LIBRARY_HELP_NAME, classBase);
}
if((helpFrame==null)||!helpPath.equals(helpFrame.getTitle())) {
helpFrame = new TextFrame(helpPath);
helpFrame.enableHyperlinks();
helpFrame.setSize(760, 560);
// center on the screen
Dimension dim = Toolkit.getDefaultToolkit().getScreenSize();
int x = (dim.width-helpFrame.getBounds().width)/2;
int y = (dim.height-helpFrame.getBounds().height)/2;
helpFrame.setLocation(x, y);
}
helpFrame.setVisible(true);
}
/**
* Returns html code that describes this browser.
* This is displayed when no LibraryTreePanel is loaded.
* @return the html code
*/
protected String getAboutLibraryBrowserText() {
String path = "org/opensourcephysics/resources/tools/images/compadre_banner.jpg"; //$NON-NLS-1$
Resource res = ResourceLoader.getResource(path);
String imageCode = "<p align=\"center\"><img src=\""+res.getURL()+"\"></p>"; //$NON-NLS-1$ //$NON-NLS-2$
String code = imageCode+
"<h1>Open Source Physics Digital Library Browser</h1>"+ //$NON-NLS-1$
"<p>The OSP Digital Library Browser enables you to browse, organize and access collections of digital library resources "+ //$NON-NLS-1$
"such as EJS models and Tracker experiments. Collections and resources may be on a local drive or remote server.</p>"+ //$NON-NLS-1$
"<ul>"+ //$NON-NLS-1$
" <li>Open a collection by choosing from the <strong>Collections</strong> menu or entering a URL directly in the toolbar "+ //$NON-NLS-1$
"as with a web browser.</li>"+ //$NON-NLS-1$
" <li>Collections are organized and displayed in a tree. Each tree node is a resource or sub-collection. "+ //$NON-NLS-1$
"Click a node to learn about the resource or double-click to download and/or open it in EJS or Tracker.</li>"+ //$NON-NLS-1$
" <li>Build and organize your own local collection by clicking the <strong>Open Editor</strong> button. "+ //$NON-NLS-1$
"Collections are stored as xml documents that contain references to the actual resource files. "+ //$NON-NLS-1$
"For more information, see the Help menu.</li>"+ //$NON-NLS-1$
" <li>Share your collections by uploading all files to the web or a local network. For more information, see the Help menu.</li>"+ //$NON-NLS-1$
"</ul>"+ //$NON-NLS-1$
"<h2>ComPADRE Digital Library</h2>"+ //$NON-NLS-1$
"<p>The ComPADRE Pathway, a part of the National Science Digital Library, is a growing network of educational resource "+ //$NON-NLS-1$
"collections supporting teachers and students in Physics and Astronomy. As a user you may explore collections designed to meet "+ //$NON-NLS-1$
"your specific needs and help build the network by recommending resources, commenting on resources, and starting or joining "+ //$NON-NLS-1$
"discussions. For more information, see <<b><a href=\"http://www.compadre.org/OSP/\">http://www.compadre.org/OSP/</a></b>>. "+ //$NON-NLS-1$
"To recommend an OSP resource for ComPADRE, visit the Suggest a Resource page at <<b><a href="+ //$NON-NLS-1$
"\"http://www.compadre.org/osp/items/suggest.cfm\">http://www.compadre.org/osp/items/suggest.cfm</a></b>>. "+ //$NON-NLS-1$
"Contact the OSP Collection editor, Wolfgang Christian, for additional information.</p>"; //$NON-NLS-1$
return code;
}
//______________________________ inner classes _________________________________
/**
* A class to display and handle actions for a ComPADRE tab title.
*/
class TabTitle extends JPanel {
JLabel titleLabel, iconLabel;
Icon normalIcon, boldIcon;
Action action;
TabTitle(Icon lightIcon, Icon heavyIcon) {
super(new BorderLayout());
this.setOpaque(false);
titleLabel = new JLabel();
iconLabel = new JLabel();
iconLabel.setBorder(BorderFactory.createEmptyBorder(0, 6, 0, 0));
iconLabel.addMouseListener(new MouseAdapter() {
public void mouseClicked(MouseEvent e) {
int i = getTabIndexFromTitle(titleLabel.getText());
if (i>-1 && tabbedPane.getSelectedIndex()!=i) tabbedPane.setSelectedIndex(i);
action.actionPerformed(null);
}
public void mouseEntered(MouseEvent e) {
iconLabel.setIcon(boldIcon);
}
public void mouseExited(MouseEvent e) {
iconLabel.setIcon(normalIcon);
}
});
add(titleLabel, BorderLayout.WEST);
add(iconLabel, BorderLayout.EAST);
setIcons(lightIcon, heavyIcon);
}
void setTitle(String title) {
titleLabel.setText(title);
}
void setIcons(Icon lightIcon, Icon heavyIcon) {
normalIcon = lightIcon;
boldIcon = heavyIcon;
iconLabel.setIcon(normalIcon);
}
void setAction(Action action) {
this.action = action;
}
}
/**
* A SwingWorker class to load the Library at startup.
*/
class LibraryLoader extends SwingWorker<Library, Object> {
@Override
public Library doInBackground() {
Runnable runner = new Runnable() {
public void run() {
webConnected = ResourceLoader.isURLAvailable("http://www.opensourcephysics.org"); //$NON-NLS-1$
if (!webConnected) {
JOptionPane.showMessageDialog(LibraryBrowser.this,
ToolsRes.getString("LibraryBrowser.Dialog.ServerUnavailable.Message"), //$NON-NLS-1$
ToolsRes.getString("LibraryBrowser.Dialog.ServerUnavailable.Title"), //$NON-NLS-1$
JOptionPane.WARNING_MESSAGE);
}
}
};
if (!libraryPath.startsWith("http:")) { //$NON-NLS-1$
// load library
library.load(libraryPath);
// add previously open tabs that are available
if (library.openTabPaths!=null) {
ArrayList<String> unopenedTabs = new ArrayList<String>();
String[] paths = library.openTabPaths;
for (String path: paths) {
// first check search cache
File cachedFile = ResourceLoader.getSearchCacheFile(path);
if (cachedFile.exists()) {
TabLoader tabAdder = addTab(path, null);
if (tabAdder!=null) tabAdder.execute();
}
else {
unopenedTabs.add(path);
}
}
if (!unopenedTabs.isEmpty()) {
paths = unopenedTabs.toArray(new String[unopenedTabs.size()]);
unopenedTabs.clear();
for (String path: paths) {
// check for local resource
Resource res = ResourceLoader.getResource(path);
if (res!=null && !path.startsWith("http:")) { //$NON-NLS-1$
TabLoader tabAdder = addTab(path, null);
if (tabAdder!=null) tabAdder.execute();
}
else {
unopenedTabs.add(path);
}
}
}
boolean done = unopenedTabs.isEmpty();
// save web-based tabs for done() method
library.openTabPaths = done? null: unopenedTabs.toArray(new String[unopenedTabs.size()]);
}
runner.run();
}
else {
runner.run(); // check web connection first
if (webConnected)
library.load(libraryPath);
}
return library;
}
@Override
protected void done() {
try {
Library library = get();
// add previously open tabs not available for loading in doInBackground method
if (library.openTabPaths!=null) {
for (final String path: library.openTabPaths) {
boolean available = isWebConnected() && path.startsWith("http:"); //$NON-NLS-1$
if (available) {
TabLoader tabAdder = addTab(path, null);
if (tabAdder!=null) tabAdder.execute();
}
}
}
} catch (Exception ignore) {
}
refreshCollectionsMenu();
refreshRecentMenu();
}
}
/**
* A SwingWorker class to load tabs.
*/
class TabLoader extends SwingWorker<LibraryTreePanel, Object> {
String path;
int index;
List<String> treePath;
boolean saveToCache = true;
TabLoader(String pathToAdd, int tabIndex, List<String> treePath) {
path = pathToAdd;
index = tabIndex;
this.treePath = treePath;
}
@Override
public LibraryTreePanel doInBackground() {
setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
String realPath = path;
File cachedFile = ResourceLoader.getSearchCacheFile(path);
if (cachedFile.exists() && path.startsWith("http:")) { //$NON-NLS-1$
realPath = cachedFile.getAbsolutePath();
saveToCache = false;
}
LibraryResource resource = loadResource(realPath);
if (resource!=null) {
LibraryTreePanel treePanel = index<0? createLibraryTreePanel(): getTreePanel(index);
// tab is editable only if it is a local XML file
boolean editable = !path.startsWith("http:") && path.toLowerCase().endsWith(".xml"); //$NON-NLS-1$ //$NON-NLS-2$
treePanel.setRootResource(resource, path, editable, isRecentPathXML);
return treePanel;
}
return null;
}
@Override
protected void done() {
try {
LibraryTreePanel treePanel = get();
if (treePanel!=null) {
treePanel.setFontLevel(FontSizer.getLevel());
if (index<0) {
tabbedPane.addTab("", treePanel); //$NON-NLS-1$
index = tabbedPane.getTabCount()-1;
}
refreshTabTitle(path, treePanel.rootResource);
tabbedPane.setToolTipTextAt(index, path);
treePanel.setSelectionPath(treePath);
// start background SwingWorker to load metadata and set up search database
if (treePanel.metadataLoader!=null) {
treePanel.metadataLoader.cancel();
}
treePanel.metadataLoader = treePanel.new MetadataLoader(saveToCache, treePath);
treePanel.metadataLoader.execute();
setProgress(index);
}
else {
String s = ToolsRes.getString("LibraryBrowser.Dialog.CollectionNotFound.Message"); //$NON-NLS-1$
JOptionPane.showMessageDialog(LibraryBrowser.this,
s+":\n"+path, //$NON-NLS-1$
ToolsRes.getString("LibraryBrowser.Dialog.CollectionNotFound.Title"), //$NON-NLS-1$
JOptionPane.WARNING_MESSAGE);
library.removeRecent(path);
refreshRecentMenu();
setProgress(-1);
}
} catch (Exception ignore) {
}
setCursor(Cursor.getDefaultCursor());
}
}
//______________________________ static methods and classes ____________________________
/**
* Entry point when run as an independent application.
*
* @param args String[] ignored
*/
public static void main(String[] args) {
final LibraryBrowser browser = LibraryBrowser.getBrowser();
browser.addOSPLibrary(TRACKER_LIBRARY);
browser.addOSPLibrary(SHARED_LIBRARY);
browser.addComPADRECollection(LibraryComPADRE.EJS_SERVER_TREE+LibraryComPADRE.PRIMARY_ONLY);
browser.addComPADRECollection(LibraryComPADRE.TRACKER_SERVER_TREE+LibraryComPADRE.PRIMARY_ONLY);
browser.refreshCollectionsMenu();
// code below opens Tracker when LibraryBrowser is launched as an independent application
// browser.addPropertyChangeListener("target", new PropertyChangeListener() { //$NON-NLS-1$
// public void propertyChange(PropertyChangeEvent e) {
// LibraryResource record = (LibraryResource)e.getNewValue();
// String target = XML.getResolvedPath(record.getTarget(), record.getBasePath());
//
// ArrayList<String> extensions = new ArrayList<String>();
// for (String ext: VideoIO.getVideoExtensions()) {
// extensions.add(ext);
// }
// extensions.add("trk"); //$NON-NLS-1$
// extensions.add("zip"); //$NON-NLS-1$
// extensions.add("trz"); //$NON-NLS-1$
// for (String ext: extensions) {
// if (target.endsWith("."+ext)) { //$NON-NLS-1$
// Tracker tracker = Tracker.getTracker();
// final TFrame frame = tracker.getFrame();
// frame.setVisible(true);
// try {
// target = ResourceLoader.getURIPath(target);
// URL url = new URL(target);
// TrackerIO.open(url, new TrackerPanel(), frame);
// } catch (Exception ex) {ex.printStackTrace();}
// }
// }
// }
// });
browser.exitOnClose = true;
Dimension dim = Toolkit.getDefaultToolkit().getScreenSize();
int x = (dim.width - browser.getBounds().width) / 2;
int y = (dim.height - browser.getBounds().height) / 2;
browser.setLocation(x, y);
browser.setVisible(true);
}
/**
* Returns true if connected to the web.
*
* @return true if web connected
*/
protected static boolean isWebConnected() {
return webConnected;
}
/**
* Returns the redirect URL path, if any, of an HTML page.
* @param code the HTML code
* @return the redirect path
*/
protected static String getRedirectFromHTMLCode(String code) {
if (code==null) return null;
String[] parts = code.split("<!--"); //$NON-NLS-1$
if (parts.length>1) {
for (int i=1; i<parts.length; i++) {
if (parts[i].trim().startsWith("redirect:")) { //$NON-NLS-1$
String[] subparts = parts[i].split("-->"); //$NON-NLS-1$
return subparts.length>1? subparts[0].substring(9).trim(): null;
}
}
}
return null;
}
/**
* A FileFilter that accepts trk, pdf, html and zip (if trk found inside) files
* with names that do NOT start with underscore.
*/
static class TrackerDLFilter implements FileFilter {
static {
// if Xuggle is available, register with VideoIO
String className = "org.opensourcephysics.media.xuggle.XuggleIO"; //$NON-NLS-1$
try {
Class<?> xuggleIOClass = Class.forName(className);
Method method = xuggleIOClass.getMethod("registerWithVideoIO", (Class[]) null); //$NON-NLS-1$
method.invoke(null, (Object[]) null);
} catch (Exception ex) {
}
}
public boolean accept(File file) {
if (file==null || file.isDirectory()) return false;
String name = file.getName();
if (name.startsWith("_")) return false; //$NON-NLS-1$
String ext = XML.getExtension(name);
if (ext==null) return false;
ext = ext.toLowerCase();
if (ext.equals("trk")) return true; //$NON-NLS-1$
if (ext.indexOf("htm")>-1) return true; //$NON-NLS-1$
if (ext.equals("pdf")) return true; //$NON-NLS-1$
if (ext.equals("trz")) return true; //$NON-NLS-1$
for (String next: VideoIO.getVideoExtensions()) {
if (ext.equals(next.toLowerCase())) return true;
}
if (ext.equals("zip")) { //$NON-NLS-1$
Set<String> files = ResourceLoader.getZipContents(file.getAbsolutePath());
for (String next: files) {
if (next.toLowerCase().endsWith(".trk")) return true; //$NON-NLS-1$
}
}
return false;
}
}
/**
* A FileFilter that accepts only directories with names that do NOT start with underscore.
*/
static class DirectoryFilter implements FileFilter {
public boolean accept(File file) {
if (file.getName().startsWith("_")) return false; //$NON-NLS-1$
return file.isDirectory();
}
}
/**
* A FileFilter that accepts html files with names that do NOT start with underscore.
*/
static class HTMLFilter implements FileFilter {
public boolean accept(File file) {
if (file==null || file.isDirectory()) return false;
String name = file.getName();
if (name.startsWith("_")) return false; //$NON-NLS-1$
String ext = XML.getExtension(name);
if (ext==null) return false;
if (ext.toLowerCase().startsWith("htm")) return true; //$NON-NLS-1$
return false;
}
}
/**
* A FileFilter that accepts xml files and directories.
*/
static class XMLFilter implements FileFilter {
public boolean accept(File file) {
if (file==null) return false;
if (file.isDirectory()) return true;
String ext = XML.getExtension(file.getName());
if (ext==null) return false;
if (ext.toLowerCase().equals("xml")) return true; //$NON-NLS-1$
return false;
}
}
/**
* A filechooser FileFilter that accepts files and folders.
*/
static class FilesAndFoldersFilter extends javax.swing.filechooser.FileFilter {
// accept all directories and files.
public boolean accept(File f) {
return f!=null;
}
// the description of this filter
public String getDescription() {
return ToolsRes.getString("LibraryBrowser.FilesAndFoldersFilter.Description"); //$NON-NLS-1$
}
}
}