package org.limewire.ui.swing.components; import java.awt.AWTEvent; import java.awt.Color; import java.awt.Component; import java.awt.Cursor; import java.awt.Dimension; import java.awt.EventQueue; import java.awt.Font; import java.awt.Insets; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.InputEvent; import java.awt.event.ItemEvent; import java.awt.event.ItemListener; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import javax.swing.AbstractAction; import javax.swing.AbstractButton; import javax.swing.Action; import javax.swing.BorderFactory; import javax.swing.ButtonGroup; import javax.swing.Icon; import javax.swing.JButton; import javax.swing.JLabel; import javax.swing.JPopupMenu; import javax.swing.JToggleButton; import javax.swing.SwingUtilities; import javax.swing.JToggleButton.ToggleButtonModel; import net.miginfocom.swing.MigLayout; import org.jdesktop.application.Resource; import org.jdesktop.swingx.JXBusyLabel; import org.jdesktop.swingx.JXPanel; import org.jdesktop.swingx.icon.EmptyIcon; import org.limewire.ui.swing.util.FontUtils; import org.limewire.ui.swing.util.GuiUtils; /** * A fancy 'tab' for use in a {@link FancyTabList}. */ public class FancyTab extends JXPanel { private final TabActionMap tabActions; private final AbstractButton mainButton; private final AbstractButton removeButton; private final JLabel busyLabel; private final JLabel additionalText; private final FancyTabProperties props; private static enum TabState { BACKGROUND, ROLLOVER, SELECTED; } private TabState currentState; private boolean mouseInside; private Icon removeEmptyIcon; @Resource private Icon removeActiveIcon; @Resource private Icon removeActiveRolloverIcon; @Resource private Icon removeActivePressedIcon; @Resource private Icon removeInactiveIcon; @Resource private Icon removeInactiveRolloverIcon; @Resource private Icon removeInactivePressedIcon; public FancyTab(TabActionMap actionMap, ButtonGroup group, FancyTabProperties fancyTabProperties) { GuiUtils.assignResources(this); removeEmptyIcon = new EmptyIcon(removeActiveIcon.getIconWidth(), removeActiveIcon.getIconHeight()); this.tabActions = actionMap; this.props = fancyTabProperties; this.mainButton = createMainButton(); this.additionalText = createAdditionalText(); this.removeButton = createRemoveButton(); this.busyLabel = createBusyLabel(); if (group != null) { group.add(mainButton); } mainButton.addItemListener(new ItemListener() { @Override public void itemStateChanged(ItemEvent e) { changeState(e.getStateChange() == ItemEvent.SELECTED ? TabState.SELECTED : TabState.BACKGROUND); } }); setOpaque(false); setToolTipText(getTooltip()); HighlightListener highlightListener = new HighlightListener(); if (props.isRemovable()) { removeButton.addMouseListener(highlightListener); } addMouseListener(highlightListener); mainButton.addMouseListener(highlightListener); updateButtons(false); changeState(isSelected() ? TabState.SELECTED : TabState.BACKGROUND); setLayout(new MigLayout("insets 0 0 0 0, fill, gap 0")); add(mainButton, "gapafter 4, gapbefore 6, growy, aligny 50%, width min(pref,50):pref:max, cell 1 0"); add(additionalText, "gapafter 4, aligny 50%, cell 2 0, hidemode 3"); add(busyLabel, "gapbefore 4, gapafter 6, gapbottom 1, aligny 50%, alignx right, cell 3 0, hidemode 3"); add(removeButton, "gapbefore 4, gapafter 6, gapbottom 1, aligny 50%, alignx right, cell 3 0, hidemode 3"); } @Override public String toString() { return "FancyTab for: " + getTitle() + ", " + super.toString(); } @Override public Insets getInsets() { if (props == null || props.getInsets() == null) return super.getInsets(); return props.getInsets(); } private boolean isBusy() { return Boolean.TRUE.equals(tabActions.getMainAction().getValue(TabActionMap.BUSY_KEY)); } JLabel createBusyLabel() { final JXBusyLabel busy = new ColoredBusyLabel(new Dimension(12, 12), Color.decode("#acacac"), Color.decode("#545454")); busy.setVisible(false); if (isBusy()) { busy.setBusy(true); busy.setVisible(true); } tabActions.getMainAction().addPropertyChangeListener(new PropertyChangeListener() { @Override public void propertyChange(PropertyChangeEvent evt) { if (evt.getPropertyName().equals(TabActionMap.BUSY_KEY)) { boolean on = Boolean.TRUE.equals(evt.getNewValue()); busy.setBusy(on); busy.setVisible(on); updateButtons(mouseInside); } } }); return busy; } JLabel createAdditionalText() { final JLabel label = new JLabel(); label.setVisible(false); if (tabActions.getMoreTextAction() != null) { label.setOpaque(false); label.setFont(mainButton.getFont()); String name = (String) tabActions.getMoreTextAction().getValue(Action.NAME); if (name != null && name.length() > 0) { label.setText("(" + name + ")"); label.setVisible(true); } tabActions.getMoreTextAction().addPropertyChangeListener(new PropertyChangeListener() { @Override public void propertyChange(PropertyChangeEvent evt) { if (evt.getPropertyName().equals(Action.NAME)) { if (evt.getNewValue() != null) { String newValue = (String) evt.getNewValue(); label.setText("(" + newValue + ")"); label.setVisible(true); } else { label.setVisible(false); } } } }); } return label; } JButton createRemoveButton() { JButton button = new JButton(); button.setIcon(removeEmptyIcon); button.setRolloverIcon(removeActiveRolloverIcon); button.setPressedIcon(removeActivePressedIcon); button.setBorderPainted(false); button.setContentAreaFilled(false); button.setFocusPainted(false); button.setRolloverEnabled(true); button.setBorder(BorderFactory.createEmptyBorder()); button.setMargin(new Insets(0, 0, 0, 0)); button.setAction(tabActions.getRemoveAction()); button.setActionCommand(TabActionMap.REMOVE_COMMAND); button.setHideActionText(true); button.setVisible(false); if (removeButton != null) { for (ActionListener listener : removeButton.getActionListeners()) { if (listener == tabActions.getRemoveAction()) { // Ignore the remove action -- it's added implicitly. continue; } button.addActionListener(listener); } } return button; } AbstractButton createMainButton() { final AbstractButton button = new JToggleButton(); button.setModel(new NoToggleModel()); button.setAction(tabActions.getMainAction()); button.setActionCommand(TabActionMap.SELECT_COMMAND); button.setBorder(BorderFactory.createEmptyBorder()); button.setFocusPainted(false); button.setContentAreaFilled(false); button.setMargin(new Insets(0, 0, 0, 0)); button.setToolTipText(getTooltip()); button.setBorderPainted(false); button.setFocusPainted(false); button.setRolloverEnabled(true); button.setOpaque(false); if (props.getTextFont() != null) { button.setFont(props.getTextFont()); } if(Boolean.TRUE.equals(tabActions.getMainAction().getValue(TabActionMap.NEW_HINT))) { FontUtils.bold(button); } else { FontUtils.plain(button); } tabActions.getMainAction().addPropertyChangeListener(new PropertyChangeListener() { @Override public void propertyChange(PropertyChangeEvent evt) { if(evt.getPropertyName().equals(TabActionMap.NEW_HINT)) { if(Boolean.TRUE.equals(evt.getNewValue())) { FontUtils.bold(button); } else { FontUtils.plain(button); } } } }); return button; } public FancyTabProperties getProperties() { return props; } /** Gets the action underlying this tab. */ public TabActionMap getTabActionMap() { return tabActions; } public void remove() { removeButton.doClick(0); } void select() { mainButton.doClick(0); } void addRemoveActionListener(ActionListener listener) { removeButton.addActionListener(listener); } void removeRemoveActionListener(ActionListener listener) { removeButton.removeActionListener(listener); } /** Selects this tab. */ void setSelected(boolean selected) { mainButton.setSelected(selected); } /** Returns true if this tab is selected. */ boolean isSelected() { return mainButton.isSelected(); } /** Sets the foreground color of the tab. */ void setButtonForeground(Color color) { mainButton.setForeground(color); additionalText.setForeground(color); } void setUnderlineEnabled(boolean enabled) { if (enabled) { FontUtils.underline(mainButton); } else { FontUtils.removeUnderline(mainButton); } } /** Returns true if the tab is currently highlighted (in a rollover). */ boolean isHighlighted() { return currentState == TabState.ROLLOVER; } /** Removes this tab from the button group. */ void removeFromGroup(ButtonGroup group) { group.remove(mainButton); } public void setTextFont(Font font) { if (mainButton != null) { mainButton.setFont(font); } if (additionalText != null) { additionalText.setFont(font); } } private void updateButtons(boolean mouseInside) { this.mouseInside = mouseInside; if(mouseInside || !isBusy()) { if(props.isRemovable()) { removeButton.setVisible(true); } else { removeButton.setVisible(false); } busyLabel.setVisible(false); } else { // isBusy == true busyLabel.setVisible(true); removeButton.setVisible(false); } } private void changeState(TabState tabState) { if (currentState != tabState) { this.currentState = tabState; switch(tabState) { case SELECTED: setUnderlineEnabled(false); mainButton.setForeground(props.getSelectionColor()); additionalText.setForeground(props.getSelectionColor()); this.setBackgroundPainter(props.getSelectedPainter()); removeButton.setIcon(removeActiveIcon); removeButton.setRolloverIcon(removeActiveRolloverIcon); removeButton.setPressedIcon(removeActivePressedIcon); break; case BACKGROUND: setUnderlineEnabled(props.isUnderlineEnabled()); mainButton.setForeground(props.getNormalColor()); additionalText.setForeground(props.getNormalColor()); this.setBackgroundPainter(props.getNormalPainter()); removeButton.setIcon(removeEmptyIcon); break; case ROLLOVER: setUnderlineEnabled(props.isUnderlineEnabled()); setBackgroundPainter(props.getHighlightPainter()); removeButton.setIcon(removeInactiveIcon); removeButton.setRolloverIcon(removeInactiveRolloverIcon); removeButton.setPressedIcon(removeInactivePressedIcon); break; } } } public String getTitle() { return (String)tabActions.getMainAction().getValue(Action.NAME); } private String getTooltip() { return (String)tabActions.getMainAction().getValue(Action.LONG_DESCRIPTION); } private void showPopup(MouseEvent e) { JPopupMenu menu = new JPopupMenu(); for (Action action : getTabActionMap().getRightClickActions()) { if(action == TabActionMap.SEPARATOR){ menu.addSeparator(); } else { menu.add(action); } } if (getComponentCount() != 0 && props.isRemovable()) { menu.addSeparator(); } if (props.isRemovable()) { menu.add(getTabActionMap().getRemoveOthers()); menu.add(getTabActionMap().getRemoveAll()); menu.addSeparator(); menu.add(new AbstractAction(props.getCloseOneText()) { @Override public void actionPerformed(ActionEvent e) { remove(); } }); } menu.show((Component)e.getSource(), e.getX() + 3, e.getY() + 3); } private class HighlightListener extends MouseAdapter { @Override public void mouseEntered(MouseEvent e) { updateButtons(true); if (!isSelected() && mainButton.isEnabled()) { getTopLevelAncestor().setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)); changeState(TabState.ROLLOVER); } } @Override public void mouseExited(MouseEvent e) { updateButtons(false); getTopLevelAncestor().setCursor(Cursor.getDefaultCursor()); if (!isSelected()) { changeState(TabState.BACKGROUND); } } @Override public void mouseClicked(MouseEvent e) { if (props.isRemovable() && SwingUtilities.isMiddleMouseButton(e)) { remove(); } else if (!(e.getSource() instanceof AbstractButton) && SwingUtilities.isLeftMouseButton(e)) { select(); } else if (e.isPopupTrigger()) { showPopup(e); } } @Override public void mousePressed(MouseEvent e) { if (e.isPopupTrigger()) { showPopup(e); } } @Override public void mouseReleased(MouseEvent e) { if (e.isPopupTrigger()) { showPopup(e); } } } private static class NoToggleModel extends ToggleButtonModel { @Override public void setPressed(boolean b) { if ((isPressed() == b) || !isEnabled()) { return; } // This is different than the super in that // we only go from false -> true, not true -> false. if (!b && isArmed() && !isSelected()) { setSelected(true); } if (b) { stateMask |= PRESSED; } else { stateMask &= ~PRESSED; } fireStateChanged(); if (!isPressed() && isArmed()) { int modifiers = 0; AWTEvent currentEvent = EventQueue.getCurrentEvent(); if (currentEvent instanceof InputEvent) { modifiers = ((InputEvent)currentEvent).getModifiers(); } else if (currentEvent instanceof ActionEvent) { modifiers = ((ActionEvent)currentEvent).getModifiers(); } fireActionPerformed( new ActionEvent(this, ActionEvent.ACTION_PERFORMED, getActionCommand(), EventQueue.getMostRecentEventTime(), modifiers)); } } } }