/**
*#########################################################################
* SearchResultsDisplay.java - part of the demo-client for Greenstone 3,
* of the Greenstone digital library suite from the New Zealand Digital
* Library Project at the * University of Waikato, New Zealand.
*
* Copyright (C) 2008 New Zealand Digital Library Project
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*########################################################################
*/
package org.greenstone.gs3client;
import java.awt.Component;
import java.awt.Container;
import java.awt.Cursor;
import java.awt.Dimension;
import java.awt.BorderLayout;
import java.awt.FlowLayout;
import javax.swing.JPopupMenu;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JSplitPane;
import javax.swing.JEditorPane;
import javax.swing.JList;
import javax.swing.JLabel;
import javax.swing.JTree;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.TreeSelectionModel;
import javax.swing.event.TreeSelectionEvent;
import javax.swing.event.TreeSelectionListener;
import javax.swing.text.html.HTMLDocument;
import org.apache.log4j.Logger;
import org.greenstone.gs3client.data.DocumentNodeData;
import java.net.URL;
import java.net.MalformedURLException;
/**
* The Search panel inside the Java-client's tab pane that's labelled
* "Search Results". This panel contains two tree views:
* - one for displaying the list of search results (top-level documents or document
* - one for displaying the structure of document nodes selected in the list of
* search results.
* This panel also contains an area where the selected documentNode's metadata is
* displayed, and a text area where the textual or image content of a selected
* documentNode is displayed.
* @author ak19
*/
public class SearchResultsDisplay
extends JPanel implements TreeSelectionListener, ColourCombo.ColourChangeable
{
/** The Logger for this class */
static Logger LOG = Logger.getLogger(SearchResultsDisplay.class);
/** Access to the running instance of GS3JavaClient */
protected GS3JavaClient client;
/* GUI items of this SearchResults Panel */
protected JTree searchResultsTree, docStructureTree;
protected JSplitPane docInfoPane, docInfoWithContent, treeviewSplitPane;
protected JEditorPane docContentEditPane;
protected JList metanames, metavalues;
/** Context menu that pops up when users right click in the browse tree area */
protected JPopupMenu popup;
/** Constructor that creates the Search Panel and its internal GUI items.
* @param client is the running instance of the client application through
* which its methods can be accessed. */
public SearchResultsDisplay(GS3JavaClient client)
{
super(new FlowLayout(FlowLayout.LEFT));
// so the contents of this panel are not centred
this.client = client;
// The panels: searchresults (with a tree), docStructure (also
// contains a tree), metadataPanel (with 2 lists for metanames
// and metavalues and docContentEditor to show a selected html
// document's contents
JPanel searchResultsPanel, docStructurePanel, metadataPanel;
this.docContentEditPane = new JEditorPane();
this.docContentEditPane.setEditable(false);
this.docContentEditPane.setContentType("text/html");
//"text/plain" to view the html markup
searchResultsPanel = new JPanel(new BorderLayout());
docStructurePanel = new JPanel(new BorderLayout());
searchResultsPanel.add(
new JLabel("Documents returned"), BorderLayout.NORTH);
docStructurePanel.add(
new JLabel("Document structure"), BorderLayout.NORTH);
metadataPanel = new JPanel(new BorderLayout());
this.metanames = new JList();
this.metavalues = new JList();
metadataPanel.add(metanames, BorderLayout.WEST);
metadataPanel.add(metavalues, BorderLayout.CENTER);
JPanel metaSuperPanel = new JPanel(new BorderLayout());
metaSuperPanel.add(new JLabel("Metadata"), BorderLayout.NORTH);
metaSuperPanel.add(
new JScrollPane(metadataPanel), BorderLayout.CENTER);
// Create the searchresults tree and the docStructure tree
// (neither of them initialised to any meaningful nodes at present).
// Both trees allow only one selection at a time.
// Add a listener to both for when their treenode selections change.
// Finally add them to their respective JPanels.
searchResultsTree = new JTree(new DefaultMutableTreeNode(null));
searchResultsTree.getSelectionModel().setSelectionMode(
TreeSelectionModel.SINGLE_TREE_SELECTION);
searchResultsTree.addTreeSelectionListener(this);
searchResultsPanel.add(new JScrollPane(searchResultsTree),
BorderLayout.CENTER);
docStructureTree = new JTree(new DefaultMutableTreeNode(null));
docStructureTree.getSelectionModel().setSelectionMode(
TreeSelectionModel.SINGLE_TREE_SELECTION);
docStructureTree.addTreeSelectionListener(this);
docStructurePanel.add(new JScrollPane(docStructureTree),
BorderLayout.CENTER);
// add a (rightclick) popup menu to the document structure tree:
popup = new JPopupMenu();
docStructureTree.setComponentPopupMenu(popup);
docStructureTree.addMouseListener(new Displays.PopupListener(
popup, docStructureTree, client, this.docContentEditPane));
// Add the docStructure and metadata panels next to each
// other within a split pane
docInfoPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT);
docInfoPane.setLeftComponent(docStructurePanel);
docInfoPane.setRightComponent(metaSuperPanel);
docInfoPane.setOneTouchExpandable(true);
// Add that to the top of the editorpanel showing the contents of
// any selected document nodes
docInfoWithContent = new JSplitPane(JSplitPane.VERTICAL_SPLIT);
docInfoWithContent.setTopComponent(docInfoPane);
docInfoWithContent.setBottomComponent(
new JScrollPane(this.docContentEditPane));
docInfoWithContent.setOneTouchExpandable(true);
// Add that splitpane to the right of the search results panel
// in a new splitpane (horizontal)
treeviewSplitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT);
treeviewSplitPane.setLeftComponent(searchResultsPanel);
treeviewSplitPane.setRightComponent(this.docInfoWithContent);
treeviewSplitPane.setOneTouchExpandable(true);
this.add(treeviewSplitPane);
// All the scrollviews and the splitpanes will be sized
// when getPreferredSize() is called on show()/setVisible()
}
/** Overrode this method to resize the splitpanes within, upon resize.
* It calculates the size of this panel, as well as setting those of
* the splitpanes it contains based on the size of the parent container.
* @return the preferred dimensions of this JPanel. */
public Dimension getPreferredSize() {
Dimension size = this.getParent().getSize(); //900, 500
int x = (int)size.getWidth() / 3;
int y = (int)(size.getHeight() / 5 * 2);
docInfoPane.setDividerLocation(x);
docInfoWithContent.setDividerLocation(y);
treeviewSplitPane.setDividerLocation(x);
docInfoPane.setPreferredSize(size);
docInfoWithContent.setPreferredSize(size);
treeviewSplitPane.setPreferredSize(size);
return size;
}
/** Changes the colour of the query form and its controls to the
* current colours set in class ColourCombo. Specified by
* the ColourCombo.ColourChangeable interface. */
public void changeUIColour() {
Component[] comps = { this, this.docStructureTree,
this.searchResultsTree, metanames, metavalues, popup };
ColourCombo.changeColor(comps);
// ensures that the JLabel for "metadata" and the trees
// are coloured properly:
ColourCombo.changeAncestorColor(metanames);
ColourCombo.changeAncestorColor(this.docStructureTree);
ColourCombo.changeAncestorColor(this.searchResultsTree);
}
/** Clears the service-specific buttons in the browseBar and the
* service-specific display-data. The rest of the GUI (split panes, panels)
* remain as they are. Resets the contents of the widgets in this panel. */
public void clear() {
//searchResultsTree.removeAll();
//docStructureTree.removeAll(); // doesn't seem to emtpy tree
//this.metanames.removeAll(); //doesn't seem to empty the list values
//this.metavalues.removeAll(); //doesn't seem to empty the list values
// empty the docStructureTree's popup menu too
popup.removeAll();
// empty the trees
((DefaultTreeModel)searchResultsTree.getModel()).setRoot(null);
((DefaultTreeModel)docStructureTree.getModel()).setRoot(null);
// empty the metadatalists
final String[] empty = {""};
this.metanames.setListData(empty);
this.metavalues.setListData(empty);
// empty the html view area
this.docContentEditPane.setText("");
}
/** Restructures the searchResultsTree when another search has been made, so
* that the search results contain a new list of documentNodeData objects.
* @param resultDocs - the list of (new) DocumentNodeData objects that have
* been returned as the results of a query/search operation. */
public void setResults(DocumentNodeData[] resultDocs) {
// Empty out whatever was in our searchResultsTree and in the
// other displays
clear();
// Populate the searchresults tree with each node being a document
// that represents a different search result
DefaultMutableTreeNode newRoot
= new DefaultMutableTreeNode();
for(int i = 0; i < resultDocs.length; i++)
newRoot.add(new DefaultMutableTreeNode(resultDocs[i]));
DefaultTreeModel model
= (DefaultTreeModel)searchResultsTree.getModel();
model.setRoot(newRoot);
// We do not want a root, without a root it is perfect for linear
// display of search results (a.o.t. hierarchical display)
searchResultsTree.setRootVisible(false);
// hierarchical display of the first search result (if its root is set)
if(resultDocs.length > 0)
// array may be empty if there were no resultdocs
this.restructureWithNewRoot(resultDocs[0].getRoot());
}
/** Restructures the docStructureTree with a new rootNode when a different
* search result has been clicked.
* @param rootDocNode - the new root DocumentNodeData whose structure is
* to be displayed in the docStructureTree. */
protected void restructureWithNewRoot(DocumentNodeData rootDocNode)
{
DefaultMutableTreeNode root = null;
if(rootDocNode != null) {
// Create the nodes.
root = new DefaultMutableTreeNode(rootDocNode);
Displays.createNodesForChildren(rootDocNode, root);
}
DefaultTreeModel model = (DefaultTreeModel)docStructureTree.getModel();
docStructureTree.removeAll();
model.setRoot(root);
// empty the contentpane
this.docContentEditPane.setText("");
}
/** Part of the TreeSelectionListener interface. When this is called, an item
* has been clicked in either the searchResultsTree or the docStructureTree.
* This method displays the metadata and textual contents accordingly
* of the selected documentNodeData object. */
public void valueChanged(TreeSelectionEvent e) {
// remove any menuItems in the popup from the previously
// selected docNode
popup.removeAll();
DefaultMutableTreeNode node = null;
if(e.getSource() == searchResultsTree)
node = (DefaultMutableTreeNode)
searchResultsTree.getLastSelectedPathComponent();
else if(e.getSource() == docStructureTree)
node = (DefaultMutableTreeNode)
docStructureTree.getLastSelectedPathComponent();
if(node == null) return;
Object nodeInfo = node.getUserObject();
// after construction there's nothing in the trees, so check for that:
if(nodeInfo == null) return;
// We need to change to a Wait cursor while we load the documentNode
Container c = client.getContentPane();
c.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
DocumentNodeData docNode = (DocumentNodeData)nodeInfo;
// If user had clicked on a searchResult:
if(e.getSource() == this.searchResultsTree && node.isLeaf()) {
this.client.retrieveTitledStructureFor(docNode);
// Now it has set the root and descendents for the
// selected docNode
DocumentNodeData root = docNode.getRoot();
// get the nodestructuredisplay from the client
// nodestructure.restructureWithNewRoot(root, docNode);
this.restructureWithNewRoot(root);
// First ensure the metadata for the docNode is set
client.retrieveAllMetadataFor(docNode);
// now metadata for the document is set
// Now, if metadata viewable, then display the metadata
// for the root document node:
Displays.showMeta(docNode, metanames, metavalues);
// make the client retrieve the content for the docNode
// that's clicked on, and display it in the docContentEditPane
client.retrieveContentFor(docNode);
// JEditorPane can't handle Justify, it centers such paras
// Therefore, left align them instead.
String docContent = docNode.getContent();
if(docContent != null) {
docContent = docContent.replaceAll(
"ALIGN=\"JUSTIFY\"", "ALIGN=\"LEFT\"");
}
this.docContentEditPane.setText(docContent);
this.docContentEditPane.setCaretPosition(0); // set 'cursor' at top
}
// If the user had clicked on any docNode - leaf or otherwise
// - in the docStructureTree panel:
else if(e.getSource() == this.docStructureTree) {
// First ensure the metadata for the docNode is set
client.retrieveAllMetadataFor(docNode);
// now metadata for the document is set
// Display the metadata for this document node:
Displays.showMeta(docNode, metanames, metavalues);
// if the document has NoText set to 1 (doc has no text), then it
// has an image - display this by default
if(docNode.hasNoText() && node.isLeaf() /*&& docNode.canBeImage()*/) {
this.docContentEditPane.setText(
Displays.getImgUrlEnclosedInHtml(docNode.getImgURL()));
this.docContentEditPane.setCaretPosition(0);
}
else { // now we know that the document must have text:
// make the client retrieve the content for the docNode
// that's clicked on, and display it in the docContentEditPane
client.retrieveContentFor(docNode);
// Java's htmlpane does not recognise justified alignment.
// It displays them all centred. So replace all ALIGN="JUSTIFY"
// with ALIGN="LEFT" here. (Not in the DocumentNodeData class,
// because our nodeContent should not be transformed: it will
// display properly in browsers and other html displays).
String docContent = docNode.getContent();
if(docContent != null) {
docContent = docContent.replaceAll(
"ALIGN=\"JUSTIFY\"", "ALIGN=\"LEFT\"");
}
// set the baseURL for digital libraries that use relative paths
// to images and don't take care of base paths themselves
// (GS3 uses the _httpdocimg_ macro to resolve relative urls)
String baseURL = client.getBaseURL();
// TODO: make the docNode itself workout its URL by passing
// baseURL to the docNode?
if(!baseURL.equals("") && docNode.getRoot() != null) {
// "" is the case where dlAPIA = gs3
// where the _httpdocimg_ macro will deal with
// resolving the relative urls into their full ones
// We don't want to set the base and meddle with macro
// for those cases.
// For Fedora's case:
HTMLDocument doc
= (HTMLDocument)this.docContentEditPane.getDocument();
try{
URL url = new URL(baseURL+docNode.getRoot().nodeID+"/");
//System.err.println("url: " + url.toString());
doc.setBase(url);
//docContent = docContent.replaceAll("_httpdocimg_/", "");
LOG.debug(docContent);
}catch(MalformedURLException mex) {
; //nothing to be done, leave the base as it is
}
}
this.docContentEditPane.setText(docContent);
// make sure 'cursor' (actually caret) is at the top
// Don't want it to autoscroll to the bottom of the contentPane
this.docContentEditPane.setCaretPosition(0);
}
}
c.setCursor(Cursor.getDefaultCursor());
}
}