org.netbeans.swing.tabcontrol.plaf.BasicSlidingTabDisplayerUI Maven / Gradle / Ivy
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.netbeans.swing.tabcontrol.plaf;
import org.netbeans.swing.tabcontrol.TabDisplayer;
import javax.swing.*;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import javax.swing.plaf.ButtonUI;
import javax.swing.plaf.ComponentUI;
import java.awt.*;
import java.awt.event.*;
import java.awt.image.BufferedImage;
import java.beans.PropertyChangeEvent;
import java.util.Arrays;
import java.util.Comparator;
import org.netbeans.swing.tabcontrol.TabData;
/** Common UI for sliding tabs. Simply uses JToggleButtons for displayers,
* since the contents of the data model are not expected to change often,
* and no scrolling behavior needs to be supported for the tabs area.
*
* Note that the "sliding" is provided by an instance of FxProvider
* provided in the DefaultTabbedContainerUI
, not here.
*
* To change the appearance of the buttons, simply provide a subclass of SlidingTabDisplayerButtonUI
* via UIDefaults. This class is final.
*
* @author Tim Boudreau
*/
public final class BasicSlidingTabDisplayerUI extends AbstractTabDisplayerUI {
private Rectangle scratch = new Rectangle();
/** Creates a new instance of BasicSlidingTabDisplayerUI */
public BasicSlidingTabDisplayerUI(TabDisplayer displayer) {
super (displayer);
}
public static ComponentUI createUI (JComponent c) {
return new BasicSlidingTabDisplayerUI((TabDisplayer) c);
}
@Override
protected void install() {
displayer.setLayout (new OrientedLayoutManager());
syncButtonsWithModel();
}
@Override
protected Font createFont() {
//XXX Sideways text is more readable with a slightly larger bold font
Font f = super.createFont();
// don't use deriveFont() - see #49973 for details
f = new Font(f.getName(), Font.BOLD, f.getSize() + 1);
return f;
}
@Override
protected void uninstall() {
displayer.removeAll();
}
@Override
public Dimension getPreferredSize(JComponent c) {
return displayer.getLayout().preferredLayoutSize(c);
}
@Override
public Dimension getMinimumSize(JComponent c) {
return displayer.getLayout().minimumLayoutSize(c);
}
private int buttonCount = 0;
private boolean syncButtonsWithModel() {
assert SwingUtilities.isEventDispatchThread();
int count = displayer.getModel().size();
boolean changed = false;
buttonCount = displayer.getComponentCount();
if (count != buttonCount) {
synchronized (displayer.getTreeLock()) {
while (count < buttonCount) {
if (buttonCount-- > 0) {
displayer.remove (buttonCount- 1);
changed = true;
}
}
while (count > buttonCount) {
IndexButton ib = new IndexButton (buttonCount++);
ib.setFont (displayer.getFont());
displayer.add (ib);
changed = true;
}
Component[] c = displayer.getComponents();
for (int i=0; i < c.length; i++) {
if (c[i] instanceof IndexButton) {
changed |= ((IndexButton) c[i]).checkChanged();
}
}
}
}
return changed;
}
/** Not used so much to determine layout as to calculate preferred sizes
* here
*/
@Override
protected TabLayoutModel createLayoutModel() {
DefaultTabLayoutModel result = new DefaultTabLayoutModel(displayer.getModel(), displayer);
result.setPadding (new Dimension (15, 2));
return result;
}
@Override
protected MouseListener createMouseListener() {
return new MouseAdapter() {};
}
@Override
public void requestAttention (int tab) {
//not implemented
}
@Override
public void cancelRequestAttention (int tab) {
//not implemented
}
@Override
protected ChangeListener createSelectionListener() {
return new ChangeListener() {
private int lastKnownSelection = -1;
@Override
public void stateChanged (ChangeEvent ce) {
int selection = selectionModel.getSelectedIndex();
if (selection != lastKnownSelection) {
if (lastKnownSelection != -1) {
IndexButton last = findButtonFor(lastKnownSelection);
if (last != null) {
last.getModel().setSelected(false);
}
}
if (selection != -1) {
IndexButton current = findButtonFor (selection);
if (displayer.getComponentCount() == 0) {
syncButtonsWithModel();
}
if (current != null) {
current.getModel().setSelected (true);
}
}
}
lastKnownSelection = selection;
}
};
}
@Override
public Polygon getExactTabIndication(int index) {
return new EqualPolygon (findButtonFor(index).getBounds());
}
@Override
public Polygon getInsertTabIndication(int index) {
Rectangle r = findButtonFor (index).getBounds();
Polygon result = new EqualPolygon (findButtonFor(index).getBounds());
return result;
}
private IndexButton findButtonFor (int index) {
Component[] c = displayer.getComponents();
for (int i=0; i < c.length; i++) {
if (c[i] instanceof IndexButton && ((IndexButton) c[i]).getIndex() == index) {
return (IndexButton) c[i];
}
}
return null;
}
@Override
public Rectangle getTabRect(int index, Rectangle destination) {
if (destination == null) {
destination = new Rectangle();
}
IndexButton ib = findButtonFor(index);
if (ib != null) {
destination.setBounds (ib.getBounds());
} else {
destination.setBounds (-20, -20, 0, 0);
}
return destination;
}
@Override
public int tabForCoordinate(Point p) {
Component[] c = displayer.getComponents();
for (int i=0; i < c.length; i++) {
if (c[i] instanceof IndexButton) {
if (c[i].contains(p)) {
return ((IndexButton) c[i]).getIndex();
}
}
}
return -1;
}
protected final class SlidingPropertyChangeListener extends AbstractTabDisplayerUI.DisplayerPropertyChangeListener {
@Override
public void propertyChange(PropertyChangeEvent e) {
super.propertyChange(e);
if (TabDisplayer.PROP_ORIENTATION.equals(e.getPropertyName())) {
displayer.revalidate();
}
}
}
private Object getDisplayerOrientation() {
return displayer.getClientProperty (TabDisplayer.PROP_ORIENTATION);
}
/** Paints the rectangle occupied by a tab into an image and returns the result */
@Override
public Image createImageOfTab(int index) {
TabData td = displayer.getModel().getTab(index);
JLabel lbl = new JLabel(td.getText());
int width = lbl.getFontMetrics(lbl.getFont()).stringWidth(td.getText());
int height = lbl.getFontMetrics(lbl.getFont()).getHeight();
width = width + td.getIcon().getIconWidth() + 6;
height = Math.max(height, td.getIcon().getIconHeight()) + 5;
GraphicsConfiguration config = GraphicsEnvironment.getLocalGraphicsEnvironment()
.getDefaultScreenDevice().getDefaultConfiguration();
BufferedImage image = config.createCompatibleImage(width, height);
Graphics2D g = image.createGraphics();
g.setColor(lbl.getForeground());
g.setFont(lbl.getFont());
td.getIcon().paintIcon(lbl, g, 0, 0);
g.drawString(td.getText(), 18, height / 2);
return image;
}
/**
* JToggleButton subclass which maps to an index in the data model, and displays
* whatever the content of the data model at that index is. Buttons are added or removed
* from the tab displayer as the model changes. This class is public to allow
* alternate UIs for the buttons to be provided via subclasses of SlidingTabDisplayerButtonUI
.
*/
public final class IndexButton extends JToggleButton implements ActionListener {
private int index;
private String lastKnownText = null;
private Icon lastKnownIcon = null;
/** UI Class ID for IndexButtons, to be used by providers of UI delegates */
public static final String UI_KEY = "IndexButtonUI"; //NOI18N
/** Create a new button representing an index in the model. The index is immutable for the life of the
* button.
*
* @param index The index
*/
public IndexButton (int index) {
this.index = index;
addActionListener(this);
setFont (displayer.getFont());
setFocusable(false);
}
@Override
public void addNotify() {
super.addNotify();
ToolTipManager.sharedInstance().registerComponent(this);
}
@Override
public void removeNotify() {
super.removeNotify();
ToolTipManager.sharedInstance().unregisterComponent(this);
}
/** Accessor for the UI delegate to determine if the tab displayer is currently active */
public boolean isActive() {
return displayer.isActive();
}
@Override
public void updateUI () {
SlidingTabDisplayerButtonUI ui = null;
try {
ui = (SlidingTabDisplayerButtonUI) UIManager.getUI(this);
setUI (ui);
return;
} catch (Error e) {
System.err.println ("Error getting sliding button UI: " + e.getMessage());
} catch (Exception ex) {
System.err.println ("Exception getting button UI: " + ex.getMessage());
}
setUI ((ButtonUI) SlidingTabDisplayerButtonUI.createUI(this));
}
@Override
public String getUIClassID() {
return UI_KEY;
}
/** Accessor for the UI delegate - orientation will be one of the constants defined on
* TabDisplayer */
public Object getOrientation() {
return getDisplayerOrientation();
}
@Override
public String getText() {
if (index == -1) {
//We're being called in the superclass constructor when the UI is
//assigned
return "";
}
if (index < displayer.getModel().size()) {
lastKnownText = displayer.getModel().getTab(index).getText();
} else {
return "This tab doesn't exist."; //NOI18N
}
return lastKnownText;
}
@Override
public String getToolTipText() {
return displayer.getModel().getTab(index).getTooltip();
}
/** Implementation of ActionListener - sets the selected index in the selection model */
@Override
public final void actionPerformed(ActionEvent e) {
if (!isSelected()) {
selectionModel.setSelectedIndex (-1);
} else {
selectionModel.setSelectedIndex (index);
}
}
/** Get the index into the data model that this button represents */
public int getIndex() {
return index;
}
@Override
public Icon getIcon() {
if (index == -1) {
//We're being called in the superclass constructor when the UI is
//assigned
return null;
}
if (index < displayer.getModel().size()) {
lastKnownIcon = displayer.getModel().getTab(index).getIcon();
}
return lastKnownIcon;
}
/**
* Test if the text or icon in the model has changed since the last time getText()
or
* getIcon()
was called. If a change has occured, the button will fire the appropriate
* property changes, including preferred size, to ensure the tab displayer is re-laid out correctly.
* This method is called when a change happens in the model over the index this button represents.
*
* @return true if something has changed
*/
final boolean checkChanged() {
boolean result = false;
Icon ic = lastKnownIcon;
Icon nue = getIcon();
if (nue != ic) {
firePropertyChange ("icon", lastKnownIcon, nue); //NOI18N
result = true;
}
String txt = lastKnownText;
String nu = getText();
if (nu != txt) { //Equality compare probably not needed
firePropertyChange ("text", lastKnownText, getText()); //NOI18N
result = true;
}
if (result) {
firePropertyChange ("preferredSize", null, null); //NOI18N
}
return result;
}
}
@Override
protected void modelChanged() {
if (syncButtonsWithModel()) {
displayer.validate();
}
}
@Override
public Icon getButtonIcon(int buttonId, int buttonState) {
return null;
}
private static final Comparator BUTTON_COMPARATOR = new IndexButtonComparator();
private static class IndexButtonComparator implements Comparator {
@Override
public int compare(Component o1, Component o2) {
if (o2 instanceof IndexButton && o1 instanceof IndexButton) {
return ((IndexButton) o1).getIndex() - ((IndexButton) o2).getIndex();
}
return 0;
}
}
private final class OrientedLayoutManager implements LayoutManager {
@Override
public void addLayoutComponent(String name, Component comp) {
//do nothing
}
@Override
public void layoutContainer(Container parent) {
synchronized (parent.getTreeLock()) {
syncButtonsWithModel();
Component[] c = parent.getComponents();
Arrays.sort (c, BUTTON_COMPARATOR);
for (int i=0; i < c.length; i++) {
if (c[i] instanceof IndexButton) {
boundsFor ((IndexButton) c[i], scratch);
c[i].setBounds (scratch);
}
}
}
}
private void boundsFor (IndexButton b, Rectangle r) {
Object orientation = getDisplayerOrientation();
boolean flip = orientation == TabDisplayer.ORIENTATION_EAST ||
orientation == TabDisplayer.ORIENTATION_WEST;
int index = b.getIndex();
if (index >= displayer.getModel().size() || index < 0) {
r.setBounds (-20, -20, 0, 0);
return;
}
r.x = layoutModel.getX(index);
r.y = layoutModel.getY(index);
r.width = layoutModel.getW(index);
r.height = layoutModel.getH(index);
if (flip) {
int tmp = r.x;
r.x = r.y;
r.y = tmp;
tmp = r.width;
r.width = r.height;
r.height = tmp;
}
}
@Override
public Dimension minimumLayoutSize(Container parent) {
return preferredLayoutSize(parent);
}
@Override
public Dimension preferredLayoutSize(Container parent) {
Object orientation = getDisplayerOrientation();
boolean flip = orientation == TabDisplayer.ORIENTATION_EAST ||
orientation == TabDisplayer.ORIENTATION_WEST;
int max = displayer.getModel().size();
Dimension result = new Dimension();
for (int i=0; i < max; i++) {
result.height = Math.max (result.height, layoutModel.getH(i));
result.width += layoutModel.getW(i);
}
if (flip) {
int tmp = result.height;
result.height = result.width;
result.width = tmp;
}
return result;
}
@Override
public void removeLayoutComponent(Component comp) {
//do nothing
}
}
}