/*
* Copyright (C) 2011 Peransin Nicolas. All rights reserved.
* Use is subject to license terms.
*/
package org.mypsycho.swing.app.beans;
import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.event.ActionEvent;
import java.io.IOException;
import java.io.PrintStream;
import java.net.URL;
import java.util.List;
import javax.swing.AbstractButton;
import javax.swing.ActionMap;
import javax.swing.JComponent;
import javax.swing.JEditorPane;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JSplitPane;
import javax.swing.JTextArea;
import javax.swing.JTextPane;
import javax.swing.JToolBar;
import javax.swing.Scrollable;
import javax.swing.text.JTextComponent;
import org.mypsycho.beans.Inject;
import org.mypsycho.swing.TextAreaStream;
import org.mypsycho.swing.TextPaneStream;
import org.mypsycho.swing.app.Action;
import org.mypsycho.swing.app.Application;
import org.mypsycho.swing.app.utils.SwingHelper;
/**
*
*
* @author PERANSIN Nicolas
*/
//as actions are referenced by toolbar or menu, they must be injected first
@Inject(order="actionMap" )
public class MenuFrame extends JFrame {
private static final long serialVersionUID = -1515838027794229212L;
protected static final String EMPTY_STATUS_BAR = " "; // In a JLabel, empty text
public static final String MAIN_PROP = "main";
public static final String CONSOLE_VISIBLE_PROP = "consoleVisible";
public static final String STATUS_VISIBLE_PROP = "statusVisible";
public static final String CONSOLE_PROP = "console";
public static final String NAVIGATION_PROP = "navigation";
public static final String STATUS_PROP = "status";
public static final String STATUS_BAR_PROP = "statusBar";
public static final String TOOL_BARS_PROP = "toolbars";
// Note: The number of frame is limited in a application.
// Lazy initialisation is useless here.
ActionMap actionMap = new ActionMap();
// private JComponent mainPane = null; // navSplit or consoleSplit or viewer.comp
JComponent main = null;
JComponent statusBar = null;
JToolBar[] toolBars = null; // An array is compatible for typed injection
final JSplitPane consoleSplit = new JSplitPane(JSplitPane.VERTICAL_SPLIT);
final JSplitPane navSplit = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT);
/** Divider location for console split pane */
int consoleDivLoc = -1;
int navDivLoc = -1;
protected JComponent console = null;
protected JComponent navigation = null;
final Application app;
/**
* A Menu frame without main component.
*
* @param pApp
*/
public MenuFrame(Application pApp) {
app = pApp;
// Building main
setConsole(createDefaultConsole());
setStatusBar(createDefaultStatus());
// MessagesPart position
consoleSplit.setDividerLocation(0.75);
consoleDivLoc = consoleSplit.getDividerLocation();
}
/**
* A Menu frame with main component.
*
* @param pApp
* @param compo
*/
public MenuFrame(Application pApp, JComponent compo) {
this(pApp);
setMain(compo);
}
/**
* Do something TODO.
* <p>Details of the function.</p>
*
* @return
*/
protected JComponent createDefaultStatus() {
return new StatusBar(getApplication(), getApplication().getContext().getTaskMonitor());
}
private Component replaceContentPaneChild(Component newChild, String constraint) {
Component oldChild = getContentPaneChild(constraint);
if (oldChild != null) {
getContentPane().remove(oldChild);
}
if (newChild != null) {
getContentPane().add(newChild, constraint);
}
return oldChild;
}
/**
* Returns the main {@link JComponent} for this View.
*
* @return The {@code component} for this View
* @see #setComponent
*/
public JComponent getMain() {
return main;
}
/**
* Sets the single main Component for this View. It's added to the
* {@code BorderLayout.CENTER} of the rootPane's contentPane. If
* the component property was already set, the old component is removed
* first.
* <p>
* This is a bound property. The default value is null.
*
* @param pComponent The {@code component} for this View
* @see #getComponent
*/
public void setMain(JComponent pComponent) {
Component oldValue = this.main;
main = pComponent;
if (isConsoleVisible()) {
consoleSplit.setTopComponent(main);
} else if (navigation != null) {
navSplit.setRightComponent(main);
} else {
replaceContentPaneChild(main, BorderLayout.CENTER);
}
firePropertyChange(MAIN_PROP, oldValue, main);
}
public void clearConsole() {
JComponent out = console;
if (out instanceof JTextComponent) {
((JTextComponent) out).setText("");
}
}
public void setConsole(JComponent comp) {
JComponent oldValue = console;
console = comp;
outStream = null;
if (comp instanceof Scrollable) {
consoleSplit.setBottomComponent(new JScrollPane(console));
} else {
consoleSplit.setBottomComponent(console);
}
firePropertyChange(CONSOLE_PROP, oldValue, console);
}
protected Component getContentPaneChild(String pConstraint) {
BorderLayout layout = (BorderLayout) getContentPane().getLayout();
return layout.getLayoutComponent(pConstraint);
}
protected JComponent createDefaultConsole() {
// JTextPane text = new SystemTextPane();
return new JTextPane();
}
public JComponent getConsole() {
return console;
}
transient PrintStream outStream = null;
public PrintStream getConsoleStream() {
if (outStream != null) {
return outStream;
}
JComponent out = console;
if (out instanceof JTextArea) {
outStream = new TextAreaStream((JTextArea) out);
} else if (out instanceof JTextPane) {
outStream =new TextPaneStream((JTextPane) out);
} else {
outStream = System.out;
}
return outStream;
}
// public Frame getFrame() { return this; }
static boolean isSourceSelected(ActionEvent ae) {
return ((AbstractButton) ae.getSource()).isSelected();
}
@Action(selected = CONSOLE_VISIBLE_PROP)
public void showConsole(ActionEvent ae) {
setConsoleVisible(isSourceSelected(ae));
}
/**
* Returns the navigation.
*
* @return the navigation
*/
public JComponent getNavigation() {
return navigation;
}
/**
* Sets the navigation.
*
* @param navigation the navigation to set
*/
public void setNavigation(JComponent nav) {
JComponent old = navigation;
if (old == nav) {
return;
}
navigation = nav;
navSplit.setLeftComponent(nav);
if ((old == null) && (nav != null)) { // show, mainPane != navSplit
Component mainPane = replaceContentPaneChild(navSplit, BorderLayout.CENTER);
navSplit.setRightComponent(mainPane);
if (navDivLoc != -1) {
navSplit.setDividerLocation(navDivLoc);
} else {
navSplit.setDividerLocation(0.25); // default proportion
}
}
if ((old != null) && (nav == null)) { // hide, mainPane = navSplit
navDivLoc = navSplit.getDividerLocation();
replaceContentPaneChild(navSplit.getRightComponent(), BorderLayout.CENTER);
}
firePropertyChange(NAVIGATION_PROP, old, navigation);
invalidate();
}
public boolean isConsoleVisible() {
boolean nav = navigation != null;
return (nav && (consoleSplit == navSplit.getLeftComponent()))
|| (!nav && (consoleSplit == getContentPaneChild(BorderLayout.CENTER)));
}
public void setConsoleVisible(boolean visible) {
boolean old = isConsoleVisible();
if (old == visible) {
return;
}
if (visible) { // showConsole
consoleSplit.setTopComponent(main);
if (navigation != null) {
navSplit.setLeftComponent(consoleSplit);
} else { // (mainPane == viewer.comp)
replaceContentPaneChild(consoleSplit, BorderLayout.CENTER);
}
consoleSplit.setDividerLocation(consoleDivLoc);
} else { // hideConsole
consoleDivLoc = consoleSplit.getDividerLocation();
if (navigation != null) {
navSplit.setLeftComponent(main);
} else { // (mainPane == viewedPane)
replaceContentPaneChild(main, BorderLayout.CENTER);
}
}
validate();
repaint();
// Action will be updated
firePropertyChange(CONSOLE_VISIBLE_PROP, old, !old);
}
public void showHelp() {
URL help = SwingHelper.getDefaultResource(app, "help", "README");
JOptionPane option;
if (help != null) {
try {
// JEditorPane: not editable, read ASCII, hmtl and RTF
option = new JOptionPane(new JScrollPane(new JEditorPane(help)));
option.setPreferredSize(new Dimension(800, 600));
} catch (IOException e) {
JTextArea text = new JTextArea(40, 100);
text.setEditable(false);
e.printStackTrace(new TextAreaStream(text));
option = new JOptionPane(new JScrollPane(text), JOptionPane.ERROR_MESSAGE);
}
} else {
option = new JOptionPane("No readme file", JOptionPane.WARNING_MESSAGE);
}
app.showOption(this, "help", option);
}
public void showAbout() {
app.showOption(this, "about", new AboutPane(app));
}
public Application getApplication() {
return app;
}
@Action(selected = STATUS_VISIBLE_PROP)
public void showStatus(ActionEvent ae) {
setStatusVisible(isSourceSelected(ae));
}
public boolean isStatusVisible() {
return statusBar.isVisible();
}
public void setStatusVisible(boolean v) {
boolean old = isStatusVisible();
if (v == old) {
return;
}
statusBar.setVisible(v);
firePropertyChange(STATUS_VISIBLE_PROP, old, v);
}
public String getStatus() {
if (!(statusBar instanceof JLabel)) {
return null;
}
return ((JLabel) statusBar).getText();
}
public void setStatus(String label) {
// Label with empty text can be an issue in some LnF + Container
if ((label == null) || label.isEmpty()) {
label = EMPTY_STATUS_BAR;
}
String old = null;
if (statusBar instanceof JLabel) {
JLabel comp = (JLabel) statusBar;
old = comp.getText();
if (label.equals(old)) {
return;
}
comp.setText(label);
} else if (statusBar instanceof StatusBar) {
StatusBar comp = (StatusBar) statusBar;
old = comp.getMessage();
if (label.equals(old)) {
return;
}
comp.setMessage(label);
} else {
return;
}
firePropertyChange(STATUS_PROP, old, label);
}
/**
* Returns the Status bar for this View.
*
* @return The status bar {@link JComponent} for this View
*/
public JComponent getStatusBar() {
return statusBar;
}
/**
* Sets the status bar for this View. The status bar is a generic {@link JComponent}.
*
* @param statusBar The status bar {@link JComponent} for this View
*/
public void setStatusBar(JComponent statusBar) {
this.statusBar = statusBar;
Component old = replaceContentPaneChild(this.statusBar, BorderLayout.PAGE_END);
firePropertyChange(STATUS_BAR_PROP, old, this.statusBar);
}
/**
* Gets the first tool bar for this View.
*
* @return The first {@link JToolBar} for this View
* @see #setToolBars
* @see #getToolBars
* @see #setToolBar
*/
public final JToolBar getToolBar() {
JToolBar[] toolBars = getToolBars();
return toolBars == null ? null : toolBars[0];
}
/**
* Sets the only tool bar for this View.
* <p>
* This is a bound property to <code>toolbars</code>.
* </p>
*
* @param toolBar The {@link JToolBar} for this view. If {@code null} resets the tool bar.
* @see #getToolBar()
* @see #setToolBars(List)
* @see #getToolBars()
*/
public final void setToolBar(JToolBar toolBar) {
setToolBars((toolBar != null) ? new JToolBar[] { toolBar } : null);
}
/**
* Returns the list of tool bars for this View
* <p>
*
*
* @return The list of tool bars
*/
public JToolBar[] getToolBars() {
return toolBars;
}
/**
* Sets the tool bars for this View.
* <p>
* This is a bound property. The default value is null : no toolbar.
* </p>
*
* @param toolBars
* @see #setToolBar(JToolBar)
* @see #getToolBars()
*/
public void setToolBars(JToolBar... newToolBars) {
JToolBar[] oldValue = getToolBars();
toolBars = ((newToolBars != null) && (newToolBars.length > 0))
? newToolBars.clone() : null;
// Create/identify comp to add
JComponent newChild = null;
if (toolBars != null) {
if (toolBars.length == 1) {
newChild = toolBars[0];
// Floatable are not compatible with status
toolBars[0].setFloatable(false);
} else if (toolBars.length > 1) {
newChild = new JPanel(); // FlowLayout
for (JToolBar toolBar : toolBars) {
newChild.add(toolBar);
toolBar.setFloatable(false);
}
}
}
replaceContentPaneChild(newChild, BorderLayout.PAGE_START);
firePropertyChange(TOOL_BARS_PROP, oldValue, toolBars);
}
/**
* Returns the actions.
*
* @return the actions
*/
public ActionMap getActionMap() {
return actionMap;
}
} // endclass StudioFrame