package org.geogebra.desktop.gui.toolbar;
import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Cursor;
import java.awt.FlowLayout;
import java.awt.FontMetrics;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.SystemColor;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ComponentEvent;
import java.awt.event.ComponentListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.text.BreakIterator;
import java.util.ArrayList;
import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.BorderFactory;
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JLabel;
import javax.swing.JMenuItem;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.JToolBar;
import javax.swing.SwingConstants;
import javax.swing.border.Border;
import org.geogebra.common.euclidian.EuclidianConstants;
import org.geogebra.common.gui.view.properties.PropertiesView;
import org.geogebra.common.kernel.ModeSetter;
import org.geogebra.common.main.App;
import org.geogebra.common.main.OptionType;
import org.geogebra.common.util.StringUtil;
import org.geogebra.desktop.gui.GuiManagerD;
import org.geogebra.desktop.gui.dialog.HelpDialog;
import org.geogebra.desktop.gui.view.properties.PropertiesViewD;
import org.geogebra.desktop.main.AppD;
import org.geogebra.desktop.main.LocalizationD;
import org.geogebra.desktop.util.GuiResourcesD;
/**
* Container for one or multiple toolbars. Takes care of fundamental things such
* as the help text.
*
* @author Florian Sonner
*/
public class ToolbarContainer extends JPanel implements ComponentListener {
private static final long serialVersionUID = 1L;
/**
* Show help text at the right.
*/
private static boolean showHelp = false;
/**
* Application instance.
*/
AppD app;
LocalizationD loc;
/**
* True if this is the main toolbar which also contains the undo buttons.
*/
private boolean isMain;
/**
* Help panel.
*/
private JPanel toolbarHelpPanel;
public JPanel getToolbarHelpPanel() {
if (toolbarHelpPanel == null) {
buildToolbarHelpPanel();
}
return toolbarHelpPanel;
}
/**
* Label in the help panel showing the current mode name.
*/
JLabel modeNameLabel;
/**
* Panel which contains all toolbars.
*/
private ToolbarPanel toolbarPanel;
public JToolBar getToolbarPanel() {
JToolBar tb = new JToolBar();
tb.add(toolbarPanel);
return tb;
}
/**
* Toolbars added to this container.
*/
private ArrayList<ToolbarD> toolbars;
/**
* The active toolbar.
*/
private int activeToolbar;
protected int orientation = SwingConstants.NORTH;
private JPanel gluePanel;
/**
* Create a new toolbar container.
*
* @param app
* application
* @param isMain
* If this container is used in the main panel, where additional
* functions are added to the toolbar (undo buttons)
*/
public ToolbarContainer(AppD app, boolean isMain) {
super(new BorderLayout(10, 0));
this.app = app;
this.loc = app.getLocalization();
this.isMain = isMain;
// add general toolbar
toolbars = new ArrayList<ToolbarD>(1);
if (isMain) {
addToolbar(new ToolbarD(app));
activeToolbar = -1;
}
// if the container is resized we have to check if the
// help text still has enough space.
addComponentListener(this);
}
/**
* Build the toolbar container GUI.
*/
public void buildGui() {
removeAll();
// add visible top border in main toolbar container
if (isMain) {
Border outsideBorder = null;
if (orientation == SwingConstants.NORTH
|| orientation == SwingConstants.SOUTH) {
outsideBorder = BorderFactory.createMatteBorder(1, 0, 0, 0,
SystemColor.controlShadow);
} else if (orientation == SwingConstants.EAST) {
outsideBorder = BorderFactory.createMatteBorder(0, 1, 0, 0,
SystemColor.controlShadow);
} else if (orientation == SwingConstants.WEST) {
outsideBorder = BorderFactory.createMatteBorder(0, 0, 0, 1,
SystemColor.controlShadow);
}
setBorder(BorderFactory.createCompoundBorder(outsideBorder,
BorderFactory.createEmptyBorder(2, 2, 1, 2)));
} else {
setBorder(BorderFactory.createEmptyBorder(2, 2, 1, 2));
}
toolbarPanel = new ToolbarPanel();
updateToolbarPanel();
// setActiveToolbar also makes the selected toolbar visible,
// therefore the following line is not completely useless ;)
setActiveToolbar(activeToolbar);
// wrap toolbar to be vertically centered
gluePanel = new JPanel();
gluePanel.setLayout(new BoxLayout(gluePanel, BoxLayout.Y_AXIS));
gluePanel.add(Box.createVerticalGlue());
gluePanel.add(toolbarPanel);
gluePanel.add(Box.createVerticalGlue());
// add glue panel and button panel according to the orientation
addPanels();
revalidate();
}
private void addPanels() {
// show help panel
if (orientation == SwingConstants.NORTH
|| orientation == SwingConstants.SOUTH) {
add(gluePanel, loc.borderWest());
add(getGridButtonPanel(), loc.borderEast());
} else {
add(gluePanel, BorderLayout.NORTH);
add(getGridButtonPanel(), BorderLayout.SOUTH);
}
if (showHelp && (orientation == SwingConstants.NORTH
|| orientation == SwingConstants.SOUTH)) {
add(getToolbarHelpPanel(), BorderLayout.CENTER);
updateHelpText();
}
}
boolean showHelpBar = false;
JLabel lblTest = new JLabel();
private JPanel gridButtonPanel;
private JPanel buildToolbarHelpPanel() {
// mode label
modeNameLabel = new JLabel();
modeNameLabel.setAlignmentX(LEFT_ALIGNMENT);
// put into panel to
if (toolbarHelpPanel == null) {
toolbarHelpPanel = new JPanel();
toolbarHelpPanel.setLayout(
new BoxLayout(toolbarHelpPanel, BoxLayout.Y_AXIS));
} else {
toolbarHelpPanel.removeAll();
}
JPanel p = new JPanel(new BorderLayout());
p.add(modeNameLabel, loc.borderWest());
if (isMain) {
JPanel p2 = new JPanel(new FlowLayout(FlowLayout.LEFT, 0, 0));
if (orientation == SwingConstants.EAST
|| orientation == SwingConstants.WEST) {
// p2.add(undoPanel);
}
p.add(p2, loc.borderEast());
}
toolbarHelpPanel.add(Box.createVerticalGlue());
toolbarHelpPanel.add(modeNameLabel);
// toolbarHelpPanel.add(p);
toolbarHelpPanel.add(Box.createVerticalGlue());
Border insideBorder = BorderFactory.createEmptyBorder(2, 10, 2, 0);
Border outsideBorder = BorderFactory.createMatteBorder(0, 0, 0, 0,
SystemColor.controlShadow);
toolbarHelpPanel.setBorder(BorderFactory
.createCompoundBorder(outsideBorder, insideBorder));
return toolbarHelpPanel;
}
public void updateGridButtonPanel() {
buildGui();
// if (gridButtonPanel == null) {
// return;
// }
//
// gridButtonPanel.removeAll();
// // build it actually
// getGridButtonPanel();
// addHelpPanel();
}
private JPanel getGridButtonPanel() {
int iconSize = (int) Math.round(app.getScaledIconSize() * 0.75);
// magnify size for some 3D inputs
if (iconSize < AppD.HUGE_UNDO_BUTTON_SIZE
&& app.useHugeGuiForInput3D()) {
iconSize = AppD.HUGE_UNDO_BUTTON_SIZE;
}
// undo button
AbstractAction undoAction = ((GuiManagerD) app.getGuiManager())
.getUndoAction();
undoAction.putValue(Action.SMALL_ICON,
app.getScaledIcon(GuiResourcesD.MENU_EDIT_UNDO, iconSize));
undoAction.putValue("enabled", false);
JButton btnUndo = new JButton(undoAction);
String text = loc.getMenuTooltip("Undo");
btnUndo.setText(null);
btnUndo.setToolTipText(text);
btnUndo.setAlignmentX(RIGHT_ALIGNMENT);
// redo button
AbstractAction redoAction = ((GuiManagerD) app.getGuiManager())
.getRedoAction();
redoAction.putValue(Action.SMALL_ICON,
app.getScaledIcon(GuiResourcesD.MENU_EDIT_REDO, iconSize));
JButton btnRedo = new JButton(redoAction);
text = loc.getMenuTooltip("Redo");
btnRedo.setText(null);
btnRedo.setToolTipText(text);
btnRedo.setAlignmentX(RIGHT_ALIGNMENT);
// properties button
final JButton btnProperties = new JButton(
app.getScaledIcon(GuiResourcesD.MENU_OPTIONS, iconSize));
btnProperties.setFocusPainted(false);
btnProperties.setBorderPainted(false);
btnProperties.setContentAreaFilled(false);
btnProperties.setToolTipText(loc.getPlainTooltip("Preferences"));
btnProperties.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent arg0) {
PropertiesMenu pm = new PropertiesMenu();
if (orientation == SwingConstants.NORTH) {
pm.show(btnProperties,
-pm.getPreferredSize().width
+ btnProperties.getWidth(),
btnProperties.getHeight());
} else if (orientation == SwingConstants.WEST) {
pm.show(btnProperties, 0, -pm.getPreferredSize().height);
} else {
pm.show(btnProperties,
-pm.getPreferredSize().width
+ btnProperties.getWidth(),
-pm.getPreferredSize().height);
}
}
});
// help button
JButton btnHelp = new JButton(
app.getScaledIcon(GuiResourcesD.MENU_HELP, iconSize));
btnHelp.setFocusPainted(false);
btnHelp.setBorderPainted(false);
btnHelp.setContentAreaFilled(false);
btnHelp.setToolTipText(loc.getMenuTooltip("Help"));
btnHelp.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent arg0) {
new HelpDialog(app).openToolHelp();
}
});
gridButtonPanel = new JPanel(new BorderLayout());
if (orientation == SwingConstants.NORTH
|| orientation == SwingConstants.SOUTH) {
JPanel gridPanel = new JPanel(new GridBagLayout());
GridBagConstraints c = new GridBagConstraints();
c.weightx = 1;
c.weighty = 0;
if (loc.isRightToLeftReadingOrder()) {
c.gridx = 0;
c.gridy = 0;
gridPanel.add(btnRedo, c);
c.gridx = 1;
c.gridy = 0;
gridPanel.add(btnUndo, c);
c.gridx = 0;
c.gridy = 1;
gridPanel.add(btnProperties, c);
c.gridx = 1;
c.gridy = 1;
gridPanel.add(btnHelp, c);
} else {
c.gridx = 0;
c.gridy = 0;
gridPanel.add(btnUndo, c);
c.gridx = 1;
c.gridy = 0;
gridPanel.add(btnRedo, c);
c.gridx = 0;
c.gridy = 1;
gridPanel.add(btnHelp, c);
c.gridx = 1;
c.gridy = 1;
gridPanel.add(btnProperties, c);
}
gridButtonPanel.add(gridPanel, BorderLayout.NORTH);
} else {
JPanel gridPanel = new JPanel();
btnHelp.setAlignmentX(RIGHT_ALIGNMENT);
btnProperties.setAlignmentX(RIGHT_ALIGNMENT);
gridPanel.setLayout(new BoxLayout(gridPanel, BoxLayout.Y_AXIS));
gridPanel.add(btnUndo);
gridPanel.add(btnRedo);
gridPanel.add(btnHelp);
gridPanel.add(btnProperties);
gridButtonPanel.add(gridPanel, BorderLayout.SOUTH);
}
// add small bottom margin when toolbar is vertical
if (orientation == SwingConstants.EAST
|| orientation == SwingConstants.WEST) {
gridButtonPanel
.setBorder(BorderFactory.createEmptyBorder(0, 0, 5, 0));
}
return gridButtonPanel;
}
/**
* Select a mode.
*
* @param mode
* new mode
* @return mode that was actually selected
*/
public int setMode(int mode) {
int ret = -1;
for (ToolbarD toolbar : toolbars) {
int tmp = toolbar.setMode(mode);
// this will be the actual mode set
if (getViewId(toolbar) == activeToolbar) {
ret = tmp;
}
}
updateHelpText(mode);
return ret;
}
public void setOrientation(int orientation) {
// TODO: Handle toolbar orientation for undocked panels
this.orientation = orientation;
int barOrientation = SwingConstants.HORIZONTAL;
if (orientation == SwingConstants.EAST
|| orientation == SwingConstants.WEST) {
barOrientation = SwingConstants.VERTICAL;
}
for (ToolbarD toolbar : toolbars) {
toolbar.setOrientation(barOrientation);
}
}
/**
* Marks the passed toolbar as active and makes it visible.
*
* @param toolbar
* toolbar
*/
public int setActiveToolbar(ToolbarD toolbar) {
int ret = setActiveToolbar(getViewId(toolbar));
setOrientation(app.getToolbarPosition());
return ret;
}
/**
* Marks the toolbar with the passed id as active and makes it visible.
*
* @param id
* The view ID
*/
public int setActiveToolbar(int id) {
if (activeToolbar == id) {
return app.getMode();
}
activeToolbar = id;
// the toolbar activate toolbar may be set even before the GUI is
// initialized
if (toolbarPanel != null) {
toolbarPanel.show(Integer.toString(id));
// prevent data analysis view from setting mode twice (hack)
if (id != App.VIEW_DATA_ANALYSIS) {
app.setMode(getToolbar(id).getSelectedMode(),
ModeSetter.DOCK_PANEL);
return getToolbar(id).getSelectedMode();
}
}
return app.getMode();
}
/**
*
* @return id of view which is setting the active toolbar
*/
public int getActiveToolbar() {
return activeToolbar;
}
/**
* Update toolbars.
*/
public void updateToolbarPanel() {
toolbarPanel.removeAll();
for (ToolbarD toolbar : toolbars) {
toolbar.buildGui();
toolbarPanel.add(toolbar, Integer.toString(getViewId(toolbar)));
}
toolbarPanel.show(Integer.toString(activeToolbar));
// updateGridButtonPanel();
}
/**
* Adds a toolbar to this container. Use updateToolbarPanel() to update the
* GUI after all toolbar changes were made.
*
* @param toolbar
* toolbar to be added
*/
public void addToolbar(ToolbarD toolbar) {
if (toolbar == null) {
return;
}
toolbars.add(toolbar);
}
/**
* Removes a toolbar from this container. Use {@link #updateToolbarPanel()}
* to update the GUI after all toolbar changes were made. If the removed
* toolbar was the active toolbar as well the active toolbar is changed to
* the general (but again, {@link #updateToolbarPanel()} has to be called
* for a visible effect).
*
* @param toolbar
* toolbar to be removed
*/
public void removeToolbar(ToolbarD toolbar) {
if (toolbar == null) {
return;
}
toolbars.remove(toolbar);
if (getViewId(toolbar) == activeToolbar) {
activeToolbar = -1;
}
}
/**
* Get toolbar associated to passed view ID.
*
* @param viewId
* view ID
* @return toolbar for given view
*/
public ToolbarD getToolbar(int viewId) {
for (ToolbarD toolbar : toolbars) {
if (getViewId(toolbar) == viewId) {
return toolbar;
}
}
return null;
}
/**
* @param toolbar
* @return The ID of the dock panel associated with the passed toolbar or -1
*/
private static int getViewId(ToolbarD toolbar) {
return (toolbar.getDockPanel() != null
? toolbar.getDockPanel().getViewId() : -1);
}
/**
* Old width of this container.
*/
private int oldWidth;
/**
* Check if we still can display a help text.
*/
@Override
public void componentResized(ComponentEvent e) {
if (getWidth() != oldWidth) {
oldWidth = getWidth();
// update help text if we show one
if (ToolbarContainer.showHelp) {
updateHelpText();
}
}
}
private MouseAdapter helpMouseAdapter;
/**
* Update the help text.
*/
public void updateHelpText() {
updateHelpText(app.getMode());
}
/**
* Update the help text.
*
* @param mode
* mode
*/
public void updateHelpText(int mode) {
if (modeNameLabel == null) {
return;
}
String toolName = app.getToolName(mode);
String helpText = app.getToolHelp(mode);
// get wrapped toolbar help text
String wrappedText = wrappedModeText(toolName, helpText,
toolbarHelpPanel);
modeNameLabel.setText(wrappedText);
resolveMouseListener(mode);
// tooltip
modeNameLabel.setToolTipText(app.getToolTooltipHTML(mode));
toolbarHelpPanel.revalidate();
}
/**
* Add mouse listener to open help if clicked + change cursor. Only removes
* old listener for custom tools.
*
* @param mode
*/
private void resolveMouseListener(final int mode) {
if (modeNameLabel.getMouseListeners().length > 0) {
modeNameLabel.removeMouseListener(helpMouseAdapter);
}
if (mode > EuclidianConstants.MACRO_MODE_ID_OFFSET) {
return;
}
final String modeName = EuclidianConstants.getModeText(mode);
if (!("".equals(modeName))) {
helpMouseAdapter = new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
if (e.getClickCount() >= 1) {
new HelpDialog(app).openToolHelp(mode);
}
}
@Override
public void mouseEntered(MouseEvent e) {
Cursor c = new Cursor(Cursor.HAND_CURSOR);
modeNameLabel.setCursor(c);
}
@Override
public void mouseExited(MouseEvent e) {
modeNameLabel.setCursor(Cursor.getDefaultCursor());
}
};
modeNameLabel.addMouseListener(helpMouseAdapter);
}
}
/**
* Returns mode text and toolbar help as html text with line breaks to fit
* in the given panel.
*/
private String wrappedModeText(String modeName, String helpText,
JPanel panel) {
FontMetrics fm = getFontMetrics(app.getBoldFont());
// check width of panel
int panelWidth = panel.getWidth();
int charWidth = fm.stringWidth("W");
panelWidth = panelWidth - charWidth; // needed for correct line breaks
if (panelWidth <= 0) {
return "";
}
// show no more than 2 lines
int maxLines = 2 * fm.getHeight() < panel.getHeight() ? 2 : 1;
// Math.min(2, Math.round(panel.getHeight() / (float) fm.getHeight()));
StringBuilder sbToolName = new StringBuilder();
sbToolName.append("<html><b>");
// check if mode name itself fits
// mode name
BreakIterator iterator = BreakIterator.getWordInstance(app.getLocale());
iterator.setText(modeName);
int start = iterator.first();
int end = iterator.next();
int nextEnd = iterator.next();
int line = 1;
int len = 0;
while (end != BreakIterator.DONE) {
String word = modeName.substring(start, end);
int spaceForDots = nextEnd == BreakIterator.DONE ? 0
: fm.stringWidth(" ...");
if (len + fm.stringWidth(word)
+ (line != maxLines ? 0 : spaceForDots) > panelWidth) {
if (++line > maxLines
|| fm.stringWidth(word) + spaceForDots > panelWidth) {
sbToolName.append(" ...");
sbToolName.append("</b></html>");
return sbToolName.toString();
}
sbToolName.append("<br>");
len = fm.stringWidth(word);
} else {
len += fm.stringWidth(word);
}
sbToolName.append(StringUtil.toHTMLString(word));
start = end;
end = nextEnd;
nextEnd = iterator.next();
}
sbToolName.append("</b>");
// mode help text
StringBuilder sbToolHelp = new StringBuilder();
fm = getFontMetrics(app.getPlainFont());
// try to put help text into single line
if (line < maxLines && fm.stringWidth(helpText) < panelWidth) {
++line;
sbToolHelp.append("<br>");
sbToolHelp.append(StringUtil.toHTMLString(helpText));
} else {
sbToolHelp.append(": ");
iterator.setText(helpText);
start = iterator.first();
end = iterator.next();
while (end != BreakIterator.DONE) {
String word = helpText.substring(start, end);
if (len + fm.stringWidth(word) > panelWidth) {
if (++line > maxLines) {
// show tool help only when it can be completely shown
sbToolHelp.setLength(0);
// sbToolHelp.append(Unicode.ellipsis);
break;
}
sbToolHelp.append("<br>");
len = fm.stringWidth(word);
} else {
len += fm.stringWidth(word);
}
sbToolHelp.append(StringUtil.toHTMLString(word));
start = end;
end = iterator.next();
}
}
// show tool help only when it can be completely shown
sbToolName.append(sbToolHelp);
sbToolName.append("</html>");
return sbToolName.toString();
}
/**
* @return The first toolbar in our list, used for the general toolbar in
* the main toolbar container.
*/
public ToolbarD getFirstToolbar() {
if (toolbars.size() > 0) {
return toolbars.get(0);
}
return null;
}
/**
* @return If the help text is displayed.
*/
public static boolean showHelp() {
return showHelp;
}
/**
* @param showHelp
* true to enable tool help (in the right part)
*/
public static void setShowHelp(boolean showHelp) {
ToolbarContainer.showHelp = showHelp;
}
// Component listener methods
@Override
public void componentShown(ComponentEvent e) { /* do nothing */
}
@Override
public void componentHidden(ComponentEvent e) {/* do nothing */
}
@Override
public void componentMoved(ComponentEvent e) { /* do nothing */
}
/*************************************************************
* Simple panel which displays a single component at a time. Just use
* ToolbarPanel::add(Component, String) to add components, use
* ToolbarPanel::show(String) to show a component.
*/
private static class ToolbarPanel extends JPanel {
private static final long serialVersionUID = 1L;
/**
* Just sets the layout of this panel.
*/
public ToolbarPanel() {
super(new FlowLayout(FlowLayout.LEFT, 0, 0));
}
/**
* Shows the component with the given name
*
* @param name
* view ID as string
*/
public void show(String name) {
for (int i = 0; i < getComponentCount(); ++i) {
Component comp = getComponent(i);
if (comp != null) {
if (comp.getName() != null) {
comp.setVisible(comp.getName().equals(name));
} else {
comp.setVisible(false);
}
}
}
revalidate();
}
/**
* Adds a component and hide it automatically.
*
* @param comp
* component to be added
* @param name
* name for the component
*/
public void add(Component comp, String name) {
super.add(comp);
comp.setName(name);
comp.setVisible(false);
}
}
private class PropertiesMenu extends JPopupMenu {
private static final long serialVersionUID = 1L;
public PropertiesMenu() {
initMenu();
}
private void initMenu() {
for (final OptionType type : OptionType.values()) {
// if(type==OptionType.EUCLIDIAN3D){
// continue;
// }
String menuText = PropertiesView.getTypeStringSimple(loc, type);
ImageIcon ic = PropertiesViewD.getTypeIcon(app, type);
JMenuItem item = new JMenuItem(menuText, ic);
// not available if no objects yet
if (type == OptionType.OBJECTS && app.getKernel().isEmpty()) {
item.setDisabledIcon(app.getEmptyIcon());
item.setEnabled(false);
}
item.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent arg0) {
openPropertiesView(type);
}
});
add(item);
item.setVisible(
PropertiesView.isOptionPanelAvailable(app, type));
}
}
protected void openPropertiesView(OptionType type) {
int viewId = App.VIEW_PROPERTIES;
((PropertiesView) ((GuiManagerD) app.getGuiManager())
.getPropertiesView()).setOptionPanel(type);
((GuiManagerD) app.getGuiManager()).setShowView(true, viewId,
false);
}
}
}