/* * Copyright (C) 2012 Jason Gedge <http://www.gedge.ca> * * This file is part of the OpGraph 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 3 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. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package ca.gedge.opgraph.app.components; import java.awt.Color; import java.awt.Cursor; import java.awt.FlowLayout; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.Point; import java.awt.Rectangle; import java.awt.RenderingHints; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import javax.swing.BorderFactory; import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.border.EmptyBorder; import ca.gedge.opgraph.app.util.GUIHelper; import ca.gedge.opgraph.util.Breadcrumb; import ca.gedge.opgraph.util.BreadcrumbListener; import ca.gedge.opgraph.util.Pair; /** * A breadcrumb component. A breadcrumb shows a linear navigation history. * * @param <S> the type of state in the breadcrumb * @param <V> the type of value in the breadcrumb */ public class BreadcrumbViewer<S, V> extends JPanel { /** The width of the right-hand side of a breadcrumb */ private static final int RIGHT_WIDTH = 10; private static class StateComponent<S, V> extends JLabel { /** The state for this component */ public final S state; public StateComponent(S state, V value) { super(value == null ? "" : value.toString()); this.state = state; setOpaque(false); setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)); if(state != null) setBorder(new EmptyBorder(5, 0, 5, 2*RIGHT_WIDTH)); } @Override protected void paintBorder(Graphics gfx) { if(state == null) { super.paintBorder(gfx); } else { Graphics2D g = (Graphics2D)gfx; g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); //g.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_ON); final int x = (getWidth() - 1) - RIGHT_WIDTH; final int y = getHeight() / 2; g.setColor(Color.BLACK); g.drawLine(x, getHeight() - 1, getWidth() - 1, y); //g.drawLine(x, 0, getWidth() - 1, y); g.setColor(Color.WHITE); g.drawLine(x - 1, getHeight() - 1, getWidth() - 2, y); g.drawLine(x - 1, 0, getWidth() - 2, y); } } @Override protected void paintComponent(Graphics g) { final Rectangle rect = GUIHelper.getInterior(this); final Point p = GUIHelper.centerTextInRectangle(g, getText(), rect); g.setColor(Color.BLACK); g.drawString(getText(), p.x + 1, p.y + 1); g.setColor(Color.WHITE); g.drawString(getText(), p.x, p.y); } } /** The breadcrumb this component is viewing */ private Breadcrumb<S, V> breadcrumb; /** The breadcrumb listener for this component */ private BreadcrumbListener<S, V> listener = new BreadcrumbListener<S, V>() { @Override public void stateChanged(S oldState, S newState) { // Find the component associated with the new state and remove // all state components after it synchronized(getTreeLock()) { removeAll(); for(Pair<S, V> statePair : breadcrumb) { final S state = statePair.getFirst(); final StateComponent<S, V> comp = new StateComponent<S, V>(state, statePair.getSecond()); comp.addMouseListener(new MouseAdapter() { @Override public void mouseClicked(MouseEvent e) { breadcrumb.gotoState(state); } }); add(comp, 0); } } revalidate(); repaint(); } @Override public void stateAdded(final S state, V value) { // synchronized(getTreeLock()) { // final StateComponent<S, V> comp = new StateComponent<S, V>(state, value); // comp.addMouseListener(new MouseAdapter() { // @Override // public void mouseClicked(MouseEvent e) { // breadcrumb.gotoState(state); // } // }); // // add(comp); // } // // revalidate(); // repaint(); } }; /** * Default constructor * * @param breadcrumb the breadcrumb to display */ public BreadcrumbViewer(Breadcrumb<S, V> breadcrumb) { super(new FlowLayout(FlowLayout.LEFT, RIGHT_WIDTH, 0)); add(new StateComponent<S, V>(null, null)); setBreadcrumb(breadcrumb); setOpaque(true); setBackground(Color.DARK_GRAY); setBorder(BorderFactory.createMatteBorder(0, 0, 1, 0, Color.BLACK)); } /** * Gets the breadcrumb this component is viewing. * * @return the breadcrumb, or <code>null</code> if no breadcrumb being viewed */ public Breadcrumb<S, V> getBreadcrumb() { return breadcrumb; } /** * Sets the breadcrumb this component is viewing. * * @param breadcrumb the breadcrumb */ public void setBreadcrumb(Breadcrumb<S, V> breadcrumb) { if(breadcrumb != this.breadcrumb) { // Remove old information if(this.breadcrumb != null) { this.breadcrumb.removeBreadcrumbListener(listener); while(getComponentCount() > 1) remove(1); } this.breadcrumb = breadcrumb; // Add new information if(this.breadcrumb != null) { this.breadcrumb.addBreadcrumbListener(listener); for(Pair<S, V> state : this.breadcrumb) listener.stateAdded(state.getFirst(), state.getSecond()); } } } }