/* * 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.Container; import java.awt.event.ActionEvent; import java.awt.event.ItemEvent; import java.awt.event.ItemListener; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import javax.swing.AbstractButton; import javax.swing.Icon; import javax.swing.JComponent; import javax.swing.JMenuItem; import javax.swing.JPanel; import javax.swing.JTabbedPane; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; import org.mypsycho.beans.Inject; import org.mypsycho.swing.app.Action; import org.mypsycho.swing.app.Application; /** * This Paged frame provides a simple approche to add pages to a single frame * application. * <p> * User can choose to display a tab for each pages. * </p> * * @author PERANSIN Nicolas */ // NOTE: Surprisingly, 'menu bar' property is 'JMenuBar' not 'jMenuBar' @Inject(order={ "actionMap", "JMenuBar", "menuBar", "pageMenuOffset"}) @SuppressWarnings("serial") public class PagedFrame extends MenuFrame { public static final String TABS_VISIBLE_PROP = "tabsVisible"; public static final String SELECTED_PROP = "selected"; public static final String PAGES_PROP = "pages"; public static final String MENU_OFFSET_PROP = "menuOffset"; final Viewer<?> tabsViewer = new Viewer<JTabbedPane>(new JTabbedPane(JTabbedPane.TOP, JTabbedPane.WRAP_TAB_LAYOUT)) { { comp.addChangeListener(new ChangeListener() { public void stateChanged(ChangeEvent e) { if (!dirty) { Page old = null; Page selected = pages.get(comp.getSelectedIndex()); for (Page page : pages) { if (page.menu.isSelected()) { old = page; } page.menu.setSelected(page == selected); } firePropertyChange(SELECTED_PROP, old, selected); } } }); } @Override void select(Page p) { comp.setSelectedComponent(p.getComponent()); } @Override void insert(Page p, int index) { comp.insertTab(p.getTitle(), p.getIcon(), p.getComponent(), p.getTip(), index); if (p.getBackground() != null) { comp.setBackgroundAt(index, p.getBackground()); } if (p.getForeground() != null) { comp.setForegroundAt(index, p.getForeground()); } if (p.getDisabledIcon() != null) { comp.setDisabledIconAt(index, p.getDisabledIcon()); } if (p.getMnemonic() != -1) { comp.setMnemonicAt(index, p.getMnemonic()); } if (p.getDisplayedMnemonicIndex() != -1) { comp.setDisplayedMnemonicIndexAt(index, p.getDisplayedMnemonicIndex()); } comp.setEnabledAt(index, p.isEnabled()); if (p.getTab() != null) { comp.setTabComponentAt(index, p.getTab()); } } @Override boolean contains(Component c) { return comp.indexOfComponent(c) != -1; } @Override boolean containsTab(Component c) { return comp.indexOfTabComponent(c) != -1; } @Override void change(String prop, Page p, Object old) { int index = pages.indexOf(p); if ("title".equals(prop)) { comp.setTitleAt(index, p.getTitle()); } else if ("component".equals(prop)) { comp.setComponentAt(index, p.getComponent()); } else if ("icon".equals(prop)) { comp.setIconAt(index, p.getIcon()); } else if ("tip".equals(prop)) { comp.setToolTipTextAt(index, p.getTip()); } else if ("background".equals(prop)) { comp.setBackgroundAt(index, p.getBackground()); } else if ("foreground".equals(prop)) { comp.setForegroundAt(index, p.getForeground()); } else if ("disabledIcon".equals(prop)) { comp.setDisabledIconAt(index, p.getDisabledIcon()); } else if ("mnemonic".equals(prop)) { comp.setMnemonicAt(index, p.getMnemonic()); } else if ("mnemonicIndex".equals(prop)) { comp.setDisplayedMnemonicIndexAt(index, p.getDisplayedMnemonicIndex()); } else if ("enabled".equals(prop)) { comp.setEnabledAt(index, p.isEnabled()); } else if ("tab".equals(prop)) { comp.setTabComponentAt(index, p.getTab()); } } @Override void remove(Page p) { // if the component does not belong, do nothing comp.remove(p.getComponent()); } }; final Viewer<?> plainViewer = new Viewer<JPanel>(new JPanel(new BorderLayout())) { BorderLayout layout = (BorderLayout) comp.getLayout(); private void fixLayout(Page p) { // BorderLayout does not keep the list of constraint of component // We need to update the contrainst if (p == null) { return; } layout.addLayoutComponent(p.getComponent(), BorderLayout.CENTER); if (p.getTab() != null) { layout.addLayoutComponent(p.getComponent(), BorderLayout.PAGE_START); } else { Component oldTab = layout.getLayoutComponent(BorderLayout.PAGE_START); if (oldTab != null) { layout.removeLayoutComponent(oldTab); } } } @Override void select(Page p) { for (Page other : pages) { other.getComponent().setVisible(other == p); if (other.getTab() != null) { other.getTab().setVisible(other == p); } } fixLayout(p); comp.revalidate(); } @Override void insert(Page p, int index) { p.getComponent().setVisible(pages.size() == 1); comp.add(p.getComponent(), BorderLayout.CENTER); if (p.getBackground() != null) { p.getComponent().setBackground(p.getBackground()); } if (p.getForeground() != null) { p.getComponent().setForeground(p.getForeground()); } p.getComponent().setEnabled(p.isEnabled()); if (p.getTab() != null) { p.getTab().setVisible(pages.size() == 1); comp.add(p.getTab(), BorderLayout.PAGE_START); } fixLayout(getSelected()); comp.revalidate(); comp.repaint(); } @Override boolean contains(Component c) { for (int i = 0; i < comp.getComponentCount(); i++) { if (comp.getComponent(i) == c) { return true; } } return false; } @Override boolean containsTab(Component c) { return contains(c); } @Override void change(String prop, Page p, Object old) { boolean selected = getSelected() == p; if ("component".equals(prop)) { if (old != null) { comp.remove((Component) old); } p.getComponent().setVisible(selected); comp.add(p.getComponent(), BorderLayout.CENTER); } else if ("background".equals(prop)) { p.getComponent().setBackground(p.getBackground()); } else if ("foreground".equals(prop)) { p.getComponent().setForeground(p.getForeground()); } else if ("enabled".equals(prop)) { p.getComponent().setEnabled(p.isEnabled()); } else if ("tab".equals(prop)) { if (old != null) { comp.remove((Component) old); } if (p.getTab() != null) { p.getTab().setVisible(selected); comp.add(p.getTab(), BorderLayout.PAGE_START); } } fixLayout(getSelected()); comp.revalidate(); comp.repaint(); } @Override void remove(Page p) { super.remove(p); if (p.getTab() != null) { comp.remove(p.getTab()); } // fix selection Page selected = null; for (Page other : pages) { if (other.getComponent().isVisible()) { selected = other; break; } } if (selected == null) { for (Page other : pages) { // First enabled if (other.isEnabled()) { selected = other; break; } } if ((selected == null) && !pages.isEmpty()) { selected = pages.get(0); } if (selected != null) { selected.getComponent().setVisible(true); if (selected.getTab() != null) { selected.getTab().setVisible(true); } } } fixLayout(getSelected()); comp.revalidate(); comp.repaint(); } }; List<Page> pages = new ArrayList<Page>(5); Viewer<?> viewer = plainViewer; final PropertyChangeListener pageListener = new PropertyChangeListener() { public void propertyChange(PropertyChangeEvent evt) { viewer.change(evt.getPropertyName(), (Page) evt.getSource(), evt.getOldValue()); } }; final ItemListener menuPagesListener = new ItemListener() { public void itemStateChanged(ItemEvent e) { JMenuItem item = (JMenuItem) e.getSource(); Page found = null; for (Page page : pages) { if (item == page.menu) { found = page; break; } } if (found != null) { if (item.isSelected()) { setSelected(found); } else { for (Page page : pages) { if (page.menu.isSelected()) { // another is selectd return; } } item.setSelected(true); // At least one must be selected } } } }; boolean dirty = false; /** * Using reflection force this object to be public */ public PagedFrame(Application pApp) { super(pApp); setMain(viewer.comp); } // 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)); } public void setConsoleVisible(boolean visible) { dirty = true; try { super.setConsoleVisible(visible); } finally { viewer.fixBorder(); dirty = false; } } /** * 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) { dirty = true; try { super.setNavigation(nav); } finally { viewer.fixBorder(); dirty = false; } } public Page[] getPages() { return pages.toArray(new Page[pages.size()]); } public Page getSelected() { for (Page page : pages) { if (page.getComponent().isVisible()) { return page; } } return null; } public void setSelected(Page page) { Page old = getSelected(); if (old == page) { return; } int index = pages.indexOf(page); if (index == -1) { throw new IllegalArgumentException("Unexpected page"); } viewer.select(page); for (Page other : pages) { other.menu.setSelected(other == page); } firePropertyChange(SELECTED_PROP, old, page); } // true => Container == TabbedPane // false => Container == JPanel CardLayout // By default not visible @Action(selected = TABS_VISIBLE_PROP) public void showTabs(ActionEvent ae) { setTabsVisible(isSourceSelected(ae)); } public boolean isTabsVisible() { return viewer == tabsViewer; } public void setTabsVisible(boolean visible) { boolean old = isTabsVisible(); if (old == visible) { // nothing change return; } Page selected = getSelected(); viewer = (visible) ? tabsViewer : plainViewer; dirty = true; setMain(viewer.comp); int index = 0; for (Page p : pages) { viewer.insert(p, index); index++; } if (selected != null) { viewer.select(selected); } dirty = false; viewer.fixBorder(); firePropertyChange(TABS_VISIBLE_PROP, old, visible); } /** * Do something TODO. * <p>Details of the function.</p> * * @param comp * @return */ public Page pageOf(Component comp) { for (Page page : pages) { if (comp == page.getComponent()) { return page; } } return null; } public Page addPage(String title, Component comp) throws IndexOutOfBoundsException { return addPage(title, null, comp); } public Page addPage(String title, Icon icon, Component comp) throws IndexOutOfBoundsException { return addPage(title, icon, null, comp, null, null); } public Page addPage(String title, Icon icon, Icon disabledIcon, Component comp, String tip, Component tab) throws IndexOutOfBoundsException { return insertPage(title, icon, disabledIcon, comp, tip, tab, pages.size()); } public Page insertPage(String title, Component comp, int index) throws IndexOutOfBoundsException { return insertPage(title, null, null, comp, null, null, index); } public Page insertPage(String title, Icon icon, Component comp, int index) throws IndexOutOfBoundsException { return insertPage(title, icon, null, comp, null, null, index); } public Page insertPage(String title, Icon icon, Icon disabledIcon, Component comp, String tip, Component tab, int index) throws IndexOutOfBoundsException { // new JTabbedPane().insertTab(title, icon, component, tip, index) Page[] old = getPages(); Page existing = pageOf(comp); if (existing != null) { remove(existing); index--; } Page added = new Page(this, title, icon, disabledIcon, comp, tip, tab); pages.add(index, added); viewer.insert(added, index); added.addPropertyChangeListener(pageListener); // insert in menu addPageMenu(added); firePropertyChange("pages", old, getPages()); return added; } Integer[] pageMenuOffset = null; /** * Returns the pageMenuOffset. * * @return the pageMenuOffset */ public Integer[] getPageMenuOffset() { return pageMenuOffset; } /** * Sets the pageMenuOffset. * * @param pageMenuOffset the pageMenuOffset to set */ public void setPageMenuOffset(Integer[] offset) { Integer[] old = pageMenuOffset; if ((offset != null) && Arrays.asList(offset).contains(null)) { offset = null; } else { offset = offset.clone(); } if (Arrays.equals(old, offset)) { return; } if (old != null) { for (Page page : pages) { JMenuItem menu = page.menu; if (menu.getParent() != null) { menu.getParent().remove(menu); menu.removeItemListener(menuPagesListener); } } } pageMenuOffset = offset; if (offset != null) { for (Page page : pages) { addPageMenu(page); } } firePropertyChange(MENU_OFFSET_PROP, old, offset); } void addPageMenu(Page page) { // This method is very sensible to concurrent access // page.menu.setSelected(page == getSelected()); page.menu.setSelected(page.getComponent().isVisible()); int position = pages.indexOf(page); if (position == -1) { throw new IllegalArgumentException("Invalid page"); } if (pageMenuOffset == null) { return; } Container menuContainer = getPageMenuParent(); if (menuContainer != null) { int offset = pageMenuOffset[pageMenuOffset.length - 1]; page.menu.addItemListener(menuPagesListener); menuContainer.add(page.menu, offset + position); } } Container getPageMenuParent() { if (pageMenuOffset == null) { return null; } Container menu = getJMenuBar(); for (int i = 0; i < pageMenuOffset.length - 1; i++) { Integer offset = pageMenuOffset[i]; menu = (Container) menu.getComponent(offset); } return menu; } public void remove(Page page) { int position = pages.indexOf(page); if (position == -1) { throw new IllegalStateException(); } Page[] old = getPages(); dirty = true; page.removePropertyChangeListener(pageListener); pages.remove(position); page.clean(); viewer.remove(page); // remove from container JComponent menu = page.menu; if (menu.getParent() != null) { menu.getParent().remove(menu); page.menu.removeItemListener(menuPagesListener); } dirty = false; firePropertyChange(PAGES_PROP, old, getPages()); } abstract class Viewer<C extends JComponent> { final C comp; Viewer(C c) { comp = c; } public void fixBorder() { // if (mainPane == viewer.comp) { // comp.setBorder(BorderFactory.createLoweredBevelBorder()); // } else { // comp.setBorder(null); // } } abstract void select(Page p); abstract void insert(Page p, int index); abstract boolean contains(Component c); abstract boolean containsTab(Component c); abstract void change(String prop, Page p, Object old); void remove(Page p) { // if the component does not belong, do nothing comp.remove(p.getComponent()); } } } // endclass StudioFrame