org.pushingpixels.flamingo.api.ribbon.JRibbon Maven / Gradle / Ivy
/*
* Copyright (c) 2005-2010 Flamingo Kirill Grouchnikov. All Rights Reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* o Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* o Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* o Neither the name of Flamingo Kirill Grouchnikov nor the names of
* its contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
* OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package org.pushingpixels.flamingo.api.ribbon;
import java.awt.Component;
import java.awt.event.ActionListener;
import java.util.*;
import javax.swing.*;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import org.pushingpixels.flamingo.api.common.*;
import org.pushingpixels.flamingo.api.common.icon.ResizableIcon;
import org.pushingpixels.flamingo.internal.ui.ribbon.BasicRibbonUI;
import org.pushingpixels.flamingo.internal.ui.ribbon.RibbonUI;
/**
* The ribbon component.
*
*
* The ribbon has the following major parts:
*
*
* - Ribbon tasks added with {@link #addTask(RibbonTask)}
* - Contextual ribbon task groups added with
* {@link #addContextualTaskGroup(RibbonContextualTaskGroup)}
* - Application menu button set by
* {@link #setApplicationMenu(RibbonApplicationMenu)}
* - Taskbar panel populated by {@link #addTaskbarComponent(Component)}
* - Help button set by {@link #configureHelp(ResizableIcon, ActionListener)}
*
*
*
* While multiple ribbon tasks can be added to the ribbon, only one is visible
* at any given time. This task is called the selected task.
* Tasks can be switched with the task buttons placed along the top part of the
* ribbon. Once a task has been added to the ribbon, it cannot be removed.
*
*
*
* The contextual ribbon task groups allow showing and hiding ribbon tasks based
* on the current selection in the application. For example, Word only shows the
* table tasks when a table is selected in the document. By default, tasks
* belonging to the groups adde by
* {@link #addContextualTaskGroup(RibbonContextualTaskGroup)} are not visible.
* To show the tasks belonging to the specific group, call
* {@link #setVisible(RibbonContextualTaskGroup, boolean)} API. Note that you
* can have multiple task groups visible at the same time.
*
*
*
* The application menu button is a big round button shown in the top left
* corner of the ribbon. If the
* {@link #setApplicationMenu(RibbonApplicationMenu)} is not called, or called
* with the null
value, the application menu button is not shown,
* and ribbon task buttons are shifted to the left.
*
*
*
* The taskbar panel allows showing controls that are visible no matter what
* ribbon task is selected. To add a taskbar component use the
* {@link #addTaskbarComponent(Component)} API. The taskbar panel lives to the
* right of the application menu button. Taskbar components can be removed with
* the {@link #removeTaskbarComponent(Component)} API.
*
*
*
* The ribbon can be minimized in one of the following ways:
*
*
* - Calling {@link #setMinimized(boolean)} with
true
.
* - User double-clicking on a task button.
* - User pressing
Ctrl+F1
key combination.
*
*
*
* A minimized ribbon shows the application menu button, taskbar panel, task
* buttons and help button, but not the ribbon bands of the selected task.
* Clicking a task button shows the ribbon bands of that task in a popup
* without shifting the application content down.
*
*
* @author Kirill Grouchnikov
*/
public class JRibbon extends JComponent {
/**
* The general tasks.
*
* @see #addTask(RibbonTask)
* @see #getTaskCount()
* @see #getTask(int)
*/
private ArrayList tasks;
/**
* The contextual task groups.
*
* @see #addContextualTaskGroup(RibbonContextualTaskGroup)
* @see #setVisible(RibbonContextualTaskGroup, boolean)
* @see #isVisible(RibbonContextualTaskGroup)
* @see #getContextualTaskGroupCount()
* @see #getContextualTaskGroup(int)
*/
private ArrayList contextualTaskGroups;
/**
* The taskbar components (to the right of the application menu button).
*
* @see #addTaskbarComponent(Component)
* @see #getTaskbarComponents()
* @see #removeTaskbarComponent(Component)
*/
private ArrayList taskbarComponents;
/**
* Bands of the currently shown task.
*/
private ArrayList bands;
/**
* Currently selected (shown) task.
*/
private RibbonTask currentlySelectedTask;
/**
* Help icon. When not null
, the ribbon will display a help
* button at the far right of the tab area.
*
* @see #helpActionListener
* @see #configureHelp(ResizableIcon, ActionListener)
* @see #getHelpIcon()
*/
private ResizableIcon helpIcon;
/**
* When the {@link #helpIcon} is not null
, this listener will
* be invoked when the user activates the help button.
*
* @see #configureHelp(ResizableIcon, ActionListener)
* @see #getHelpActionListener()
*/
private ActionListener helpActionListener;
/**
* Visibility status of the contextual task group. Must contain a value for
* each group in {@link #contextualTaskGroups}.
*
* @see #setVisible(RibbonContextualTaskGroup, boolean)
* @see #isVisible(RibbonContextualTaskGroup)
*/
private Map groupVisibilityMap;
/**
* The application menu.
*
* @see #setApplicationMenu(RibbonApplicationMenu)
* @see #getApplicationMenu()
*/
private RibbonApplicationMenu applicationMenu;
/**
* The rich tooltip of {@link #applicationMenu} button.
*
* @see #applicationMenu
* @see #setApplicationMenuRichTooltip(RichTooltip)
* @see #getApplicationMenuRichTooltip()
*/
private RichTooltip applicationMenuRichTooltip;
/**
* The key tip of {@link #applicationMenu} button.
*
* @see #applicationMenu
* @see #setApplicationMenuKeyTip(String)
* @see #getApplicationMenuKeyTip()
*/
private String applicationMenuKeyTip;
/**
* Indicates whether the ribbon is currently minimized.
*
* @see #setMinimized(boolean)
* @see #isMinimized()
*/
private boolean isMinimized;
/**
* The host ribbon frame. Is null
when the ribbon is not hosted
* in a {@link JRibbonFrame}.
*/
private JRibbonFrame ribbonFrame;
/**
* The UI class ID string.
*/
public static final String uiClassID = "RibbonUI";
/**
* Creates a new empty ribbon. Applications are highly encouraged to use
* {@link JRibbonFrame} and access the ribbon with
* {@link JRibbonFrame#getRibbon()} API.
*/
public JRibbon() {
this.tasks = new ArrayList();
this.contextualTaskGroups = new ArrayList();
this.taskbarComponents = new ArrayList();
this.bands = new ArrayList();
this.currentlySelectedTask = null;
this.groupVisibilityMap = new HashMap();
updateUI();
}
/**
* Creates an empty ribbon for the specified ribbon frame.
*
* @param ribbonFrame
* Host ribbon frame.
*/
JRibbon(JRibbonFrame ribbonFrame) {
this();
this.ribbonFrame = ribbonFrame;
}
/**
* Adds the specified taskbar component to this ribbon.
*
* @param comp
* The taskbar component to add.
* @see #removeTaskbarComponent(Component)
* @see #getTaskbarComponents()
*/
public synchronized void addTaskbarComponent(Component comp) {
if (comp instanceof AbstractCommandButton) {
AbstractCommandButton button = (AbstractCommandButton) comp;
button.setDisplayState(CommandButtonDisplayState.SMALL);
button.setGapScaleFactor(0.5);
button.setFocusable(false);
}
this.taskbarComponents.add(comp);
this.fireStateChanged();
}
/**
* Removes the specified taskbar component from this ribbon.
*
* @param comp
* The taskbar component to remove.
* @see #addTaskbarComponent(Component)
* @see #getTaskbarComponents()
*/
public synchronized void removeTaskbarComponent(Component comp) {
this.taskbarComponents.remove(comp);
this.fireStateChanged();
}
/**
* Adds the specified task to this ribbon.
*
* @param task
* The ribbon task to add.
* @see #addContextualTaskGroup(RibbonContextualTaskGroup)
* @see #getTaskCount()
* @see #getTask(int)
*/
public synchronized void addTask(RibbonTask task) {
task.setRibbon(this);
this.tasks.add(task);
if (this.tasks.size() == 1) {
this.setSelectedTask(task);
}
this.fireStateChanged();
}
/**
* Configures the help button of this ribbon.
*
* @param helpIcon
* The icon for the help button.
* @param helpActionListener
* The action listener for the help button.
* @see #getHelpIcon()
* @see #getHelpActionListener()
*/
public synchronized void configureHelp(ResizableIcon helpIcon,
ActionListener helpActionListener) {
this.helpIcon = helpIcon;
this.helpActionListener = helpActionListener;
this.fireStateChanged();
}
/**
* Returns the icon for the help button. Will return null
if
* the help button has not been configured with the
* {@link #configureHelp(ResizableIcon, ActionListener)} API.
*
* @return The icon for the help button.
* @see #configureHelp(ResizableIcon, ActionListener)
* @see #getHelpActionListener()
*/
public ResizableIcon getHelpIcon() {
return this.helpIcon;
}
/**
* Returns the action listener for the help button. Will return
* null
if the help button has not been configured with the
* {@link #configureHelp(ResizableIcon, ActionListener)} API.
*
* @return The action listener for the help button.
* @see #configureHelp(ResizableIcon, ActionListener)
* @see #getHelpIcon()
*/
public ActionListener getHelpActionListener() {
return this.helpActionListener;
}
/**
* Adds the specified contextual task group to this ribbon.
*
* @param group
* Task group to add.
* @see #addTask(RibbonTask)
* @see #setVisible(RibbonContextualTaskGroup, boolean)
* @see #isVisible(RibbonContextualTaskGroup)
*/
public synchronized void addContextualTaskGroup(
RibbonContextualTaskGroup group) {
group.setRibbon(this);
this.contextualTaskGroups.add(group);
this.groupVisibilityMap.put(group, Boolean.valueOf(false));
this.fireStateChanged();
}
/**
* Returns the number of regular tasks in this
ribbon.
*
* @return Number of regular tasks in this
ribbon.
* @see #getTask(int)
* @see #addTask(RibbonTask)
*/
public synchronized int getTaskCount() {
return this.tasks.size();
}
/**
* Retrieves the regular task at specified index.
*
* @param index
* Task index.
* @return Task that matches the specified index.
* @see #getTaskCount()
* @see #addTask(RibbonTask)
*/
public synchronized RibbonTask getTask(int index) {
return this.tasks.get(index);
}
/**
* Returns the number of contextual task groups in this
ribbon.
*
* @return Number of contextual task groups in this
ribbon.
* @see #addContextualTaskGroup(RibbonContextualTaskGroup)
* @see #getContextualTaskGroup(int)
*/
public synchronized int getContextualTaskGroupCount() {
return this.contextualTaskGroups.size();
}
/**
* Retrieves contextual task group at specified index.
*
* @param index
* Group index.
* @return Group that matches the specified index.
* @see #addContextualTaskGroup(RibbonContextualTaskGroup)
* @see #getContextualTaskGroupCount()
*/
public synchronized RibbonContextualTaskGroup getContextualTaskGroup(
int index) {
return this.contextualTaskGroups.get(index);
}
/**
* Selects the specified task. The task can be either regular (added with
* {@link #addTask(RibbonTask)}) or a task in a visible contextual task
* group (added with
* {@link #addContextualTaskGroup(RibbonContextualTaskGroup)}. Fires a
* selectedTask
property change event.
*
* @param task
* Task to select.
* @throws IllegalArgumentException
* If the specified task is not in the ribbon or not visible.
* @see #getSelectedTask()
*/
public synchronized void setSelectedTask(RibbonTask task) {
boolean valid = this.tasks.contains(task);
if (!valid) {
for (int i = 0; i < this.getContextualTaskGroupCount(); i++) {
RibbonContextualTaskGroup group = this
.getContextualTaskGroup(i);
if (!this.isVisible(group))
continue;
for (int j = 0; j < group.getTaskCount(); j++) {
if (group.getTask(j) == task) {
valid = true;
break;
}
}
if (valid)
break;
}
}
if (!valid) {
throw new IllegalArgumentException(
"The specified task to be selected is either not "
+ "part of this ribbon or not marked as visible");
}
for (AbstractRibbonBand ribbonBand : this.bands) {
ribbonBand.setVisible(false);
}
this.bands.clear();
for (int i = 0; i < task.getBandCount(); i++) {
AbstractRibbonBand ribbonBand = task.getBand(i);
ribbonBand.setVisible(true);
this.bands.add(ribbonBand);
}
RibbonTask old = this.currentlySelectedTask;
this.currentlySelectedTask = task;
this.revalidate();
this.repaint();
this
.firePropertyChange("selectedTask", old,
this.currentlySelectedTask);
}
/**
* Returns the currently selected task.
*
* @return The currently selected task.
* @see #setSelectedTask(RibbonTask)
*/
public synchronized RibbonTask getSelectedTask() {
return this.currentlySelectedTask;
}
/*
* (non-Javadoc)
*
* @see javax.swing.JComponent#updateUI()
*/
@Override
public void updateUI() {
if (UIManager.get(getUIClassID()) != null) {
setUI(UIManager.getUI(this));
} else {
setUI(new BasicRibbonUI());
}
for (Component comp : this.taskbarComponents) {
SwingUtilities.updateComponentTreeUI(comp);
}
}
/**
* Returns the UI object which implements the L&F for this component.
*
* @return a RibbonUI
object
* @see #setUI
*/
public RibbonUI getUI() {
return (RibbonUI) ui;
}
/*
* (non-Javadoc)
*
* @see javax.swing.JComponent#getUIClassID()
*/
@Override
public String getUIClassID() {
return uiClassID;
}
/**
* Gets an unmodifiable list of all taskbar components of this
* ribbon.
*
* @return All taskbar components of this
ribbon.
* @see #addTaskbarComponent(Component)
* @see #removeTaskbarComponent(Component)
*/
public synchronized List getTaskbarComponents() {
return Collections.unmodifiableList(this.taskbarComponents);
}
/**
* Adds the specified change listener to track changes to this ribbon.
*
* @param l
* Change listener to add.
* @see #removeChangeListener(ChangeListener)
*/
public void addChangeListener(ChangeListener l) {
this.listenerList.add(ChangeListener.class, l);
}
/**
* Removes the specified change listener from tracking changes to this
* ribbon.
*
* @param l
* Change listener to remove.
* @see #addChangeListener(ChangeListener)
*/
public void removeChangeListener(ChangeListener l) {
this.listenerList.remove(ChangeListener.class, l);
}
/**
* Notifies all registered listeners that the state of this ribbon has
* changed.
*/
protected void fireStateChanged() {
// Guaranteed to return a non-null array
Object[] listeners = this.listenerList.getListenerList();
// Process the listeners last to first, notifying
// those that are interested in this event
ChangeEvent event = new ChangeEvent(this);
for (int i = listeners.length - 2; i >= 0; i -= 2) {
if (listeners[i] == ChangeListener.class) {
((ChangeListener) listeners[i + 1]).stateChanged(event);
}
}
}
/**
* Sets the visibility of ribbon tasks in the specified contextual task
* group. Visibility of all ribbon tasks in the specified group is affected.
* Note that the ribbon can show ribbon tasks of multiple groups at the same
* time.
*
* @param group
* Contextual task group.
* @param isVisible
* If true
, all ribbon tasks in the specified group
* will be visible. If false
, all ribbon tasks in
* the specified group will be hidden.
* @see #isVisible(RibbonContextualTaskGroup)
*/
public synchronized void setVisible(RibbonContextualTaskGroup group,
boolean isVisible) {
this.groupVisibilityMap.put(group, Boolean.valueOf(isVisible));
// special handling of selected tab
if (!isVisible) {
boolean isSelectedBeingHidden = false;
for (int i = 0; i < group.getTaskCount(); i++) {
if (this.getSelectedTask() == group.getTask(i)) {
isSelectedBeingHidden = true;
break;
}
}
if (isSelectedBeingHidden) {
this.setSelectedTask(this.getTask(0));
}
}
this.fireStateChanged();
this.revalidate();
SwingUtilities.getWindowAncestor(this).repaint();
}
/**
* Returns the visibility of ribbon tasks in the specified contextual task
* group.
*
* @param group
* Contextual task group.
* @return true
if the ribbon tasks in the specified group are
* visible, false
otherwise.
*/
public synchronized boolean isVisible(RibbonContextualTaskGroup group) {
return this.groupVisibilityMap.get(group);
}
/**
* Sets the application menu for this ribbon. If null
is
* passed, the application menu button is hidden. Fires an
* applicationMenu
property change event.
*
* @param applicationMenu
* The new application menu. Can be null
.
* @see #getApplicationMenu()
*/
public synchronized void setApplicationMenu(
RibbonApplicationMenu applicationMenu) {
RibbonApplicationMenu old = this.applicationMenu;
if (old != applicationMenu) {
this.applicationMenu = applicationMenu;
if (this.applicationMenu != null) {
this.applicationMenu.setFrozen();
}
this.firePropertyChange("applicationMenu", old,
this.applicationMenu);
}
}
/**
* Returns the application menu of this ribbon.
*
* @return The application menu of this ribbon.
* @see #setApplicationMenu(RibbonApplicationMenu)
*/
public synchronized RibbonApplicationMenu getApplicationMenu() {
return this.applicationMenu;
}
/**
* Sets the rich tooltip of the application menu button. Fires an
* applicationMenuRichTooltip
property change event.
*
* @param tooltip
* The rich tooltip of the application menu button.
* @see #getApplicationMenuRichTooltip()
* @see #setApplicationMenu(RibbonApplicationMenu)
*/
public synchronized void setApplicationMenuRichTooltip(RichTooltip tooltip) {
RichTooltip old = this.applicationMenuRichTooltip;
this.applicationMenuRichTooltip = tooltip;
this.firePropertyChange("applicationMenuRichTooltip", old,
this.applicationMenuRichTooltip);
}
/**
* Returns the rich tooltip of the application menu button.
*
* @return The rich tooltip of the application menu button.
* @see #setApplicationMenuRichTooltip(RichTooltip)
* @see #setApplicationMenu(RibbonApplicationMenu)
*/
public synchronized RichTooltip getApplicationMenuRichTooltip() {
return this.applicationMenuRichTooltip;
}
/**
* Sets the key tip of the application menu button. Fires an
* applicationMenuKeyTip
property change event.
*
* @param keyTip
* The new key tip for the application menu button.
* @see #setApplicationMenu(RibbonApplicationMenu)
* @see #getApplicationMenuKeyTip()
*/
public synchronized void setApplicationMenuKeyTip(String keyTip) {
String old = this.applicationMenuKeyTip;
this.applicationMenuKeyTip = keyTip;
this.firePropertyChange("applicationMenuKeyTip", old,
this.applicationMenuKeyTip);
}
/**
* Returns the key tip of the application menu button.
*
* @return The key tip of the application menu button.
* @see #setApplicationMenuKeyTip(String)
* @see #setApplicationMenu(RibbonApplicationMenu)
*/
public synchronized String getApplicationMenuKeyTip() {
return this.applicationMenuKeyTip;
}
/**
* Returns the indication whether this ribbon is minimized.
*
* @return true
if this ribbon is minimized, false
* otherwise.
* @see #setMinimized(boolean)
*/
public synchronized boolean isMinimized() {
return this.isMinimized;
}
/**
* Changes the minimized state of this ribbon. Fires a
* minimized
property change event.
*
* @param isMinimized
* if true
, this ribbon becomes minimized, otherwise
* it is unminimized.
*/
public synchronized void setMinimized(boolean isMinimized) {
// System.out.println("Ribbon minimized -> " + isMinimized);
boolean old = this.isMinimized;
if (old != isMinimized) {
this.isMinimized = isMinimized;
this.firePropertyChange("minimized", old, this.isMinimized);
}
}
/**
* Returns the ribbon frame that hosts this ribbon. The result can be
* null
.
*
* @return The ribbon frame that hosts this ribbon.
*/
public JRibbonFrame getRibbonFrame() {
return this.ribbonFrame;
}
/*
* (non-Javadoc)
*
* @see javax.swing.JComponent#setVisible(boolean)
*/
@Override
public void setVisible(boolean flag) {
if (!flag && (getRibbonFrame() != null))
throw new IllegalArgumentException(
"Can't hide ribbon on JRibbonFrame");
super.setVisible(flag);
}
}