diff options
Diffstat (limited to 'javax/swing/plaf/basic')
21 files changed, 3217 insertions, 1138 deletions
diff --git a/javax/swing/plaf/basic/BasicButtonListener.java b/javax/swing/plaf/basic/BasicButtonListener.java index fe0365a50..042192b62 100644 --- a/javax/swing/plaf/basic/BasicButtonListener.java +++ b/javax/swing/plaf/basic/BasicButtonListener.java @@ -38,6 +38,8 @@ exception statement from your version. */ package javax.swing.plaf.basic; +import gnu.classpath.SystemProperties; + import java.awt.event.ActionEvent; import java.awt.event.FocusEvent; import java.awt.event.FocusListener; @@ -71,10 +73,12 @@ public class BasicButtonListener implements MouseListener, MouseMotionListener, // Store the TextLayout for this in a client property for speed-up // painting of the label. String property = e.getPropertyName(); - if (property.equals(AbstractButton.TEXT_CHANGED_PROPERTY) - || property.equals("font")) + AbstractButton b = (AbstractButton) e.getSource(); + if ((property.equals(AbstractButton.TEXT_CHANGED_PROPERTY) + || property.equals("font")) + && SystemProperties.getProperty("gnu.javax.swing.noGraphics2D") + == null) { - AbstractButton b = (AbstractButton) e.getSource(); String text = b.getText(); if (text == null) text = ""; @@ -83,6 +87,10 @@ public class BasicButtonListener implements MouseListener, MouseMotionListener, TextLayout layout = new TextLayout(text, b.getFont(), frc); b.putClientProperty(BasicGraphicsUtils.CACHED_TEXT_LAYOUT, layout); } + if (property.equals(AbstractButton.TEXT_CHANGED_PROPERTY)) + { + BasicHTML.updateRenderer(b, b.getText()); + } } protected void checkOpacity(AbstractButton b) @@ -176,11 +184,14 @@ public class BasicButtonListener implements MouseListener, MouseMotionListener, { AbstractButton button = (AbstractButton) e.getSource(); ButtonModel model = button.getModel(); - if (e.getButton() == MouseEvent.BUTTON1) + if (SwingUtilities.isLeftMouseButton(e)) { // It is important that these transitions happen in this order. model.setArmed(true); model.setPressed(true); + + if (! button.isFocusOwner() && button.isRequestFocusEnabled()) + button.requestFocus(); } } } diff --git a/javax/swing/plaf/basic/BasicButtonUI.java b/javax/swing/plaf/basic/BasicButtonUI.java index d531133ba..cdaec2543 100644 --- a/javax/swing/plaf/basic/BasicButtonUI.java +++ b/javax/swing/plaf/basic/BasicButtonUI.java @@ -56,6 +56,7 @@ import javax.swing.UIManager; import javax.swing.plaf.ButtonUI; import javax.swing.plaf.ComponentUI; import javax.swing.plaf.UIResource; +import javax.swing.text.View; /** * A UI delegate for the {@link JButton} component. @@ -255,10 +256,72 @@ public class BasicButtonUI extends ButtonUI installDefaults(b); installListeners(b); installKeyboardActions(b); + BasicHTML.updateRenderer(b, b.getText()); } } /** + * Uninstalls the UI from the component. + * + * @param c the component from which to uninstall the UI + */ + public void uninstallUI(JComponent c) + { + if (c instanceof AbstractButton) + { + AbstractButton b = (AbstractButton) c; + uninstallKeyboardActions(b); + uninstallListeners(b); + uninstallDefaults(b); + BasicHTML.updateRenderer(b, ""); + } + } + + /** + * Calculates the minimum size for the specified component. + * + * @param c the component for which to compute the minimum size + * + * @return the minimum size for the specified component + */ + public Dimension getMinimumSize(JComponent c) + { + Dimension size = getPreferredSize(c); + // When the HTML view has a minimum width different from the preferred + // width, then substract this here accordingly. The height is not + // affected by that. + View html = (View) c.getClientProperty(BasicHTML.propertyKey); + if (html != null) + { + size.width -= html.getPreferredSpan(View.X_AXIS) + - html.getPreferredSpan(View.X_AXIS); + } + return size; + } + + /** + * Calculates the maximum size for the specified component. + * + * @param c the component for which to compute the maximum size + * + * @return the maximum size for the specified component + */ + public Dimension getMaximumSize(JComponent c) + { + Dimension size = getPreferredSize(c); + // When the HTML view has a maximum width different from the preferred + // width, then add this here accordingly. The height is not + // affected by that. + View html = (View) c.getClientProperty(BasicHTML.propertyKey); + if (html != null) + { + size.width += html.getMaximumSpan(View.X_AXIS) + - html.getPreferredSpan(View.X_AXIS); + } + return size; + } + + /** * Calculate the preferred size of this component, by delegating to * {@link BasicGraphicsUtils#getPreferredButtonSize}. * @@ -269,8 +332,8 @@ public class BasicButtonUI extends ButtonUI public Dimension getPreferredSize(JComponent c) { AbstractButton b = (AbstractButton) c; - Dimension d = BasicGraphicsUtils.getPreferredButtonSize(b, - defaultTextIconGap + defaultTextShiftOffset); + Dimension d = BasicGraphicsUtils.getPreferredButtonSize(b, + b.getIconTextGap()); return d; } @@ -344,7 +407,13 @@ public class BasicButtonUI extends ButtonUI paintIcon(g, c, ir); if (text != null) - paintText(g, b, tr, text); + { + View html = (View) b.getClientProperty(BasicHTML.propertyKey); + if (html != null) + html.paint(g, tr); + else + paintText(g, b, tr, text); + } if (b.isFocusOwner() && b.isFocusPainted()) paintFocus(g, b, vr, tr, ir); } diff --git a/javax/swing/plaf/basic/BasicComboBoxUI.java b/javax/swing/plaf/basic/BasicComboBoxUI.java index ea97782c3..d98fd2afe 100644 --- a/javax/swing/plaf/basic/BasicComboBoxUI.java +++ b/javax/swing/plaf/basic/BasicComboBoxUI.java @@ -289,7 +289,7 @@ public class BasicComboBoxUI extends ComboBoxUI comboBox.addPropertyChangeListener(propertyChangeListener); focusListener = createFocusListener(); - editor.addFocusListener(focusListener); + comboBox.addFocusListener(focusListener); itemListener = createItemListener(); comboBox.addItemListener(itemListener); @@ -543,7 +543,9 @@ public class BasicComboBoxUI extends ComboBoxUI { editor.setFont(comboBox.getFont()); if (popupKeyListener != null) - editor.addKeyListener(popupKeyListener); + editor.addKeyListener(popupKeyListener); + if (keyListener != null) + editor.addKeyListener(keyListener); comboBox.configureEditor(comboBox.getEditor(), comboBox.getSelectedItem()); } @@ -555,6 +557,8 @@ public class BasicComboBoxUI extends ComboBoxUI { if (popupKeyListener != null) editor.removeKeyListener(popupKeyListener); + if (keyListener != null) + editor.removeKeyListener(keyListener); } /** @@ -775,7 +779,9 @@ public class BasicComboBoxUI extends ComboBoxUI protected boolean isNavigationKey(int keyCode) { return keyCode == KeyEvent.VK_UP || keyCode == KeyEvent.VK_DOWN - || keyCode == KeyEvent.VK_LEFT || keyCode == KeyEvent.VK_RIGHT; + || keyCode == KeyEvent.VK_LEFT || keyCode == KeyEvent.VK_RIGHT + || keyCode == KeyEvent.VK_ENTER || keyCode == KeyEvent.VK_ESCAPE + || keyCode == KeyEvent.VK_TAB; } /** @@ -796,7 +802,7 @@ public class BasicComboBoxUI extends ComboBoxUI protected void selectPreviousPossibleValue() { int index = comboBox.getSelectedIndex(); - if (index != 0) + if (index > 0) comboBox.setSelectedIndex(index - 1); } @@ -1201,11 +1207,29 @@ public class BasicComboBoxUI extends ComboBoxUI */ public void keyPressed(KeyEvent e) { - if (! isNavigationKey(e.getKeyCode()) && comboBox.isEnabled() - && comboBox.getModel().getSize() != 0) + if (comboBox.getModel().getSize() != 0 && comboBox.isEnabled()) { - if (comboBox.selectWithKeyChar(e.getKeyChar())) - e.consume(); + if (! isNavigationKey(e.getKeyCode())) + { + if (! comboBox.isEditable()) + if (comboBox.selectWithKeyChar(e.getKeyChar())) + e.consume(); + } + else + { + if (e.getKeyCode() == KeyEvent.VK_UP && comboBox.isPopupVisible()) + selectPreviousPossibleValue(); + else if (e.getKeyCode() == KeyEvent.VK_DOWN) + { + if (comboBox.isPopupVisible()) + selectNextPossibleValue(); + else + comboBox.showPopup(); + } + else if (e.getKeyCode() == KeyEvent.VK_ENTER + || e.getKeyCode() == KeyEvent.VK_ESCAPE) + popup.hide(); + } } } } @@ -1370,5 +1394,4 @@ public class BasicComboBoxUI extends ComboBoxUI // FIXME: Need to handle changes in other bound properties. } } - } diff --git a/javax/swing/plaf/basic/BasicDirectoryModel.java b/javax/swing/plaf/basic/BasicDirectoryModel.java index ef7a880c2..ed916cb5f 100644 --- a/javax/swing/plaf/basic/BasicDirectoryModel.java +++ b/javax/swing/plaf/basic/BasicDirectoryModel.java @@ -1,5 +1,5 @@ /* BasicDirectoryModel.java -- - Copyright (C) 2005 Free Software Foundation, Inc. + Copyright (C) 2005, 2006 Free Software Foundation, Inc. This file is part of GNU Classpath. @@ -40,35 +40,296 @@ package javax.swing.plaf.basic; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.io.File; +import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.Enumeration; +import java.util.Iterator; +import java.util.List; import java.util.Vector; import javax.swing.AbstractListModel; import javax.swing.JFileChooser; +import javax.swing.SwingUtilities; import javax.swing.event.ListDataEvent; import javax.swing.filechooser.FileSystemView; /** - * DOCUMENT ME! + * Implements an AbstractListModel for directories where the source + * of the files is a JFileChooser object. + * + * This class is used for sorting and ordering the file list in + * a JFileChooser L&F object. */ public class BasicDirectoryModel extends AbstractListModel implements PropertyChangeListener { - /** DOCUMENT ME! */ + /** The list of files itself */ private Vector contents; - /** DOCUMENT ME! */ - private int directories; + /** + * The directories in the list. + */ + private Vector directories; + + /** + * The files in the list. + */ + private Vector files; - /** DOCUMENT ME! */ + /** The listing mode of the associated JFileChooser, + either FILES_ONLY, DIRECTORIES_ONLY or FILES_AND_DIRECTORIES */ private int listingMode; - /** DOCUMENT ME! */ + /** The JFileCooser associated with this model */ private JFileChooser filechooser; - /** DOCUMENT ME! */ + /** + * The thread that loads the file view. + */ + private DirectoryLoadThread loadThread; + + /** + * This thread is responsible for loading file lists from the + * current directory and updating the model. + */ + private class DirectoryLoadThread extends Thread + { + + /** + * Updates the Swing list model. + */ + private class UpdateSwingRequest + implements Runnable + { + + private List added; + private int addIndex; + private List removed; + private int removeIndex; + private boolean cancel; + + UpdateSwingRequest(List add, int ai, List rem, int ri) + { + added = add; + addIndex = ai; + removed = rem; + removeIndex = ri; + cancel = false; + } + + public void run() + { + if (! cancel) + { + int numRemoved = removed == null ? 0 : removed.size(); + int numAdded = added == null ? 0 : added.size(); + synchronized (contents) + { + if (numRemoved > 0) + contents.removeAll(removed); + if (numAdded > 0) + contents.addAll(added); + + files = null; + directories = null; + } + if (numRemoved > 0 && numAdded == 0) + fireIntervalRemoved(BasicDirectoryModel.this, removeIndex, + removeIndex + numRemoved - 1); + else if (numRemoved == 0 && numAdded > 0) + fireIntervalAdded(BasicDirectoryModel.this, addIndex, + addIndex + numAdded - 1); + else + fireContentsChanged(); + } + } + + void cancel() + { + cancel = true; + } + } + + /** + * The directory beeing loaded. + */ + File directory; + + /** + * Stores all UpdateSwingRequests that are sent to the event queue. + */ + private UpdateSwingRequest pending; + + /** + * Creates a new DirectoryLoadThread that loads the specified + * directory. + * + * @param dir the directory to load + */ + DirectoryLoadThread(File dir) + { + super("Basic L&F directory loader"); + directory = dir; + } + + public void run() + { + FileSystemView fsv = filechooser.getFileSystemView(); + File[] files = fsv.getFiles(directory, + filechooser.isFileHidingEnabled()); + + // Occasional check if we have been interrupted. + if (isInterrupted()) + return; + + // Check list for accepted files. + Vector accepted = new Vector(); + for (int i = 0; i < files.length; i++) + { + if (filechooser.accept(files[i])) + accepted.add(files[i]); + } + + // Occasional check if we have been interrupted. + if (isInterrupted()) + return; + + // Sort list. + sort(accepted); + + // Now split up directories from files so that we get the directories + // listed before the files. + Vector newFiles = new Vector(); + Vector newDirectories = new Vector(); + for (Iterator i = accepted.iterator(); i.hasNext();) + { + File f = (File) i.next(); + boolean traversable = filechooser.isTraversable(f); + if (traversable) + newDirectories.add(f); + else if (! traversable && filechooser.isFileSelectionEnabled()) + newFiles.add(f); + + // Occasional check if we have been interrupted. + if (isInterrupted()) + return; + + } + + // Build up new file cache. Try to update only the changed elements. + // This will be important for actions like adding new files or + // directories inside a large file list. + Vector newCache = new Vector(newDirectories); + newCache.addAll(newFiles); + + int newSize = newCache.size(); + int oldSize = contents.size(); + if (newSize < oldSize) + { + // Check for removed interval. + int start = -1; + int end = -1; + boolean found = false; + for (int i = 0; i < newSize && !found; i++) + { + if (! newCache.get(i).equals(contents.get(i))) + { + start = i; + end = i + oldSize - newSize; + found = true; + } + } + if (start >= 0 && end > start + && contents.subList(end, oldSize) + .equals(newCache.subList(start, newSize))) + { + // Occasional check if we have been interrupted. + if (isInterrupted()) + return; + + Vector removed = new Vector(contents.subList(start, end)); + UpdateSwingRequest r = new UpdateSwingRequest(null, 0, + removed, start); + invokeLater(r); + newCache = null; + } + } + else if (newSize > oldSize) + { + // Check for inserted interval. + int start = oldSize; + int end = newSize; + boolean found = false; + for (int i = 0; i < oldSize && ! found; i++) + { + if (! newCache.get(i).equals(contents.get(i))) + { + start = i; + boolean foundEnd = false; + for (int j = i; j < newSize && ! foundEnd; j++) + { + if (newCache.get(j).equals(contents.get(i))) + { + end = j; + foundEnd = true; + } + } + end = i + oldSize - newSize; + } + } + if (start >= 0 && end > start + && newCache.subList(end, newSize) + .equals(contents.subList(start, oldSize))) + { + // Occasional check if we have been interrupted. + if (isInterrupted()) + return; + + List added = newCache.subList(start, end); + UpdateSwingRequest r = new UpdateSwingRequest(added, start, + null, 0); + invokeLater(r); + newCache = null; + } + } + + // Handle complete list changes (newCache != null). + if (newCache != null && ! contents.equals(newCache)) + { + // Occasional check if we have been interrupted. + if (isInterrupted()) + return; + UpdateSwingRequest r = new UpdateSwingRequest(newCache, 0, + contents, 0); + invokeLater(r); + } + } + + /** + * Wraps SwingUtilities.invokeLater() and stores the request in + * a Vector so that we can still cancel it later. + * + * @param update the request to invoke + */ + private void invokeLater(UpdateSwingRequest update) + { + pending = update; + SwingUtilities.invokeLater(update); + } + + /** + * Cancels all pending update requests that might be in the AWT + * event queue. + */ + void cancelPending() + { + if (pending != null) + pending.cancel(); + } + } + + /** A Comparator class/object for sorting the file list. */ private Comparator comparator = new Comparator() { public int compare(Object o1, Object o2) @@ -91,14 +352,15 @@ public class BasicDirectoryModel extends AbstractListModel filechooser.addPropertyChangeListener(this); listingMode = filechooser.getFileSelectionMode(); contents = new Vector(); + validateFileCache(); } /** - * DOCUMENT ME! + * Returns whether a given (File) object is included in the list. * - * @param o DOCUMENT ME! + * @param o - The file object to test. * - * @return DOCUMENT ME! + * @return <code>true</code> if the list contains the given object. */ public boolean contains(Object o) { @@ -106,7 +368,7 @@ public class BasicDirectoryModel extends AbstractListModel } /** - * DOCUMENT ME! + * Fires a content change event. */ public void fireContentsChanged() { @@ -114,80 +376,99 @@ public class BasicDirectoryModel extends AbstractListModel } /** - * DOCUMENT ME! + * Returns a Vector of (java.io.File) objects containing + * the directories in this list. * - * @return DOCUMENT ME! + * @return a Vector */ public Vector getDirectories() { - Vector tmp = new Vector(); - for (int i = 0; i < directories; i++) - tmp.add(contents.get(i)); - return tmp; + // Synchronize this with the UpdateSwingRequest for the case when + // contents is modified. + synchronized (contents) + { + Vector dirs = directories; + if (dirs == null) + { + // Initializes this in getFiles(). + getFiles(); + dirs = directories; + } + return dirs; + } } /** - * DOCUMENT ME! + * Returns the (java.io.File) object at + * an index in the list. * - * @param index DOCUMENT ME! - * - * @return DOCUMENT ME! + * @param index The list index + * @return a File object */ public Object getElementAt(int index) { if (index > getSize() - 1) return null; - if (listingMode == JFileChooser.FILES_ONLY) - return contents.get(directories + index); - else - return contents.elementAt(index); + return contents.elementAt(index); } /** - * DOCUMENT ME! + * Returns a Vector of (java.io.File) objects containing + * the files in this list. * - * @return DOCUMENT ME! + * @return a Vector */ public Vector getFiles() { - Vector tmp = new Vector(); - for (int i = directories; i < getSize(); i++) - tmp.add(contents.get(i)); - return tmp; + synchronized (contents) + { + Vector f = files; + if (f == null) + { + f = new Vector(); + Vector d = new Vector(); // Directories; + for (Iterator i = contents.iterator(); i.hasNext();) + { + File file = (File) i.next(); + if (filechooser.isTraversable(file)) + d.add(file); + else + f.add(file); + } + files = f; + directories = d; + } + return f; + } } /** - * DOCUMENT ME! + * Returns the size of the list, which only includes directories + * if the JFileChooser is set to DIRECTORIES_ONLY. + * + * Otherwise, both directories and files are included in the count. * - * @return DOCUMENT ME! + * @return The size of the list. */ public int getSize() { - if (listingMode == JFileChooser.DIRECTORIES_ONLY) - return directories; - else if (listingMode == JFileChooser.FILES_ONLY) - return contents.size() - directories; return contents.size(); } /** - * DOCUMENT ME! + * Returns the index of an (java.io.File) object in the list. * - * @param o DOCUMENT ME! + * @param o The object - normally a File. * - * @return DOCUMENT ME! + * @return the index of that object, or -1 if it is not in the list. */ public int indexOf(Object o) { - if (listingMode == JFileChooser.FILES_ONLY) - return contents.indexOf(o) - directories; return contents.indexOf(o); } /** - * DOCUMENT ME! - * - * @param e DOCUMENT ME! + * Obsoleted method which does nothing. */ public void intervalAdded(ListDataEvent e) { @@ -195,9 +476,7 @@ public class BasicDirectoryModel extends AbstractListModel } /** - * DOCUMENT ME! - * - * @param e DOCUMENT ME! + * Obsoleted method which does nothing. */ public void intervalRemoved(ListDataEvent e) { @@ -205,7 +484,7 @@ public class BasicDirectoryModel extends AbstractListModel } /** - * DOCUMENT ME! + * Obsoleted method which does nothing. */ public void invalidateFileCache() { @@ -213,12 +492,16 @@ public class BasicDirectoryModel extends AbstractListModel } /** - * DOCUMENT ME! + * Less than, determine the relative order in the list of two files + * for sorting purposes. + * + * The order is: directories < files, and thereafter alphabetically, + * using the default locale collation. * - * @param a DOCUMENT ME! - * @param b DOCUMENT ME! + * @param a the first file + * @param b the second file * - * @return DOCUMENT ME! + * @return <code>true</code> if a > b, <code>false</code> if a < b. */ protected boolean lt(File a, File b) { @@ -241,73 +524,66 @@ public class BasicDirectoryModel extends AbstractListModel } /** - * DOCUMENT ME! + * Listens for a property change; the change in file selection mode of the + * associated JFileChooser. Reloads the file cache on that event. * - * @param e DOCUMENT ME! + * @param e - A PropertyChangeEvent. */ public void propertyChange(PropertyChangeEvent e) { - if (e.getPropertyName().equals(JFileChooser.FILE_SELECTION_MODE_CHANGED_PROPERTY)) - listingMode = filechooser.getFileSelectionMode(); + String property = e.getPropertyName(); + if (property.equals(JFileChooser.DIRECTORY_CHANGED_PROPERTY) + || property.equals(JFileChooser.FILE_FILTER_CHANGED_PROPERTY) + || property.equals(JFileChooser.FILE_HIDING_CHANGED_PROPERTY) + || property.equals(JFileChooser.FILE_SELECTION_MODE_CHANGED_PROPERTY) + || property.equals(JFileChooser.FILE_VIEW_CHANGED_PROPERTY) + ) + { + validateFileCache(); + } } /** - * DOCUMENT ME! + * Renames a file - However, does <I>not</I> re-sort the list + * or replace the old file with the new one in the list. * - * @param oldFile DOCUMENT ME! - * @param newFile DOCUMENT ME! + * @param oldFile The old file + * @param newFile The new file name * - * @return DOCUMENT ME! + * @return <code>true</code> if the rename succeeded */ public boolean renameFile(File oldFile, File newFile) { - // FIXME: implement - return false; + return oldFile.renameTo( newFile ); } /** - * DOCUMENT ME! + * Sorts a Vector of File objects. * - * @param v DOCUMENT ME! + * @param v The Vector to sort. */ protected void sort(Vector v) { Collections.sort(v, comparator); - Enumeration e = Collections.enumeration(v); - Vector tmp = new Vector(); - for (; e.hasMoreElements();) - tmp.add(e.nextElement()); - - contents = tmp; } /** - * DOCUMENT ME! + * Re-loads the list of files */ public void validateFileCache() { - contents.clear(); - directories = 0; - FileSystemView fsv = filechooser.getFileSystemView(); - File[] list = fsv.getFiles(filechooser.getCurrentDirectory(), - filechooser.isFileHidingEnabled()); - - if (list == null) - return; - - for (int i = 0; i < list.length; i++) + File dir = filechooser.getCurrentDirectory(); + if (dir != null) { - if (list[i] == null) - continue; - if (filechooser.accept(list[i])) - { - contents.add(list[i]); - if (filechooser.isTraversable(list[i])) - directories++; - } + // Cancel all pending requests. + if (loadThread != null) + { + loadThread.interrupt(); + loadThread.cancelPending(); + } + loadThread = new DirectoryLoadThread(dir); + loadThread.start(); } - sort(contents); - filechooser.revalidate(); - filechooser.repaint(); } } + diff --git a/javax/swing/plaf/basic/BasicFileChooserUI.java b/javax/swing/plaf/basic/BasicFileChooserUI.java index 1356db4ae..dc1c05122 100644 --- a/javax/swing/plaf/basic/BasicFileChooserUI.java +++ b/javax/swing/plaf/basic/BasicFileChooserUI.java @@ -160,6 +160,8 @@ public class BasicFileChooserUI extends FileChooserUI else { File f = new File(filechooser.getCurrentDirectory(), getFileName()); + if ( selectedDir != null ) + f = selectedDir; if (filechooser.isTraversable(f)) { filechooser.setCurrentDirectory(f); @@ -266,7 +268,14 @@ public class BasicFileChooserUI extends FileChooserUI */ public String getName(File f) { - return f.getName(); + String name = null; + if (f != null) + { + JFileChooser c = getFileChooser(); + FileSystemView v = c.getFileSystemView(); + name = v.getSystemDisplayName(f); + } + return name; } /** @@ -409,7 +418,7 @@ public class BasicFileChooserUI extends FileChooserUI closeDialog(); } } - else + else // single click { String path = p.toString(); File f = fsv.createFileObject(path); @@ -436,10 +445,11 @@ public class BasicFileChooserUI extends FileChooserUI } lastSelected = path; parentPath = path.substring(0, path.lastIndexOf("/") + 1); + if (f.isFile()) setFileName(path.substring(path.lastIndexOf("/") + 1)); - else if (filechooser.getFileSelectionMode() == - JFileChooser.DIRECTORIES_ONLY) + else if (filechooser.getFileSelectionMode() != + JFileChooser.FILES_ONLY) setFileName(path); } } @@ -538,7 +548,7 @@ public class BasicFileChooserUI extends FileChooserUI } /** - * DOCUMENT ME! + * Sets the JFileChooser to the selected file on an update * * @param e DOCUMENT ME! */ @@ -550,9 +560,15 @@ public class BasicFileChooserUI extends FileChooserUI return; File file = filechooser.getFileSystemView().createFileObject(f.toString()); if (! filechooser.isTraversable(file)) - filechooser.setSelectedFile(file); + { + selectedDir = null; + filechooser.setSelectedFile(file); + } else - filechooser.setSelectedFile(null); + { + selectedDir = file; + filechooser.setSelectedFile(null); + } } } @@ -752,6 +768,13 @@ public class BasicFileChooserUI extends FileChooserUI * @see #getUpdateAction() */ private UpdateAction updateAction; + + /** + * When in FILES_ONLY, mode a directory cannot be selected, so + * we save a reference to any it here. This is used to enter + * the directory on "Open" when in that mode. + */ + private File selectedDir; // -- end private -- @@ -874,7 +897,9 @@ public class BasicFileChooserUI extends FileChooserUI protected void installListeners(JFileChooser fc) { propertyChangeListener = createPropertyChangeListener(filechooser); - filechooser.addPropertyChangeListener(propertyChangeListener); + if (propertyChangeListener != null) + filechooser.addPropertyChangeListener(propertyChangeListener); + fc.addPropertyChangeListener(getModel()); } /** @@ -884,8 +909,12 @@ public class BasicFileChooserUI extends FileChooserUI */ protected void uninstallListeners(JFileChooser fc) { - filechooser.removePropertyChangeListener(propertyChangeListener); - propertyChangeListener = null; + if (propertyChangeListener != null) + { + filechooser.removePropertyChangeListener(propertyChangeListener); + propertyChangeListener = null; + } + fc.removePropertyChangeListener(getModel()); } /** @@ -1046,12 +1075,8 @@ public class BasicFileChooserUI extends FileChooserUI */ public PropertyChangeListener createPropertyChangeListener(JFileChooser fc) { - return new PropertyChangeListener() - { - public void propertyChange(PropertyChangeEvent e) - { - } - }; + // The RI returns null here, so do we. + return null; } /** diff --git a/javax/swing/plaf/basic/BasicHTML.java b/javax/swing/plaf/basic/BasicHTML.java index 98c9cb277..67f9c20dc 100644 --- a/javax/swing/plaf/basic/BasicHTML.java +++ b/javax/swing/plaf/basic/BasicHTML.java @@ -107,6 +107,7 @@ public class BasicHTML editorKit = kit; document = doc; setView(view); + setSize(view.getPreferredSpan(X_AXIS), view.getPreferredSpan(Y_AXIS)); } /** @@ -151,6 +152,14 @@ public class BasicHTML } /** + * Overridden to forward to real view. + */ + public void setSize(float w, float h) + { + view.setSize(w, h); + } + + /** * Returns the real root view, regardless of the index. * * @param index not used here diff --git a/javax/swing/plaf/basic/BasicInternalFrameUI.java b/javax/swing/plaf/basic/BasicInternalFrameUI.java index bfd97b913..8161df29f 100644 --- a/javax/swing/plaf/basic/BasicInternalFrameUI.java +++ b/javax/swing/plaf/basic/BasicInternalFrameUI.java @@ -38,7 +38,6 @@ exception statement from your version. */ package javax.swing.plaf.basic; -import java.awt.AWTEvent; import java.awt.Color; import java.awt.Component; import java.awt.Container; @@ -460,18 +459,12 @@ public class BasicInternalFrameUI extends InternalFrameUI { if (frame.isMaximum()) { - JDesktopPane pane = (JDesktopPane) e.getSource(); - Insets insets = pane.getInsets(); - Rectangle bounds = pane.getBounds(); - - frame.setBounds(bounds.x + insets.left, bounds.y + insets.top, - bounds.width - insets.left - insets.right, - bounds.height - insets.top - insets.bottom); - frame.revalidate(); - frame.repaint(); + Container parent = frame.getParent(); + Insets i = parent.getInsets(); + int width = parent.getWidth() - i.left - i.right; + int height = parent.getHeight() - i.top - i.bottom; + frame.setBounds(0, 0, width, height); } - - // Sun also resizes the icons. but it doesn't seem to do anything. } /** @@ -692,17 +685,12 @@ public class BasicInternalFrameUI extends InternalFrameUI /** The MouseEvent target. */ private transient Component mouseEventTarget; - /** The component pressed. */ - private transient Component pressedComponent; - - /** The last component entered. */ - private transient Component lastComponentEntered; - - /** Used to store/reset lastComponentEntered. */ - private transient Component tempComponent; + private Component dragTarget; - /** The number of presses. */ - private transient int pressCount; + /** + * Indicates if we are currently in a dragging operation or not. + */ + private boolean isDragging; /** * This method is called when the mouse enters the glass pane. @@ -767,7 +755,10 @@ public class BasicInternalFrameUI extends InternalFrameUI */ public void mousePressed(MouseEvent e) { - activateFrame(frame); + // Experiments show that this seems to call the + // borderListener.mousePressed() method to activate the frame. + if (borderListener != null) + borderListener.mousePressed(e); handleEvent(e); } @@ -783,149 +774,104 @@ public class BasicInternalFrameUI extends InternalFrameUI } /** - * This method acquires a candidate component to dispatch the MouseEvent to. + * This is a helper method that dispatches the GlassPane MouseEvents to the + * proper component. * - * @param me - * The MouseEvent to acquire a component for. + * @param e the mouse event to be dispatched */ - private void acquireComponentForMouseEvent(MouseEvent me) + private void handleEvent(MouseEvent e) { - int x = me.getX(); - int y = me.getY(); - - // Find the candidate which should receive this event. - Component parent = frame.getLayeredPane(); - if (parent == null) - return; - Component candidate = null; - Point p = me.getPoint(); - while (candidate == null && parent != null) - { - candidate = SwingUtilities.getDeepestComponentAt(parent, p.x, p.y); - if (candidate == null) - { - p = SwingUtilities.convertPoint(parent, p.x, p.y, - parent.getParent()); - parent = parent.getParent(); - } - } - - // If the only candidate we found was the native container itself, - // don't dispatch any event at all. We only care about the lightweight - // children here. - if (candidate == frame.getContentPane()) - candidate = null; - - // If our candidate is new, inform the old target we're leaving. - if (lastComponentEntered != null && lastComponentEntered.isShowing() - && lastComponentEntered != candidate) + // Find candidate component inside the JInternalFrame. + Component target = frame.getLayeredPane().findComponentAt(e.getX(), + e.getY()); + + // Now search upwards to find a component that actually has + // a MouseListener attached. + while (target != null + && target.getMouseListeners().length == 0 + && target.getMouseMotionListeners().length == 0 + && target.getMouseWheelListeners().length == 0) { - Point tp = SwingUtilities.convertPoint(frame.getContentPane(), x, y, - lastComponentEntered); - MouseEvent exited = new MouseEvent(lastComponentEntered, - MouseEvent.MOUSE_EXITED, - me.getWhen(), me.getModifiersEx(), - tp.x, tp.y, me.getClickCount(), - me.isPopupTrigger(), - me.getButton()); - tempComponent = lastComponentEntered; - lastComponentEntered = null; - tempComponent.dispatchEvent(exited); + target = target.getParent(); } - // If we have a candidate, maybe enter it. - if (candidate != null) + if (target != null) { - mouseEventTarget = candidate; - if (candidate.isLightweight() && candidate.isShowing() - && candidate != frame.getContentPane() - && candidate != lastComponentEntered) - { - lastComponentEntered = mouseEventTarget; - Point cp = SwingUtilities.convertPoint(frame.getContentPane(), x, - y, lastComponentEntered); - MouseEvent entered = new MouseEvent(lastComponentEntered, - MouseEvent.MOUSE_ENTERED, - me.getWhen(), - me.getModifiersEx(), cp.x, - cp.y, me.getClickCount(), - me.isPopupTrigger(), - me.getButton()); - lastComponentEntered.dispatchEvent(entered); - } - } - - if (me.getID() == MouseEvent.MOUSE_RELEASED - || me.getID() == MouseEvent.MOUSE_PRESSED && pressCount > 0 - || me.getID() == MouseEvent.MOUSE_DRAGGED) - // If any of the following events occur while a button is held down, - // they should be dispatched to the same component to which the - // original MOUSE_PRESSED event was dispatched: - // - MOUSE_RELEASED - // - MOUSE_PRESSED: another button pressed while the first is held down - // - MOUSE_DRAGGED - mouseEventTarget = pressedComponent; - else if (me.getID() == MouseEvent.MOUSE_CLICKED) - { - // Don't dispatch CLICKED events whose target is not the same as the - // target for the original PRESSED event. - if (candidate != pressedComponent) - mouseEventTarget = null; - else if (pressCount == 0) - pressedComponent = null; + int id = e.getID(); + switch (id) + { + case MouseEvent.MOUSE_ENTERED: + // Now redispatch the thing. + if (! isDragging || frame.isSelected()) + { + mouseEventTarget = target; + redispatch(id, e, mouseEventTarget); + } + break; + case MouseEvent.MOUSE_EXITED: + if (! isDragging || frame.isSelected()) + { + redispatch(id, e, mouseEventTarget); + } + break; + case MouseEvent.MOUSE_PRESSED: + mouseEventTarget = target; + redispatch(id, e, mouseEventTarget); + // Start dragging. + dragTarget = target; + break; + case MouseEvent.MOUSE_RELEASED: + if (isDragging) + { + redispatch(id, e, dragTarget); + isDragging = false; + } + else + redispatch(id, e, mouseEventTarget); + break; + case MouseEvent.MOUSE_CLICKED: + redispatch(id, e, mouseEventTarget); + break; + case MouseEvent.MOUSE_MOVED: + if (target != mouseEventTarget) + { + // Create additional MOUSE_EXITED/MOUSE_ENTERED pairs. + redispatch(MouseEvent.MOUSE_EXITED, e, mouseEventTarget); + mouseEventTarget = target; + redispatch(MouseEvent.MOUSE_ENTERED, e, mouseEventTarget); + } + redispatch(id, e, mouseEventTarget); + break; + case MouseEvent.MOUSE_DRAGGED: + if (! isDragging) + isDragging = true; + redispatch(id, e, mouseEventTarget); + break; + case MouseEvent.MOUSE_WHEEL: + redispatch(id, e, mouseEventTarget); + break; + default: + assert false : "Must not reach here"; + } } } /** - * This is a helper method that dispatches the GlassPane MouseEvents to the - * proper component. - * - * @param e - * The AWTEvent to be dispatched. Usually an instance of - * MouseEvent. + * Redispatches the event to the real target with the specified id. + * + * @param id the new event ID + * @param e the original event + * @param target the real event target */ - private void handleEvent(AWTEvent e) + private void redispatch(int id, MouseEvent e, Component target) { - if (e instanceof MouseEvent) - { - MouseEvent me = (MouseEvent) e; - acquireComponentForMouseEvent(me); - - //If there is no target, return - if (mouseEventTarget == null) - return; - - //Avoid re-dispatching to ourselves and causing an infinite loop - if (mouseEventTarget.equals(frame.getGlassPane())) - return; - - // Avoid dispatching ENTERED and EXITED events twice. - if (mouseEventTarget.isShowing() - && e.getID() != MouseEvent.MOUSE_ENTERED - && e.getID() != MouseEvent.MOUSE_EXITED) - { - MouseEvent newEvt = SwingUtilities.convertMouseEvent( - frame.getGlassPane(), - me, - mouseEventTarget); - mouseEventTarget.dispatchEvent(newEvt); - - switch (e.getID()) - { - case MouseEvent.MOUSE_PRESSED: - if (pressCount++ == 0) - pressedComponent = mouseEventTarget; - break; - case MouseEvent.MOUSE_RELEASED: - // Clear our memory of the original PRESSED event, only if - // we're not expecting a CLICKED event after this. If - // there is a CLICKED event after this, it will do clean up. - if (--pressCount == 0 && mouseEventTarget != pressedComponent) - pressedComponent = null; - break; - } - } - } + Point p = SwingUtilities.convertPoint(frame.getLayeredPane(), e.getX(), + e.getY(), target); + MouseEvent ev = new MouseEvent(target, id, e.getWhen(), + e.getModifiers() | e.getModifiersEx(), + p.x, p.y, e.getClickCount(), + e.isPopupTrigger()); + target.dispatchEvent(ev); } } @@ -962,10 +908,17 @@ public class BasicInternalFrameUI extends InternalFrameUI } else if (property.equals(JInternalFrame.IS_SELECTED_PROPERTY)) { + Component glassPane = frame.getGlassPane(); if (frame.isSelected()) - activateFrame(frame); + { + activateFrame(frame); + glassPane.setVisible(false); + } else - deactivateFrame(frame); + { + deactivateFrame(frame); + glassPane.setVisible(true); + } } else if (property.equals(JInternalFrame.ROOT_PANE_PROPERTY) || property.equals(JInternalFrame.GLASS_PANE_PROPERTY)) @@ -990,17 +943,25 @@ public class BasicInternalFrameUI extends InternalFrameUI { if (evt.getNewValue() == Boolean.TRUE) { + Container parent = frame.getParent(); + if (parent != null) + parent.removeComponentListener(componentListener); closeFrame(frame); } } - /* - * FIXME: need to add ancestor properties to JComponents. else if - * (evt.getPropertyName().equals(JComponent.ANCESTOR_PROPERTY)) { if - * (desktopPane != null) - * desktopPane.removeComponentListener(componentListener); desktopPane = - * frame.getDesktopPane(); if (desktopPane != null) - * desktopPane.addComponentListener(componentListener); } - */ + else if (property.equals("ancestor")) + { + Container newParent = (Container) evt.getNewValue(); + Container oldParent = (Container) evt.getOldValue(); + if (newParent != null) + { + newParent.addComponentListener(componentListener); + } + else if (oldParent != null) + { + oldParent.removeComponentListener(componentListener); + } + } } } @@ -1299,6 +1260,12 @@ public class BasicInternalFrameUI extends InternalFrameUI frame.addPropertyChangeListener(propertyChangeListener); frame.getRootPane().getGlassPane().addMouseListener(glassPaneDispatcher); frame.getRootPane().getGlassPane().addMouseMotionListener(glassPaneDispatcher); + + Container parent = frame.getParent(); + if (parent != null) + { + parent.addComponentListener(componentListener); + } } /** @@ -1327,8 +1294,13 @@ public class BasicInternalFrameUI extends InternalFrameUI */ protected void uninstallListeners() { - if (desktopPane != null) - desktopPane.removeComponentListener(componentListener); + + Container parent = frame.getParent(); + if (parent != null) + { + parent.removeComponentListener(componentListener); + } + componentListener = null; frame.getRootPane().getGlassPane().removeMouseMotionListener(glassPaneDispatcher); frame.getRootPane().getGlassPane().removeMouseListener(glassPaneDispatcher); @@ -1339,7 +1311,7 @@ public class BasicInternalFrameUI extends InternalFrameUI frame.removeMouseListener(borderListener); propertyChangeListener = null; - componentListener = null; + borderListener = null; internalFrameListener = null; glassPaneDispatcher = null; diff --git a/javax/swing/plaf/basic/BasicListUI.java b/javax/swing/plaf/basic/BasicListUI.java index 10f86ebda..493fc0578 100644 --- a/javax/swing/plaf/basic/BasicListUI.java +++ b/javax/swing/plaf/basic/BasicListUI.java @@ -479,7 +479,8 @@ public class BasicListUI extends ListUI */ public void mousePressed(MouseEvent event) { - // TODO: What should be done here, if anything? + // We need to explicitly request focus. + list.requestFocusInWindow(); } /** @@ -1210,7 +1211,7 @@ public class BasicListUI extends ListUI boolean hasFocus = (list.getLeadSelectionIndex() == row) && BasicListUI.this.list.hasFocus(); Component comp = rend.getListCellRendererComponent(list, data.getElementAt(row), - 0, isSel, hasFocus); + row, isSel, hasFocus); rendererPane.paintComponent(g, comp, list, bounds); } diff --git a/javax/swing/plaf/basic/BasicLookAndFeel.java b/javax/swing/plaf/basic/BasicLookAndFeel.java index 1fffcddca..c056a2403 100644 --- a/javax/swing/plaf/basic/BasicLookAndFeel.java +++ b/javax/swing/plaf/basic/BasicLookAndFeel.java @@ -1031,6 +1031,25 @@ public abstract class BasicLookAndFeel extends LookAndFeel "PopupMenu.border", new BorderUIResource.BevelBorderUIResource(0), "PopupMenu.font", new FontUIResource("Dialog", Font.PLAIN, 12), "PopupMenu.foreground", new ColorUIResource(darkShadow), + "PopupMenu.selectedWindowInputMapBindings", + new Object[] {"ESCAPE", "cancel", + "DOWN", "selectNext", + "KP_DOWN", "selectNext", + "UP", "selectPrevious", + "KP_UP", "selectPrevious", + "LEFT", "selectParent", + "KP_LEFT", "selectParent", + "RIGHT", "selectChild", + "KP_RIGHT", "selectChild", + "ENTER", "return", + "SPACE", "return" + }, + "PopupMenu.selectedWindowInputMapBindings.RightToLeft", + new Object[] {"LEFT", "selectChild", + "KP_LEFT", "selectChild", + "RIGHT", "selectParent", + "KP_RIGHT", "selectParent", + }, "ProgressBar.background", new ColorUIResource(Color.LIGHT_GRAY), "ProgressBar.border", new BorderUIResource.LineBorderUIResource(Color.GREEN, 2), diff --git a/javax/swing/plaf/basic/BasicMenuBarUI.java b/javax/swing/plaf/basic/BasicMenuBarUI.java index f258ebe30..cd25a3baf 100644 --- a/javax/swing/plaf/basic/BasicMenuBarUI.java +++ b/javax/swing/plaf/basic/BasicMenuBarUI.java @@ -38,24 +38,31 @@ exception statement from your version. */ package javax.swing.plaf.basic; -import gnu.classpath.NotImplementedException; - import java.awt.Dimension; +import java.awt.event.ActionEvent; import java.awt.event.ContainerEvent; import java.awt.event.ContainerListener; import java.awt.event.MouseEvent; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; +import javax.swing.AbstractAction; +import javax.swing.Action; +import javax.swing.ActionMap; import javax.swing.BoxLayout; +import javax.swing.InputMap; import javax.swing.JComponent; import javax.swing.JMenu; import javax.swing.JMenuBar; import javax.swing.LookAndFeel; import javax.swing.MenuElement; +import javax.swing.MenuSelectionManager; +import javax.swing.SwingUtilities; +import javax.swing.UIManager; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; import javax.swing.event.MouseInputListener; +import javax.swing.plaf.ActionMapUIResource; import javax.swing.plaf.ComponentUI; import javax.swing.plaf.MenuBarUI; @@ -64,6 +71,47 @@ import javax.swing.plaf.MenuBarUI; */ public class BasicMenuBarUI extends MenuBarUI { + + /** + * This action is performed for the action command 'takeFocus'. + */ + private static class FocusAction + extends AbstractAction + { + + /** + * Creates a new FocusAction. + */ + FocusAction() + { + super("takeFocus"); + } + + /** + * Performs the action. + */ + public void actionPerformed(ActionEvent event) + { + // In the JDK this action seems to pop up the first menu of the + // menu bar. + JMenuBar menuBar = (JMenuBar) event.getSource(); + MenuSelectionManager defaultManager = + MenuSelectionManager.defaultManager(); + MenuElement me[]; + MenuElement subElements[]; + JMenu menu = menuBar.getMenu(0); + if (menu != null) + { + me = new MenuElement[3]; + me[0] = (MenuElement) menuBar; + me[1] = (MenuElement) menu; + me[2] = (MenuElement) menu.getPopupMenu(); + defaultManager.setSelectedPath(me); + } + } + + } + protected ChangeListener changeListener; /*ContainerListener that listens to the ContainerEvents fired from menu bar*/ @@ -178,9 +226,46 @@ public class BasicMenuBarUI extends MenuBarUI * This method installs the keyboard actions for the JMenuBar. */ protected void installKeyboardActions() - throws NotImplementedException { - // FIXME: implement + // Install InputMap. + Object[] bindings = + (Object[]) SharedUIDefaults.get("MenuBar.windowBindings"); + InputMap inputMap = LookAndFeel.makeComponentInputMap(menuBar, bindings); + SwingUtilities.replaceUIInputMap(menuBar, + JComponent.WHEN_IN_FOCUSED_WINDOW, + inputMap); + + // Install ActionMap. + SwingUtilities.replaceUIActionMap(menuBar, getActionMap()); + } + + /** + * Creates and returns the shared action map for JTrees. + * + * @return the shared action map for JTrees + */ + private ActionMap getActionMap() + { + ActionMap am = (ActionMap) UIManager.get("MenuBar.actionMap"); + if (am == null) + { + am = createDefaultActions(); + UIManager.getLookAndFeelDefaults().put("MenuBar.actionMap", am); + } + return am; + } + + /** + * Creates the default actions when there are none specified by the L&F. + * + * @return the default actions + */ + private ActionMap createDefaultActions() + { + ActionMapUIResource am = new ActionMapUIResource(); + Action action = new FocusAction(); + am.put(action.getValue(Action.NAME), action); + return am; } /** @@ -226,9 +311,10 @@ public class BasicMenuBarUI extends MenuBarUI * This method reverses the work done in installKeyboardActions. */ protected void uninstallKeyboardActions() - throws NotImplementedException { - // FIXME: implement. + SwingUtilities.replaceUIInputMap(menuBar, + JComponent.WHEN_IN_FOCUSED_WINDOW, null); + SwingUtilities.replaceUIActionMap(menuBar, null); } /** diff --git a/javax/swing/plaf/basic/BasicMenuItemUI.java b/javax/swing/plaf/basic/BasicMenuItemUI.java index c5ed2ff7e..6110aca66 100644 --- a/javax/swing/plaf/basic/BasicMenuItemUI.java +++ b/javax/swing/plaf/basic/BasicMenuItemUI.java @@ -38,6 +38,8 @@ exception statement from your version. */ package javax.swing.plaf.basic; +import gnu.classpath.SystemProperties; + import java.awt.Color; import java.awt.Component; import java.awt.Container; @@ -256,8 +258,11 @@ public class BasicMenuItemUI extends MenuItemUI map.put(accelerator, "doClick"); } // TextLayout caching for speed-up drawing of text. - else if (property.equals(AbstractButton.TEXT_CHANGED_PROPERTY) - || property.equals("font")) + else if ((property.equals(AbstractButton.TEXT_CHANGED_PROPERTY) + || property.equals("font")) + && SystemProperties.getProperty("gnu.javax.swing.noGraphics2D") + == null) + { AbstractButton b = (AbstractButton) e.getSource(); String text = b.getText(); @@ -406,10 +411,6 @@ public class BasicMenuItemUI extends MenuItemUI { ArrayList path = new ArrayList(); - // Path to menu should also include its popup menu. - if (menuItem instanceof JMenu) - path.add(((JMenu) menuItem).getPopupMenu()); - Component c = menuItem; while (c instanceof MenuElement) { diff --git a/javax/swing/plaf/basic/BasicMenuUI.java b/javax/swing/plaf/basic/BasicMenuUI.java index 7d8784fd1..355e0435e 100644 --- a/javax/swing/plaf/basic/BasicMenuUI.java +++ b/javax/swing/plaf/basic/BasicMenuUI.java @@ -41,16 +41,22 @@ package javax.swing.plaf.basic; import gnu.classpath.NotImplementedException; import java.awt.Component; +import java.awt.Container; import java.awt.Dimension; +import java.awt.Point; +import java.awt.event.ActionEvent; import java.awt.event.MouseEvent; import java.beans.PropertyChangeListener; +import javax.swing.AbstractAction; import javax.swing.JComponent; import javax.swing.JMenu; import javax.swing.JMenuBar; import javax.swing.JPopupMenu; import javax.swing.LookAndFeel; +import javax.swing.MenuElement; import javax.swing.MenuSelectionManager; +import javax.swing.Timer; import javax.swing.UIDefaults; import javax.swing.UIManager; import javax.swing.event.ChangeEvent; @@ -69,6 +75,32 @@ import javax.swing.plaf.ComponentUI; */ public class BasicMenuUI extends BasicMenuItemUI { + /** + * Selects a menu. This is used to delay menu selection. + */ + class SelectMenuAction + extends AbstractAction + { + /** + * Performs the action. + */ + public void actionPerformed(ActionEvent event) + { + JMenu menu = (JMenu) menuItem; + MenuSelectionManager defaultManager = + MenuSelectionManager.defaultManager(); + MenuElement path[] = defaultManager.getSelectedPath(); + if(path.length > 0 && path[path.length - 1] == menu) + { + MenuElement newPath[] = new MenuElement[path.length + 1]; + System.arraycopy(path, 0, newPath, 0, path.length); + newPath[path.length] = menu.getPopupMenu(); + defaultManager.setSelectedPath(newPath); + } + } + + } + protected ChangeListener changeListener; /* MenuListener listens to MenuEvents fired by JMenu */ @@ -201,6 +233,7 @@ public class BasicMenuUI extends BasicMenuItemUI */ protected void installDefaults() { + LookAndFeel.installBorder(menuItem, "Menu.border"); LookAndFeel.installColorsAndFont(menuItem, "Menu.background", "Menu.foreground", "Menu.font"); @@ -212,6 +245,7 @@ public class BasicMenuUI extends BasicMenuItemUI selectionForeground = UIManager.getColor("Menu.selectionForeground"); arrowIcon = UIManager.getIcon("Menu.arrowIcon"); oldBorderPainted = UIManager.getBoolean("Menu.borderPainted"); + ((JMenu) menuItem).setDelay(200); } /** @@ -234,9 +268,10 @@ public class BasicMenuUI extends BasicMenuItemUI } protected void setupPostTimer(JMenu menu) - throws NotImplementedException { - // TODO: Implement this properly. + Timer timer = new Timer(menu.getDelay(), new SelectMenuAction()); + timer.setRepeats(false); + timer.start(); } /** @@ -285,8 +320,7 @@ public class BasicMenuUI extends BasicMenuItemUI { public void mouseClicked(MouseEvent e) { - MenuSelectionManager manager = MenuSelectionManager.defaultManager(); - manager.processMouseEvent(e); + // Nothing to do here. } public void mouseDragged(MouseEvent e) @@ -313,29 +347,46 @@ public class BasicMenuUI extends BasicMenuItemUI public void mouseEntered(MouseEvent e) { - /* When mouse enters menu item, it should be considered selected - - if (i) if this menu is a submenu in some other menu - (ii) or if this menu is in a menu bar and some other menu in a - menu bar was just selected and has its popup menu visible. - (If nothing was selected, menu should be pressed before - it will be selected) - */ JMenu menu = (JMenu) menuItem; - - // NOTE: the following if used to require !menu.isArmed but I could find - // no reason for this and it was preventing some JDK-compatible behaviour. - // Specifically, if a menu is selected but its popup menu not visible, - // and then another menu is selected whose popup menu IS visible, when - // the mouse is moved over the first menu, its popup menu should become - // visible. - - if (! menu.isTopLevelMenu() || popupVisible()) + if (menu.isEnabled()) { - // set new selection and forward this event to MenuSelectionManager - MenuSelectionManager manager = MenuSelectionManager.defaultManager(); - manager.setSelectedPath(getPath()); - manager.processMouseEvent(e); + MenuSelectionManager manager = + MenuSelectionManager.defaultManager(); + MenuElement[] selectedPath = manager.getSelectedPath(); + if (! menu.isTopLevelMenu()) + { + // Open the menu immediately or delayed, depending on the + // delay value. + if(! (selectedPath.length > 0 + && selectedPath[selectedPath.length - 1] == menu.getPopupMenu())) + { + if(menu.getDelay() == 0) + { + MenuElement[] path = getPath(); + MenuElement[] newPath = new MenuElement[path.length + 1]; + System.arraycopy(path, 0, newPath, 0, path.length); + newPath[path.length] = menu.getPopupMenu(); + manager.setSelectedPath(newPath); + } + else + { + manager.setSelectedPath(getPath()); + setupPostTimer(menu); + } + } + } + else + { + if(selectedPath.length > 0 + && selectedPath[0] == menu.getParent()) + { + MenuElement[] newPath = new MenuElement[3]; + newPath[0] = (MenuElement) menu.getParent(); + newPath[1] = menu; + newPath[2] = menu.getPopupMenu(); + manager.setSelectedPath(newPath); + } + } } } @@ -354,29 +405,48 @@ public class BasicMenuUI extends BasicMenuItemUI { MenuSelectionManager manager = MenuSelectionManager.defaultManager(); JMenu menu = (JMenu) menuItem; - manager.processMouseEvent(e); - - // Menu should be displayed when the menu is pressed only if - // it is top-level menu - if (menu.isTopLevelMenu()) + if (menu.isEnabled()) { - if (menu.getPopupMenu().isVisible()) - // If menu is visible and menu button was pressed.. - // then need to cancel the menu - manager.clearSelectedPath(); - else - { - // Display the menu - int x = 0; - int y = menu.getHeight(); - - manager.setSelectedPath(getPath()); - - JMenuBar mb = (JMenuBar) menu.getParent(); - - // set selectedIndex of the selectionModel of a menuBar - mb.getSelectionModel().setSelectedIndex(mb.getComponentIndex(menu)); - } + // Open up the menu immediately if it's a toplevel menu. + // But not yet the popup, which might be opened delayed, see below. + if (menu.isTopLevelMenu()) + { + if (menu.isSelected()) + manager.clearSelectedPath(); + else + { + Container cnt = menu.getParent(); + if (cnt != null && cnt instanceof JMenuBar) + { + MenuElement[] me = new MenuElement[2]; + me[0] = (MenuElement) cnt; + me[1] = menu; + manager.setSelectedPath(me); + } + } + } + + // Open the menu's popup. Either do that immediately if delay == 0, + // or delayed when delay > 0. + MenuElement[] selectedPath = manager.getSelectedPath(); + if (selectedPath.length > 0 + && selectedPath[selectedPath.length - 1] != menu.getPopupMenu()) + { + if(menu.isTopLevelMenu() || menu.getDelay() == 0) + { + MenuElement[] newPath = + new MenuElement[selectedPath.length + 1]; + System.arraycopy(selectedPath, 0, newPath, 0, + selectedPath.length); + newPath[selectedPath.length] = menu.getPopupMenu(); + manager.setSelectedPath(newPath); + } + else + { + setupPostTimer(menu); + } + } + } } @@ -493,8 +563,44 @@ public class BasicMenuUI extends BasicMenuItemUI */ public void menuDragMouseDragged(MenuDragMouseEvent e) { - MenuSelectionManager manager = MenuSelectionManager.defaultManager(); - manager.setSelectedPath(e.getPath()); + if (menuItem.isEnabled()) + { + MenuSelectionManager manager = e.getMenuSelectionManager(); + MenuElement path[] = e.getPath(); + + Point p = e.getPoint(); + if(p.x >= 0 && p.x < menuItem.getWidth() + && p.y >= 0 && p.y < menuItem.getHeight()) + { + JMenu menu = (JMenu) menuItem; + MenuElement[] selectedPath = manager.getSelectedPath(); + if(! (selectedPath.length > 0 + && selectedPath[selectedPath.length-1] + == menu.getPopupMenu())) + { + if(menu.isTopLevelMenu() || menu.getDelay() == 0 + || e.getID() == MouseEvent.MOUSE_DRAGGED) + { + MenuElement[] newPath = new MenuElement[path.length + 1]; + System.arraycopy(path, 0, newPath, 0, path.length); + newPath[path.length] = menu.getPopupMenu(); + manager.setSelectedPath(newPath); + } + else + { + manager.setSelectedPath(path); + setupPostTimer(menu); + } + } + } + else if (e.getID() == MouseEvent.MOUSE_RELEASED) + { + Component comp = manager.componentForPoint(e.getComponent(), + e.getPoint()); + if (comp == null) + manager.clearSelectedPath(); + } + } } /** @@ -505,8 +611,7 @@ public class BasicMenuUI extends BasicMenuItemUI */ public void menuDragMouseEntered(MenuDragMouseEvent e) { - MenuSelectionManager manager = MenuSelectionManager.defaultManager(); - manager.setSelectedPath(e.getPath()); + // Nothing to do here. } /** diff --git a/javax/swing/plaf/basic/BasicOptionPaneUI.java b/javax/swing/plaf/basic/BasicOptionPaneUI.java index 27bcb8c46..e23808580 100644 --- a/javax/swing/plaf/basic/BasicOptionPaneUI.java +++ b/javax/swing/plaf/basic/BasicOptionPaneUI.java @@ -38,13 +38,12 @@ exception statement from your version. */ package javax.swing.plaf.basic; -import gnu.classpath.NotImplementedException; - import java.awt.BorderLayout; import java.awt.Color; import java.awt.Component; import java.awt.Container; import java.awt.Dimension; +import java.awt.Font; import java.awt.Graphics; import java.awt.GridBagConstraints; import java.awt.GridBagLayout; @@ -58,10 +57,14 @@ import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.beans.PropertyVetoException; +import javax.swing.AbstractAction; +import javax.swing.Action; +import javax.swing.ActionMap; import javax.swing.BorderFactory; import javax.swing.Box; import javax.swing.BoxLayout; import javax.swing.Icon; +import javax.swing.InputMap; import javax.swing.JButton; import javax.swing.JComboBox; import javax.swing.JComponent; @@ -76,6 +79,7 @@ import javax.swing.LookAndFeel; import javax.swing.SwingUtilities; import javax.swing.UIManager; import javax.swing.border.Border; +import javax.swing.plaf.ActionMapUIResource; import javax.swing.plaf.ComponentUI; import javax.swing.plaf.OptionPaneUI; @@ -85,6 +89,21 @@ import javax.swing.plaf.OptionPaneUI; public class BasicOptionPaneUI extends OptionPaneUI { /** + * Implements the "close" keyboard action. + */ + static class OptionPaneCloseAction + extends AbstractAction + { + + public void actionPerformed(ActionEvent event) + { + JOptionPane op = (JOptionPane) event.getSource(); + op.setValue(new Integer(JOptionPane.CLOSED_OPTION)); + } + + } + + /** * This is a helper class that listens to the buttons located at the bottom * of the JOptionPane. * @@ -389,36 +408,20 @@ public class BasicOptionPaneUI extends OptionPaneUI */ public void propertyChange(PropertyChangeEvent e) { - if (e.getPropertyName().equals(JOptionPane.ICON_PROPERTY) - || e.getPropertyName().equals(JOptionPane.MESSAGE_TYPE_PROPERTY)) - addIcon(messageAreaContainer); - else if (e.getPropertyName().equals(JOptionPane.INITIAL_SELECTION_VALUE_PROPERTY)) - resetSelectedValue(); - else if (e.getPropertyName().equals(JOptionPane.INITIAL_VALUE_PROPERTY) - || e.getPropertyName().equals(JOptionPane.OPTIONS_PROPERTY) - || e.getPropertyName().equals(JOptionPane.OPTION_TYPE_PROPERTY)) - { - Container newButtons = createButtonArea(); - optionPane.remove(buttonContainer); - optionPane.add(newButtons); - buttonContainer = newButtons; - } - - else if (e.getPropertyName().equals(JOptionPane.MESSAGE_PROPERTY) - || e.getPropertyName().equals(JOptionPane.WANTS_INPUT_PROPERTY) - || e.getPropertyName().equals(JOptionPane.SELECTION_VALUES_PROPERTY)) + String property = e.getPropertyName(); + if (property.equals(JOptionPane.ICON_PROPERTY) + || property.equals(JOptionPane.INITIAL_SELECTION_VALUE_PROPERTY) + || property.equals(JOptionPane.INITIAL_VALUE_PROPERTY) + || property.equals(JOptionPane.MESSAGE_PROPERTY) + || property.equals(JOptionPane.MESSAGE_TYPE_PROPERTY) + || property.equals(JOptionPane.OPTION_TYPE_PROPERTY) + || property.equals(JOptionPane.OPTIONS_PROPERTY) + || property.equals(JOptionPane.WANTS_INPUT_PROPERTY)) { - optionPane.remove(messageAreaContainer); - messageAreaContainer = createMessageArea(); - optionPane.add(messageAreaContainer); - Container newButtons = createButtonArea(); - optionPane.remove(buttonContainer); - optionPane.add(newButtons); - buttonContainer = newButtons; - optionPane.add(buttonContainer); + uninstallComponents(); + installComponents(); + optionPane.validate(); } - optionPane.invalidate(); - optionPane.repaint(); } } @@ -462,15 +465,6 @@ public class BasicOptionPaneUI extends OptionPaneUI /** The size of the icons. */ private static final int ICON_SIZE = 36; - /** The foreground color for the message area. */ - private transient Color messageForeground; - - /** The border around the message area. */ - private transient Border messageBorder; - - /** The border around the button area. */ - private transient Border buttonBorder; - /** The string used to describe OK buttons. */ private static final String OK_STRING = "OK"; @@ -700,6 +694,7 @@ public class BasicOptionPaneUI extends OptionPaneUI if (icon != null) { iconLabel = new JLabel(icon); + configureLabel(iconLabel); top.add(iconLabel, BorderLayout.WEST); } } @@ -761,7 +756,9 @@ public class BasicOptionPaneUI extends OptionPaneUI } else if (msg instanceof Icon) { - container.add(new JLabel((Icon) msg), cons); + JLabel label = new JLabel((Icon) msg); + configureLabel(label); + container.add(label, cons); cons.gridy++; } else @@ -778,8 +775,11 @@ public class BasicOptionPaneUI extends OptionPaneUI addMessageComponents(container, cons, tmp, maxll, true); } else - addMessageComponents(container, cons, new JLabel(msg.toString()), - maxll, true); + { + JLabel label = new JLabel(msg.toString()); + configureLabel(label); + addMessageComponents(container, cons, label, maxll, true); + } } } @@ -810,6 +810,7 @@ public class BasicOptionPaneUI extends OptionPaneUI remainder = d.substring(maxll); } JLabel label = new JLabel(line); + configureLabel(label); c.add(label); // If there is nothing left to burst, then we can stop. @@ -820,8 +821,12 @@ public class BasicOptionPaneUI extends OptionPaneUI if (remainder.length() > maxll || remainder.contains("\n")) burstStringInto(c, remainder, maxll); else - // Add the remainder to the container and be done. - c.add(new JLabel(remainder)); + { + // Add the remainder to the container and be done. + JLabel l = new JLabel(remainder); + configureLabel(l); + c.add(l); + } } /** @@ -857,6 +862,9 @@ public class BasicOptionPaneUI extends OptionPaneUI protected Container createButtonArea() { JPanel buttonPanel = new JPanel(); + Border b = UIManager.getBorder("OptionPane.buttonAreaBorder"); + if (b != null) + buttonPanel.setBorder(b); buttonPanel.setLayout(createLayoutManager()); addButtonComponents(buttonPanel, getButtons(), getInitialValueIndex()); @@ -882,6 +890,10 @@ public class BasicOptionPaneUI extends OptionPaneUI protected Container createMessageArea() { JPanel messageArea = new JPanel(); + Border messageBorder = UIManager.getBorder("OptionPane.messageAreaBorder"); + if (messageBorder != null) + messageArea.setBorder(messageBorder); + messageArea.setLayout(new BorderLayout()); addIcon(messageArea); @@ -935,10 +947,10 @@ public class BasicOptionPaneUI extends OptionPaneUI * @return A Container that will separate the message and button areas. */ protected Container createSeparator() - throws NotImplementedException { - // FIXME: Figure out what this method is supposed to return and where - // this should be added to the OptionPane. + // The reference implementation returns null here. When overriding + // to return something non-null, the component gets added between + // the message area and the button area. See installComponents(). return null; } @@ -1139,35 +1151,17 @@ public class BasicOptionPaneUI extends OptionPaneUI */ protected void installComponents() { - // reset it. - hasCustomComponents = false; - Container msg = createMessageArea(); - if (msg != null) - { - ((JComponent) msg).setBorder(messageBorder); - msg.setForeground(messageForeground); - messageAreaContainer = msg; - optionPane.add(msg); - } + // First thing is the message area. + optionPane.add(createMessageArea()); - // FIXME: Figure out if the separator should be inserted here or what - // this thing is supposed to do. Note: The JDK does NOT insert another - // component at this place. The JOptionPane only has two panels in it - // and there actually are applications that depend on this beeing so. + // Add separator when createSeparator() is overridden to return + // something other than null. Container sep = createSeparator(); if (sep != null) optionPane.add(sep); - Container button = createButtonArea(); - if (button != null) - { - ((JComponent) button).setBorder(buttonBorder); - buttonContainer = button; - optionPane.add(button); - } - - optionPane.setBorder(BorderFactory.createEmptyBorder(12, 12, 11, 11)); - optionPane.invalidate(); + // Last thing is the button area. + optionPane.add(createButtonArea()); } /** @@ -1181,10 +1175,6 @@ public class BasicOptionPaneUI extends OptionPaneUI LookAndFeel.installBorder(optionPane, "OptionPane.border"); optionPane.setOpaque(true); - messageBorder = UIManager.getBorder("OptionPane.messageAreaBorder"); - messageForeground = UIManager.getColor("OptionPane.messageForeground"); - buttonBorder = UIManager.getBorder("OptionPane.buttonAreaBorder"); - minimumSize = UIManager.getDimension("OptionPane.minimumSize"); // FIXME: Image icons don't seem to work properly right now. @@ -1202,9 +1192,44 @@ public class BasicOptionPaneUI extends OptionPaneUI * This method installs keyboard actions for the JOptionpane. */ protected void installKeyboardActions() - throws NotImplementedException { - // FIXME: implement. + // Install the input map. + Object[] bindings = + (Object[]) SharedUIDefaults.get("OptionPane.windowBindings"); + InputMap inputMap = LookAndFeel.makeComponentInputMap(optionPane, + bindings); + SwingUtilities.replaceUIInputMap(optionPane, + JComponent.WHEN_IN_FOCUSED_WINDOW, + inputMap); + + // FIXME: The JDK uses a LazyActionMap for parentActionMap + SwingUtilities.replaceUIActionMap(optionPane, getActionMap()); + } + + /** + * Fetches the action map from the UI defaults, or create a new one + * if the action map hasn't been initialized. + * + * @return the action map + */ + private ActionMap getActionMap() + { + ActionMap am = (ActionMap) UIManager.get("OptionPane.actionMap"); + if (am == null) + { + am = createDefaultActions(); + UIManager.getLookAndFeelDefaults().put("OptionPane.actionMap", am); + } + return am; + } + + private ActionMap createDefaultActions() + { + ActionMapUIResource am = new ActionMapUIResource(); + Action action = new OptionPaneCloseAction(); + + am.put("close", action); + return am; } /** @@ -1317,10 +1342,6 @@ public class BasicOptionPaneUI extends OptionPaneUI minimumSize = null; - messageBorder = null; - buttonBorder = null; - messageForeground = null; - // FIXME: ImageIcons don't seem to work properly /* @@ -1335,9 +1356,10 @@ public class BasicOptionPaneUI extends OptionPaneUI * This method uninstalls keyboard actions for the JOptionPane. */ protected void uninstallKeyboardActions() - throws NotImplementedException { - // FIXME: implement. + SwingUtilities.replaceUIInputMap(optionPane, JComponent. + WHEN_ANCESTOR_OF_FOCUSED_COMPONENT, null); + SwingUtilities.replaceUIActionMap(optionPane, null); } /** @@ -1363,4 +1385,20 @@ public class BasicOptionPaneUI extends OptionPaneUI optionPane = null; } + + /** + * Applies the proper UI configuration to labels that are added to + * the OptionPane. + * + * @param l the label to configure + */ + private void configureLabel(JLabel l) + { + Color c = UIManager.getColor("OptionPane.messageForeground"); + if (c != null) + l.setForeground(c); + Font f = UIManager.getFont("OptionPane.messageFont"); + if (f != null) + l.setFont(f); + } } diff --git a/javax/swing/plaf/basic/BasicPopupMenuUI.java b/javax/swing/plaf/basic/BasicPopupMenuUI.java index 5ceaecb91..8c0fe6757 100644 --- a/javax/swing/plaf/basic/BasicPopupMenuUI.java +++ b/javax/swing/plaf/basic/BasicPopupMenuUI.java @@ -37,33 +37,574 @@ exception statement from your version. */ package javax.swing.plaf.basic; -import gnu.classpath.NotImplementedException; - import java.awt.Component; import java.awt.Dimension; +import java.awt.KeyboardFocusManager; +import java.awt.event.ActionEvent; import java.awt.event.ComponentEvent; import java.awt.event.ComponentListener; import java.awt.event.MouseEvent; +import java.util.EventListener; +import javax.swing.AbstractAction; +import javax.swing.Action; +import javax.swing.ActionMap; import javax.swing.BoxLayout; +import javax.swing.InputMap; +import javax.swing.JApplet; import javax.swing.JComponent; +import javax.swing.JFrame; +import javax.swing.JMenu; +import javax.swing.JMenuBar; import javax.swing.JMenuItem; import javax.swing.JPopupMenu; +import javax.swing.JRootPane; import javax.swing.LookAndFeel; import javax.swing.MenuElement; import javax.swing.MenuSelectionManager; import javax.swing.SwingUtilities; +import javax.swing.UIManager; +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; import javax.swing.event.PopupMenuEvent; import javax.swing.event.PopupMenuListener; +import javax.swing.plaf.ActionMapUIResource; import javax.swing.plaf.ComponentUI; import javax.swing.plaf.PopupMenuUI; - /** * UI Delegate for JPopupMenu */ public class BasicPopupMenuUI extends PopupMenuUI { + /** + * Handles keyboard navigation through menus. + */ + private static class NavigateAction + extends AbstractAction + { + + /** + * Creates a new NavigateAction instance. + * + * @param name the name of the action + */ + NavigateAction(String name) + { + super(name); + } + + /** + * Actually performs the action. + */ + public void actionPerformed(ActionEvent event) + { + String name = (String) getValue(Action.NAME); + if (name.equals("selectNext")) + navigateNextPrevious(true); + else if (name.equals("selectPrevious")) + navigateNextPrevious(false); + else if (name.equals("selectChild")) + navigateParentChild(true); + else if (name.equals("selectParent")) + navigateParentChild(false); + else if (name.equals("cancel")) + cancel(); + else if (name.equals("return")) + doReturn(); + else + assert false : "Must not reach here"; + } + + /** + * Navigates to the next or previous menu item. + * + * @param dir <code>true</code>: navigate to next, <code>false</code>: + * navigate to previous + */ + private void navigateNextPrevious(boolean dir) + { + MenuSelectionManager msm = MenuSelectionManager.defaultManager(); + MenuElement path[] = msm.getSelectedPath(); + int len = path.length; + if (len >= 2) + { + + if (path[0] instanceof JMenuBar && + path[1] instanceof JMenu && len == 2) + { + + // A toplevel menu is selected, but its popup not yet shown. + // Show the popup and select the first item + JPopupMenu popup = ((JMenu)path[1]).getPopupMenu(); + MenuElement next = + findEnabledChild(popup.getSubElements(), -1, true); + MenuElement[] newPath; + + if (next != null) + { + newPath = new MenuElement[4]; + newPath[3] = next; + } + else + { + // Menu has no enabled items, show the popup anyway. + newPath = new MenuElement[3]; + } + System.arraycopy(path, 0, newPath, 0, 2); + newPath[2] = popup; + msm.setSelectedPath(newPath); + } + else if (path[len - 1] instanceof JPopupMenu && + path[len - 2] instanceof JMenu) + { + // Select next item in already shown popup menu. + JMenu menu = (JMenu) path[len - 2]; + JPopupMenu popup = menu.getPopupMenu(); + MenuElement next = + findEnabledChild(popup.getSubElements(), -1, dir); + + if (next != null) + { + MenuElement[] newPath = new MenuElement[len + 1]; + System.arraycopy(path, 0, newPath, 0, len); + newPath[len] = next; + msm.setSelectedPath(newPath); + } + else + { + // All items in the popup are disabled. + // Find the parent popup menu and select + // its next item. If there's no parent popup menu , do nothing. + if (len > 2 && path[len - 3] instanceof JPopupMenu) + { + popup = ((JPopupMenu) path[len - 3]); + next = findEnabledChild(popup.getSubElements(), + menu, dir); + if (next != null && next != menu) + { + MenuElement[] newPath = new MenuElement[len - 1]; + System.arraycopy(path, 0, newPath, 0, len - 2); + newPath[len - 2] = next; + msm.setSelectedPath(newPath); + } + } + } + } + else + { + // Only select the next item. + MenuElement subs[] = path[len - 2].getSubElements(); + MenuElement nextChild = + findEnabledChild(subs, path[len - 1], dir); + if (nextChild == null) + { + nextChild = findEnabledChild(subs, -1, dir); + } + if (nextChild != null) + { + path[len-1] = nextChild; + msm.setSelectedPath(path); + } + } + } + } + + private MenuElement findEnabledChild(MenuElement[] children, + MenuElement start, boolean dir) + { + MenuElement found = null; + for (int i = 0; i < children.length && found == null; i++) + { + if (children[i] == start) + { + found = findEnabledChild(children, i, dir); + } + } + return found; + } + + /** + * Searches the next or previous enabled child menu element. + * + * @param children the children to search through + * @param start the index at which to start + * @param dir the direction (true == forward, false == backward) + * + * @return the found element or null + */ + private MenuElement findEnabledChild(MenuElement[] children, + int start, boolean dir) + { + MenuElement result = null; + if (dir) + { + result = findNextEnabledChild(children, start + 1, children.length-1); + if (result == null) + result = findNextEnabledChild(children, 0, start - 1); + } + else + { + result = findPreviousEnabledChild(children, start - 1, 0); + if (result == null) + result = findPreviousEnabledChild(children, children.length-1, + start + 1); + } + return result; + } + + /** + * Finds the next child element that is enabled and visible. + * + * @param children the children to search through + * @param start the start index + * @param end the end index + * + * @return the found child, or null + */ + private MenuElement findNextEnabledChild(MenuElement[] children, int start, + int end) + { + MenuElement found = null; + for (int i = start; i <= end && found == null; i++) + { + if (children[i] != null) + { + Component comp = children[i].getComponent(); + if (comp != null && comp.isEnabled() && comp.isVisible()) + { + found = children[i]; + } + } + } + return found; + } + + /** + * Finds the previous child element that is enabled and visible. + * + * @param children the children to search through + * @param start the start index + * @param end the end index + * + * @return the found child, or null + */ + private MenuElement findPreviousEnabledChild(MenuElement[] children, + int start, int end) + { + MenuElement found = null; + for (int i = start; i >= end && found == null; i--) + { + if (children[i] != null) + { + Component comp = children[i].getComponent(); + if (comp != null && comp.isEnabled() && comp.isVisible()) + { + found = children[i]; + } + } + } + return found; + } + + /** + * Navigates to the parent or child menu item. + * + * @param selectChild <code>true</code>: navigate to child, + * <code>false</code>: navigate to parent + */ + private void navigateParentChild(boolean selectChild) + { + MenuSelectionManager msm = MenuSelectionManager.defaultManager(); + MenuElement path[] = msm.getSelectedPath(); + int len = path.length; + + if (selectChild) + { + if (len > 0 && path[len - 1] instanceof JMenu + && ! ((JMenu) path[len-1]).isTopLevelMenu()) + { + // We have a submenu, open it. + JMenu menu = (JMenu) path[len - 1]; + JPopupMenu popup = menu.getPopupMenu(); + MenuElement[] subs = popup.getSubElements(); + MenuElement item = findEnabledChild(subs, -1, true); + MenuElement[] newPath; + + if (item == null) + { + newPath = new MenuElement[len + 1]; + } + else + { + newPath = new MenuElement[len + 2]; + newPath[len + 1] = item; + } + System.arraycopy(path, 0, newPath, 0, len); + newPath[len] = popup; + msm.setSelectedPath(newPath); + return; + } + } + else + { + int popupIndex = len-1; + if (len > 2 + && (path[popupIndex] instanceof JPopupMenu + || path[--popupIndex] instanceof JPopupMenu) + && ! ((JMenu) path[popupIndex - 1]).isTopLevelMenu()) + { + // We have a submenu, close it. + MenuElement newPath[] = new MenuElement[popupIndex]; + System.arraycopy(path, 0, newPath, 0, popupIndex); + msm.setSelectedPath(newPath); + return; + } + } + + // If we got here, we have not selected a child or parent. + // Check if we have a toplevel menu selected. If so, then select + // another one. + if (len > 1 && path[0] instanceof JMenuBar) + { + MenuElement currentMenu = path[1]; + MenuElement nextMenu = findEnabledChild(path[0].getSubElements(), + currentMenu, selectChild); + + if (nextMenu != null && nextMenu != currentMenu) + { + MenuElement newSelection[]; + if (len == 2) + { + // Menu is selected but its popup not shown. + newSelection = new MenuElement[2]; + newSelection[0] = path[0]; + newSelection[1] = nextMenu; + } + else + { + // Menu is selected and its popup is shown. + newSelection = new MenuElement[3]; + newSelection[0] = path[0]; + newSelection[1] = nextMenu; + newSelection[2] = ((JMenu) nextMenu).getPopupMenu(); + } + msm.setSelectedPath(newSelection); + } + } + } + + /** + * Handles cancel requests (ESC key). + */ + private void cancel() + { + // Fire popup menu cancelled event. Unfortunately the + // firePopupMenuCancelled() is protected in JPopupMenu so we work + // around this limitation by fetching the listeners and notifying them + // directly. + JPopupMenu lastPopup = (JPopupMenu) getLastPopup(); + EventListener[] ll = lastPopup.getListeners(PopupMenuListener.class); + for (int i = 0; i < ll.length; i++) + { + PopupMenuEvent ev = new PopupMenuEvent(lastPopup); + ((PopupMenuListener) ll[i]).popupMenuCanceled(ev); + } + + // Close the last popup or the whole selection if there's only one + // popup left. + MenuSelectionManager msm = MenuSelectionManager.defaultManager(); + MenuElement path[] = msm.getSelectedPath(); + if(path.length > 4) + { + MenuElement newPath[] = new MenuElement[path.length - 2]; + System.arraycopy(path,0,newPath,0,path.length-2); + MenuSelectionManager.defaultManager().setSelectedPath(newPath); + } + else + msm.clearSelectedPath(); + } + + /** + * Returns the last popup menu in the current selection or null. + * + * @return the last popup menu in the current selection or null + */ + private JPopupMenu getLastPopup() + { + MenuSelectionManager msm = MenuSelectionManager.defaultManager(); + MenuElement[] p = msm.getSelectedPath(); + JPopupMenu popup = null; + for(int i = p.length - 1; popup == null && i >= 0; i--) + { + if (p[i] instanceof JPopupMenu) + popup = (JPopupMenu) p[i]; + } + return popup; + } + + /** + * Handles ENTER key requests. This normally opens submenus on JMenu + * items, or activates the menu item as if it's been clicked on it. + */ + private void doReturn() + { + KeyboardFocusManager fmgr = + KeyboardFocusManager.getCurrentKeyboardFocusManager(); + Component focusOwner = fmgr.getFocusOwner(); + if((focusOwner == null || (focusOwner instanceof JRootPane))) + { + MenuSelectionManager msm = MenuSelectionManager.defaultManager(); + MenuElement path[] = msm.getSelectedPath(); + MenuElement lastElement; + if(path.length > 0) + { + lastElement = path[path.length - 1]; + if(lastElement instanceof JMenu) + { + MenuElement newPath[] = new MenuElement[path.length + 1]; + System.arraycopy(path,0,newPath,0,path.length); + newPath[path.length] = ((JMenu) lastElement).getPopupMenu(); + msm.setSelectedPath(newPath); + } + else if(lastElement instanceof JMenuItem) + { + JMenuItem mi = (JMenuItem)lastElement; + if (mi.getUI() instanceof BasicMenuItemUI) + { + ((BasicMenuItemUI)mi.getUI()).doClick(msm); + } + else + { + msm.clearSelectedPath(); + mi.doClick(0); + } + } + } + } + } + } + + /** + * Installs keyboard actions when a popup is opened, and uninstalls the + * keyboard actions when closed. This listens on the default + * MenuSelectionManager. + */ + private class KeyboardHelper + implements ChangeListener + { + private MenuElement[] lastSelectedPath = new MenuElement[0]; + private Component lastFocused; + private JRootPane invokerRootPane; + + public void stateChanged(ChangeEvent event) + { + MenuSelectionManager msm = (MenuSelectionManager) event.getSource(); + MenuElement[] p = msm.getSelectedPath(); + JPopupMenu popup = getActivePopup(p); + if (popup == null || popup.isFocusable()) + { + if (lastSelectedPath.length != 0 && p.length != 0 ) + { + if (! invokerEquals(p[0], lastSelectedPath[0])) + { + uninstallKeyboardActionsImpl(); + lastSelectedPath = new MenuElement[0]; + } + } + + if (lastSelectedPath.length == 0 && p.length > 0) + { + JComponent invoker; + if (popup == null) + { + if (p.length == 2 && p[0] instanceof JMenuBar + && p[1] instanceof JMenu) + { + // A menu has been selected but not opened. + invoker = (JComponent)p[1]; + popup = ((JMenu)invoker).getPopupMenu(); + } + else + { + return; + } + } + else + { + Component c = popup.getInvoker(); + if(c instanceof JFrame) + { + invoker = ((JFrame) c).getRootPane(); + } + else if(c instanceof JApplet) + { + invoker = ((JApplet) c).getRootPane(); + } + else + { + while (!(c instanceof JComponent)) + { + if (c == null) + { + return; + } + c = c.getParent(); + } + invoker = (JComponent)c; + } + } + + // Remember current focus owner. + lastFocused = KeyboardFocusManager. + getCurrentKeyboardFocusManager().getFocusOwner(); + + // Install keybindings used for menu navigation. + invokerRootPane = SwingUtilities.getRootPane(invoker); + if (invokerRootPane != null) + { + invokerRootPane.requestFocus(true); + installKeyboardActionsImpl(); + } + } + else if (lastSelectedPath.length != 0 && p.length == 0) + { + // menu hidden -- return focus to where it had been before + // and uninstall menu keybindings + uninstallKeyboardActionsImpl(); + } + } + + // Remember the last path selected + lastSelectedPath = p; + } + + private JPopupMenu getActivePopup(MenuElement[] path) + { + JPopupMenu active = null; + for (int i = path.length - 1; i >= 0 && active == null; i--) + { + MenuElement elem = path[i]; + if (elem instanceof JPopupMenu) + { + active = (JPopupMenu) elem; + } + } + return active; + } + + private boolean invokerEquals(MenuElement el1, MenuElement el2) + { + Component invoker1 = el1.getComponent(); + Component invoker2 = el2.getComponent(); + if (invoker1 instanceof JPopupMenu) + invoker1 = ((JPopupMenu) invoker1).getInvoker(); + if (invoker2 instanceof JPopupMenu) + invoker2 = ((JPopupMenu) invoker2).getInvoker(); + return invoker1 == invoker2; + } + } + /* popupMenu for which this UI delegate is for*/ protected JPopupMenu popupMenu; @@ -75,6 +616,19 @@ public class BasicPopupMenuUI extends PopupMenuUI TopWindowListener topWindowListener; /** + * Counts how many popup menus are handled by this UI or a subclass. + * This is used to install a KeyboardHelper on the MenuSelectionManager + * for the first popup, and uninstall this same KeyboardHelper when the + * last popup is uninstalled. + */ + private static int numPopups; + + /** + * This is the KeyboardHelper that listens on the MenuSelectionManager. + */ + private static KeyboardHelper keyboardHelper; + + /** * Creates a new BasicPopupMenuUI object. */ public BasicPopupMenuUI() @@ -106,6 +660,16 @@ public class BasicPopupMenuUI extends PopupMenuUI public void installUI(JComponent c) { super.installUI(c); + + // Install KeyboardHelper when the first popup is initialized. + if (numPopups == 0) + { + keyboardHelper = new KeyboardHelper(); + MenuSelectionManager msm = MenuSelectionManager.defaultManager(); + msm.addChangeListener(keyboardHelper); + } + numPopups++; + popupMenu = (JPopupMenu) c; popupMenu.setLayout(new DefaultMenuLayout(popupMenu, BoxLayout.Y_AXIS)); popupMenu.setBorderPainted(true); @@ -113,6 +677,7 @@ public class BasicPopupMenuUI extends PopupMenuUI installDefaults(); installListeners(); + installKeyboardActions(); } /** @@ -139,9 +704,77 @@ public class BasicPopupMenuUI extends PopupMenuUI * This method installs the keyboard actions for this {@link JPopupMenu}. */ protected void installKeyboardActions() - throws NotImplementedException { - // FIXME: Need to implement + // We can't install the keyboard actions here, because then all + // popup menus would have their actions registered in the KeyboardManager. + // So we install it when the popup menu is opened, and uninstall it + // when it's closed. This is done in the KeyboardHelper class. + // Install InputMap. + } + + /** + * Called by the KeyboardHandler when a popup is made visible. + */ + void installKeyboardActionsImpl() + { + Object[] bindings; + if (popupMenu.getComponentOrientation().isLeftToRight()) + { + bindings = (Object[]) + SharedUIDefaults.get("PopupMenu.selectedWindowInputMapBindings"); + } + else + { + bindings = (Object[]) SharedUIDefaults.get + ("PopupMenu.selectedWindowInputMapBindings.RightToLeft"); + } + InputMap inputMap = LookAndFeel.makeComponentInputMap(popupMenu, bindings); + SwingUtilities.replaceUIInputMap(popupMenu, + JComponent.WHEN_IN_FOCUSED_WINDOW, + inputMap); + + // Install ActionMap. + SwingUtilities.replaceUIActionMap(popupMenu, getActionMap()); + } + + /** + * Creates and returns the shared action map for JTrees. + * + * @return the shared action map for JTrees + */ + private ActionMap getActionMap() + { + ActionMap am = (ActionMap) UIManager.get("PopupMenu.actionMap"); + if (am == null) + { + am = createDefaultActions(); + UIManager.getLookAndFeelDefaults().put("PopupMenu.actionMap", am); + } + return am; + } + + /** + * Creates the default actions when there are none specified by the L&F. + * + * @return the default actions + */ + private ActionMap createDefaultActions() + { + ActionMapUIResource am = new ActionMapUIResource(); + Action action = new NavigateAction("selectNext"); + am.put(action.getValue(Action.NAME), action); + action = new NavigateAction("selectPrevious"); + am.put(action.getValue(Action.NAME), action); + action = new NavigateAction("selectParent"); + am.put(action.getValue(Action.NAME), action); + action = new NavigateAction("selectChild"); + am.put(action.getValue(Action.NAME), action); + action = new NavigateAction("return"); + am.put(action.getValue(Action.NAME), action); + action = new NavigateAction("cancel"); + am.put(action.getValue(Action.NAME), action); + + return am; } /** @@ -155,7 +788,17 @@ public class BasicPopupMenuUI extends PopupMenuUI { uninstallListeners(); uninstallDefaults(); + uninstallKeyboardActions(); popupMenu = null; + + // Install KeyboardHelper when the first popup is initialized. + numPopups--; + if (numPopups == 0) + { + MenuSelectionManager msm = MenuSelectionManager.defaultManager(); + msm.removeChangeListener(keyboardHelper); + } + } /** @@ -182,9 +825,22 @@ public class BasicPopupMenuUI extends PopupMenuUI * Uninstalls any keyboard actions. */ protected void uninstallKeyboardActions() - throws NotImplementedException { - // FIXME: Need to implement + // We can't install the keyboard actions here, because then all + // popup menus would have their actions registered in the KeyboardManager. + // So we install it when the popup menu is opened, and uninstall it + // when it's closed. This is done in the KeyboardHelper class. + // Install InputMap. + } + + /** + * Called by the KeyboardHandler when a popup is made invisible. + */ + void uninstallKeyboardActionsImpl() + { + SwingUtilities.replaceUIInputMap(popupMenu, + JComponent.WHEN_IN_FOCUSED_WINDOW, null); + SwingUtilities.replaceUIActionMap(popupMenu, null); } /** diff --git a/javax/swing/plaf/basic/BasicScrollBarUI.java b/javax/swing/plaf/basic/BasicScrollBarUI.java index 78e5168fc..ee246cbba 100644 --- a/javax/swing/plaf/basic/BasicScrollBarUI.java +++ b/javax/swing/plaf/basic/BasicScrollBarUI.java @@ -1228,8 +1228,12 @@ public class BasicScrollBarUI extends ScrollBarUI implements LayoutManager, */ protected void scrollByBlock(int direction) { + if (direction > 0) scrollbar.setValue(scrollbar.getValue() + scrollbar.getBlockIncrement(direction)); + else + scrollbar.setValue(scrollbar.getValue() + - scrollbar.getBlockIncrement(direction)); } /** @@ -1239,8 +1243,12 @@ public class BasicScrollBarUI extends ScrollBarUI implements LayoutManager, */ protected void scrollByUnit(int direction) { - scrollbar.setValue(scrollbar.getValue() - + scrollbar.getUnitIncrement(direction)); + if (direction > 0) + scrollbar.setValue(scrollbar.getValue() + + scrollbar.getUnitIncrement(direction)); + else + scrollbar.setValue(scrollbar.getValue() + - scrollbar.getUnitIncrement(direction)); } /** diff --git a/javax/swing/plaf/basic/BasicTabbedPaneUI.java b/javax/swing/plaf/basic/BasicTabbedPaneUI.java index 17fa6075b..11f25167d 100644 --- a/javax/swing/plaf/basic/BasicTabbedPaneUI.java +++ b/javax/swing/plaf/basic/BasicTabbedPaneUI.java @@ -1,5 +1,5 @@ /* BasicTabbedPaneUI.java -- - Copyright (C) 2002, 2004, 2005 Free Software Foundation, Inc. + Copyright (C) 2002, 2004, 2005, 2006 Free Software Foundation, Inc. This file is part of GNU Classpath. @@ -38,8 +38,6 @@ exception statement from your version. */ package javax.swing.plaf.basic; -import gnu.classpath.NotImplementedException; - import java.awt.Color; import java.awt.Component; import java.awt.Container; @@ -51,6 +49,7 @@ import java.awt.Insets; import java.awt.LayoutManager; import java.awt.Point; import java.awt.Rectangle; +import java.awt.event.ActionEvent; import java.awt.event.FocusAdapter; import java.awt.event.FocusEvent; import java.awt.event.FocusListener; @@ -60,7 +59,10 @@ import java.awt.event.MouseListener; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; +import javax.swing.AbstractAction; +import javax.swing.ActionMap; import javax.swing.Icon; +import javax.swing.InputMap; import javax.swing.JComponent; import javax.swing.JPanel; import javax.swing.JTabbedPane; @@ -72,6 +74,7 @@ import javax.swing.SwingUtilities; import javax.swing.UIManager; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; +import javax.swing.plaf.ActionMapUIResource; import javax.swing.plaf.ComponentUI; import javax.swing.plaf.PanelUI; import javax.swing.plaf.TabbedPaneUI; @@ -80,11 +83,127 @@ import javax.swing.text.View; /** * This is the Basic Look and Feel's UI delegate for JTabbedPane. + * + * @author Lillian Angel (langel@redhat.com) + * @author Kim Ho (kho@redhat.com) + * @author Roman Kennke (kennke@aicas.com) + * @author Robert Schuster (robertschuster@fsfe.org) */ public class BasicTabbedPaneUI extends TabbedPaneUI implements SwingConstants { + + static class NavigateAction extends AbstractAction + { + int direction; + + NavigateAction(String name, int dir) + { + super(name); + direction = dir; + } + + public void actionPerformed(ActionEvent event) + { + JTabbedPane tp = (JTabbedPane) event.getSource(); + BasicTabbedPaneUI ui = (BasicTabbedPaneUI) tp.getUI(); + + ui.navigateSelectedTab(direction); + } + + } + + static class NavigatePageDownAction extends AbstractAction + { + + public NavigatePageDownAction() + { + super("navigatePageDown"); + } + + public void actionPerformed(ActionEvent event) + { + JTabbedPane tp = (JTabbedPane) event.getSource(); + BasicTabbedPaneUI ui = (BasicTabbedPaneUI) tp.getUI(); + + int i = tp.getSelectedIndex(); + + if (i < 0) + i = 0; + + ui.selectNextTabInRun(i); + } + + } + + static class NavigatePageUpAction extends AbstractAction + { + + public NavigatePageUpAction() + { + super("navigatePageUp"); + } + + public void actionPerformed(ActionEvent event) + { + JTabbedPane tp = (JTabbedPane) event.getSource(); + BasicTabbedPaneUI ui = (BasicTabbedPaneUI) tp.getUI(); + + int i = tp.getSelectedIndex(); + + if (i < 0) + i = 0; + + ui.selectPreviousTabInRun(i); + + } + } + + static class RequestFocusAction extends AbstractAction + { + + public RequestFocusAction() + { + super("requestFocus"); + } + + public void actionPerformed(ActionEvent event) + { + ((JTabbedPane) event.getSource()).requestFocus(); + } + + } + + static class RequestFocusForVisibleComponentAction extends AbstractAction + { + + public RequestFocusForVisibleComponentAction() + { + super("requestFocusForVisibleComponent"); + } + + public void actionPerformed(ActionEvent event) + { + JTabbedPane tp = (JTabbedPane) event.getSource(); + + // FIXME: This should select a suitable component within + // the tab content. However I dont know whether we have + // to search for this component or wether the called is + // supposed to do that. + tp.getSelectedComponent().requestFocus(); + } + + } + /** - * A helper class that handles focus. + * A helper class that handles focus. + * <p>The purpose of this class is to implement a more flexible focus + * handling for the tabbed pane, which is used to determine whether the + * focus indicator should be painted or not. When in scrolling layout + * mode the area containing the tabs is a scrollpane, so simply testing + * whether the tabbed pane has the focus does not work.</p> + * <p>The <code>FocusHandler</code> is installed on the scrollpane and + * the tabbed pane and sets the variable <code>hasFocus</code> to + * <code>false</code> only when both components do not hold the focus.</p> * * @specnote Apparently this class was intended to be protected, * but was made public by a compiler bug and is now @@ -98,9 +217,12 @@ public class BasicTabbedPaneUI extends TabbedPaneUI implements SwingConstants * @param e The FocusEvent. */ public void focusGained(FocusEvent e) - throws NotImplementedException { - // FIXME: Implement. + Object source = e.getSource(); + if (source == panel ) + tabPane.requestFocus(); + else if (source == tabPane) + tabPane.repaint(); } /** @@ -109,9 +231,11 @@ public class BasicTabbedPaneUI extends TabbedPaneUI implements SwingConstants * @param e The FocusEvent. */ public void focusLost(FocusEvent e) - throws NotImplementedException { - // FIXME: Implement. + if (e.getOppositeComponent() == tabPane.getSelectedComponent()) + tabPane.requestFocus(); + else if (e.getSource() == tabPane) + tabPane.repaint(); } } @@ -126,6 +250,11 @@ public class BasicTabbedPaneUI extends TabbedPaneUI implements SwingConstants */ public class MouseHandler extends MouseAdapter { + public void mouseReleased(MouseEvent e) + { + // Nothing to do here. + } + /** * This method is called when the mouse is pressed. The index cannot * change to a tab that is not enabled. @@ -134,14 +263,84 @@ public class BasicTabbedPaneUI extends TabbedPaneUI implements SwingConstants */ public void mousePressed(MouseEvent e) { - if (tabPane.isEnabled()) + Object s = e.getSource(); + int placement = tabPane.getTabPlacement(); + + if (s == incrButton) { - int index = tabForCoordinate(tabPane, e.getX(), e.getY()); - if (index >= 0 && tabPane.isEnabledAt(index)) + if(!incrButton.isEnabled()) + return; + + currentScrollLocation++; + + switch (placement) { - tabPane.setSelectedIndex(index); + case JTabbedPane.TOP: + case JTabbedPane.BOTTOM: + currentScrollOffset = getTabAreaInsets(placement).left; + for (int i = 0; i < currentScrollLocation; i++) + currentScrollOffset += rects[i].width; + break; + default: + currentScrollOffset = getTabAreaInsets(placement).top; + for (int i = 0; i < currentScrollLocation; i++) + currentScrollOffset += rects[i].height; + break; } + + updateViewPosition(); + updateButtons(); + + tabPane.repaint(); + } + else if (s == decrButton) + { + if(!decrButton.isEnabled()) + return; + + // The scroll location may be zero but the offset + // greater than zero because of an adjustement to + // make a partially visible tab completely visible. + if (currentScrollLocation > 0) + currentScrollLocation--; + + // Set the offset back to 0 and recompute it. + currentScrollOffset = 0; + + switch (placement) + { + case JTabbedPane.TOP: + case JTabbedPane.BOTTOM: + // Take the tab area inset into account. + if (currentScrollLocation > 0) + currentScrollOffset = getTabAreaInsets(placement).left; + // Recompute scroll offset. + for (int i = 0; i < currentScrollLocation; i++) + currentScrollOffset += rects[i].width; + break; + default: + // Take the tab area inset into account. + if (currentScrollLocation > 0) + currentScrollOffset = getTabAreaInsets(placement).top; + + for (int i = 0; i < currentScrollLocation; i++) + currentScrollOffset += rects[i].height; + } + + updateViewPosition(); + updateButtons(); + + tabPane.repaint(); + } else if (tabPane.isEnabled()) + { + int index = tabForCoordinate(tabPane, e.getX(), e.getY()); + if (tabPane.getTabLayoutPolicy() == JTabbedPane.SCROLL_TAB_LAYOUT + && s == panel) + scrollTab(index, placement); + + tabPane.setSelectedIndex(index); } + } /** @@ -199,6 +398,8 @@ public class BasicTabbedPaneUI extends TabbedPaneUI implements SwingConstants { if (e.getPropertyName().equals("tabLayoutPolicy")) { + currentScrollLocation = currentScrollOffset = 0; + layoutManager = createLayoutManager(); tabPane.setLayout(layoutManager); @@ -267,7 +468,8 @@ public class BasicTabbedPaneUI extends TabbedPaneUI implements SwingConstants // Find out the minimum/preferred size to display the largest child // of the tabbed pane. - for (int i = 0; i < tabPane.getTabCount(); i++) + int count = tabPane.getTabCount(); + for (int i = 0; i < count; i++) { c = tabPane.getComponentAt(i); if (c == null) @@ -284,21 +486,19 @@ public class BasicTabbedPaneUI extends TabbedPaneUI implements SwingConstants if (tabPlacement == SwingConstants.TOP || tabPlacement == SwingConstants.BOTTOM) { - int min = calculateMaxTabWidth(tabPlacement); - width = Math.max(min, width); - int tabAreaHeight = preferredTabAreaHeight(tabPlacement, - width - tabAreaInsets.left - - tabAreaInsets.right); - height += tabAreaHeight; + width = Math.max(calculateMaxTabWidth(tabPlacement), width); + + height += preferredTabAreaHeight(tabPlacement, + width - tabAreaInsets.left + - tabAreaInsets.right); } else { - int min = calculateMaxTabHeight(tabPlacement); - height = Math.max(min, height); - int tabAreaWidth = preferredTabAreaWidth(tabPlacement, - height - tabAreaInsets.top - - tabAreaInsets.bottom); - width += tabAreaWidth; + height = Math.max(calculateMaxTabHeight(tabPlacement), height); + + width += preferredTabAreaWidth(tabPlacement, + height - tabAreaInsets.top + - tabAreaInsets.bottom); } Insets tabPaneInsets = tabPane.getInsets(); @@ -308,7 +508,8 @@ public class BasicTabbedPaneUI extends TabbedPaneUI implements SwingConstants // if tab placement is LEFT OR RIGHT, they share width. // if tab placement is TOP OR BOTTOM, they share height - // PRE STEP: finds the default sizes for the labels as well as their locations. + // PRE STEP: finds the default sizes for the labels as well as their + // locations. // AND where they will be placed within the run system. // 1. calls normalizeTab Runs. // 2. calls rotate tab runs. @@ -347,7 +548,8 @@ public class BasicTabbedPaneUI extends TabbedPaneUI implements SwingConstants break; case RIGHT: maxTabWidth = calculateMaxTabWidth(tabPlacement); - x = size.width - (insets.right + tabAreaInsets.right) - maxTabWidth; + x = size.width - (insets.right + tabAreaInsets.right) + - maxTabWidth - 1; y = insets.top + tabAreaInsets.top; breakAt = size.height - (insets.bottom + tabAreaInsets.bottom); break; @@ -355,7 +557,7 @@ public class BasicTabbedPaneUI extends TabbedPaneUI implements SwingConstants maxTabHeight = calculateMaxTabHeight(tabPlacement); x = insets.left + tabAreaInsets.left; y = size.height - (insets.bottom + tabAreaInsets.bottom) - - maxTabHeight; + - maxTabHeight - 1; breakAt = size.width - (insets.right + tabAreaInsets.right); break; case TOP: @@ -374,6 +576,8 @@ public class BasicTabbedPaneUI extends TabbedPaneUI implements SwingConstants runCount = 0; selectedRun = -1; int selectedIndex = tabPane.getSelectedIndex(); + if (selectedIndex < 0) + selectedIndex = 0; Rectangle rect; @@ -411,7 +615,6 @@ public class BasicTabbedPaneUI extends TabbedPaneUI implements SwingConstants rect.height = maxTabHeight; if (i == selectedIndex) selectedRun = runCount - 1; - } } else @@ -456,9 +659,9 @@ public class BasicTabbedPaneUI extends TabbedPaneUI implements SwingConstants int start; if (tabPlacement == SwingConstants.TOP || tabPlacement == SwingConstants.BOTTOM) - start = y; - else start = x; + else + start = y; normalizeTabRuns(tabPlacement, tabCount, start, breakAt); selectedRun = getRunForTab(tabCount, selectedIndex); if (shouldRotateTabRuns(tabPlacement)) @@ -466,7 +669,11 @@ public class BasicTabbedPaneUI extends TabbedPaneUI implements SwingConstants rotateTabRuns(tabPlacement, selectedRun); } } - + + // Suppress padding if we have only one tab run. + if (runCount == 1) + return; + // Pad the runs. int tabRunOverlay = getTabRunOverlay(tabPlacement); for (int i = runCount - 1; i >= 0; --i) @@ -606,16 +813,18 @@ public class BasicTabbedPaneUI extends TabbedPaneUI implements SwingConstants */ public Dimension minimumLayoutSize(Container parent) { - return calculateSize(false); + return calculateSize(true); } - // If there is more free space in an adjacent run AND the tab in the run can fit in the - // adjacent run, move it. This method is not perfect, it is merely an approximation. + // If there is more free space in an adjacent run AND the tab + // in the run can fit in the adjacent run, move it. This method + // is not perfect, it is merely an approximation. // If you play around with Sun's JTabbedPane, you'll see that // it does do some pretty strange things with regards to not moving tabs // that should be moved. // start = the x position where the tabs will begin - // max = the maximum position of where the tabs can go to (tabAreaInsets.left + the width of the tab area) + // max = the maximum position of where the tabs can go to + // (tabAreaInsets.left + the width of the tab area) /** * This method tries to "even out" the number of tabs in each run based on @@ -633,18 +842,20 @@ public class BasicTabbedPaneUI extends TabbedPaneUI implements SwingConstants if (tabPlacement == SwingUtilities.TOP || tabPlacement == SwingUtilities.BOTTOM) { - // We should only do this for runCount - 1, cause we can only shift that many times between - // runs. + // We should only do this for runCount - 1, cause we can + // only shift that many times between runs. for (int i = 1; i < runCount; i++) { Rectangle currRun = rects[lastTabInRun(tabCount, i)]; - Rectangle nextRun = rects[lastTabInRun(tabCount, getNextTabRun(i))]; + Rectangle nextRun = rects[lastTabInRun(tabCount, + getNextTabRun(i))]; int spaceInCurr = currRun.x + currRun.width; int spaceInNext = nextRun.x + nextRun.width; int diffNow = spaceInCurr - spaceInNext; int diffLater = (spaceInCurr - currRun.width) - (spaceInNext + currRun.width); + while (Math.abs(diffLater) < Math.abs(diffNow) && spaceInNext + currRun.width < max) { @@ -656,11 +867,12 @@ public class BasicTabbedPaneUI extends TabbedPaneUI implements SwingConstants diffLater = (spaceInCurr - currRun.width) - (spaceInNext + currRun.width); } - - // Fix the bounds. - int first = lastTabInRun(tabCount, i) + 1; - int last = lastTabInRun(tabCount, getNextTabRun(i)); - int currX = tabAreaInsets.left; + + // Fixes the bounds of all tabs in the current + // run. + int first = tabRuns[i]; + int last = lastTabInRun(tabCount, i); + int currX = start; for (int j = first; j <= last; j++) { rects[j].x = currX; @@ -673,7 +885,8 @@ public class BasicTabbedPaneUI extends TabbedPaneUI implements SwingConstants for (int i = 1; i < runCount; i++) { Rectangle currRun = rects[lastTabInRun(tabCount, i)]; - Rectangle nextRun = rects[lastTabInRun(tabCount, getNextTabRun(i))]; + Rectangle nextRun = rects[lastTabInRun(tabCount, + getNextTabRun(i))]; int spaceInCurr = currRun.y + currRun.height; int spaceInNext = nextRun.y + nextRun.height; @@ -692,9 +905,10 @@ public class BasicTabbedPaneUI extends TabbedPaneUI implements SwingConstants - (spaceInNext + currRun.height); } - int first = lastTabInRun(tabCount, i) + 1; - int last = lastTabInRun(tabCount, getNextTabRun(i)); - int currY = tabAreaInsets.top; + // Fixes the bounds of tabs in the current run. + int first = tabRuns[i]; + int last = lastTabInRun(tabCount, i); + int currY = start; for (int j = first; j <= last; j++) { rects[j].y = currY; @@ -720,7 +934,8 @@ public class BasicTabbedPaneUI extends TabbedPaneUI implements SwingConstants rects[selectedIndex].height += insets.top + insets.bottom; } - // If the tabs on the run don't fill the width of the window, make it fit now. + // If the tabs on the run don't fill the width of the window, make it + // fit now. // start = starting index of the run // end = last index of the run // max = tabAreaInsets.left + width (or equivalent) @@ -744,7 +959,7 @@ public class BasicTabbedPaneUI extends TabbedPaneUI implements SwingConstants int runWidth = rects[end].x + rects[end].width; int spaceRemaining = max - runWidth; int numTabs = end - start + 1; - + // now divvy up the space. int spaceAllocated = spaceRemaining / numTabs; int currX = rects[start].x; @@ -752,11 +967,13 @@ public class BasicTabbedPaneUI extends TabbedPaneUI implements SwingConstants { rects[i].x = currX; rects[i].width += spaceAllocated; + currX += rects[i].width; // This is used because since the spaceAllocated // variable is an int, it rounds down. Sometimes, // we don't fill an entire row, so we make it do // so now. + if (i == end && rects[i].x + rects[i].width != max) rects[i].width = max - rects[i].x; } @@ -821,7 +1038,7 @@ public class BasicTabbedPaneUI extends TabbedPaneUI implements SwingConstants // The reason why we can't use runCount: // This method is only called to calculate the size request - // for the tabbedPane. However, this size request is dependent on + // for the tabbedPane. However, this size request is dependent on // our desired width. We need to find out what the height would // be IF we got our desired width. for (int i = 0; i < tabPane.getTabCount(); i++) @@ -884,7 +1101,8 @@ public class BasicTabbedPaneUI extends TabbedPaneUI implements SwingConstants runs++; int maxTabWidth = calculateMaxTabWidth(tabPlacement); - int tabAreaWidth = calculateTabAreaWidth(tabPlacement, runs, maxTabWidth); + int tabAreaWidth = calculateTabAreaWidth(tabPlacement, runs, + maxTabWidth); return tabAreaWidth; } @@ -898,11 +1116,11 @@ public class BasicTabbedPaneUI extends TabbedPaneUI implements SwingConstants */ protected void rotateTabRuns(int tabPlacement, int selectedRun) { - if (runCount == 1 || selectedRun == 1 || selectedRun == -1) + if (runCount == 1 || selectedRun == 0 || selectedRun == -1) return; int[] newTabRuns = new int[tabRuns.length]; int currentRun = selectedRun; - int i = 1; + int i = 0; do { newTabRuns[i] = tabRuns[currentRun]; @@ -910,8 +1128,6 @@ public class BasicTabbedPaneUI extends TabbedPaneUI implements SwingConstants i++; } while (i < runCount); - if (runCount > 1) - newTabRuns[0] = tabRuns[currentRun]; tabRuns = newTabRuns; BasicTabbedPaneUI.this.selectedRun = 1; @@ -944,7 +1160,7 @@ public class BasicTabbedPaneUI extends TabbedPaneUI implements SwingConstants */ public Dimension preferredLayoutSize(Container parent) { - return super.calculateSize(true); + return super.calculateSize(false); } /** @@ -1018,29 +1234,27 @@ public class BasicTabbedPaneUI extends TabbedPaneUI implements SwingConstants SwingUtilities.calculateInnerArea(tabPane, calcRect); Insets tabAreaInsets = getTabAreaInsets(tabPlacement); Insets insets = tabPane.getInsets(); - int runs = 1; - int start = 0; - int top = 0; if (tabPlacement == SwingConstants.TOP || tabPlacement == SwingConstants.BOTTOM) { int maxHeight = calculateMaxTabHeight(tabPlacement); calcRect.width -= tabAreaInsets.left + tabAreaInsets.right; - start = tabAreaInsets.left + insets.left; int width = 0; - int runWidth = start; - top = insets.top + tabAreaInsets.top; + int runWidth = tabAreaInsets.left + insets.left; + int top = insets.top + tabAreaInsets.top; for (int i = 0; i < tabCount; i++) { width = calculateTabWidth(tabPlacement, i, fm); - - rects[i] = new Rectangle(runWidth, top, width, maxHeight); + + // The proper instances should exists because + // assureRectsCreated() was being run already. + rects[i].setBounds(runWidth, top, width, maxHeight); + runWidth += width; } tabAreaRect.width = tabPane.getWidth() - insets.left - insets.right; - tabAreaRect.height = runs * maxTabHeight - - (runs - 1) * tabRunOverlay - + tabAreaInsets.top + tabAreaInsets.bottom; + tabAreaRect.height = maxTabHeight + tabAreaInsets.top + + tabAreaInsets.bottom; contentRect.width = tabAreaRect.width; contentRect.height = tabPane.getHeight() - insets.top - insets.bottom - tabAreaRect.height; @@ -1063,23 +1277,25 @@ public class BasicTabbedPaneUI extends TabbedPaneUI implements SwingConstants calcRect.height -= tabAreaInsets.top + tabAreaInsets.bottom; int height = 0; - start = tabAreaInsets.top + insets.top; - int runHeight = start; + int runHeight = tabAreaInsets.top + insets.top;; int fontHeight = fm.getHeight(); - top = insets.left + tabAreaInsets.left; + int left = insets.left + tabAreaInsets.left; for (int i = 0; i < tabCount; i++) { height = calculateTabHeight(tabPlacement, i, fontHeight); - rects[i] = new Rectangle(top, runHeight, maxWidth, height); + + // The proper instances should exists because + // assureRectsCreated() was being run already. + rects[i].setBounds(left, runHeight, maxWidth, height); runHeight += height; } - tabAreaRect.width = runs * maxTabWidth - (runs - 1) * tabRunOverlay - + tabAreaInsets.left + tabAreaInsets.right; + tabAreaRect.width = maxTabWidth + tabAreaInsets.left + + tabAreaInsets.right; tabAreaRect.height = tabPane.getHeight() - insets.top - - insets.bottom; + - insets.bottom; tabAreaRect.y = insets.top; contentRect.width = tabPane.getWidth() - insets.left - insets.right - - tabAreaRect.width; + - tabAreaRect.width; contentRect.height = tabAreaRect.height; contentRect.y = insets.top; if (tabPlacement == SwingConstants.LEFT) @@ -1093,11 +1309,9 @@ public class BasicTabbedPaneUI extends TabbedPaneUI implements SwingConstants tabAreaRect.x = contentRect.x + contentRect.width; } } - runCount = runs; - if (runCount > tabRuns.length) - expandTabRunsArray(); - - padSelectedTab(tabPlacement, tabPane.getSelectedIndex()); + + // Unlike the behavior in the WRAP_TAB_LAYOUT the selected + // tab is not padded specially. } /** @@ -1115,8 +1329,7 @@ public class BasicTabbedPaneUI extends TabbedPaneUI implements SwingConstants if (tabCount == 0) return; int tabPlacement = tabPane.getTabPlacement(); - incrButton.setVisible(false); - decrButton.setVisible(false); + if (tabPlacement == SwingConstants.TOP || tabPlacement == SwingConstants.BOTTOM) { @@ -1126,18 +1339,49 @@ public class BasicTabbedPaneUI extends TabbedPaneUI implements SwingConstants Dimension incrDims = incrButton.getPreferredSize(); Dimension decrDims = decrButton.getPreferredSize(); - decrButton.setBounds(tabAreaRect.x + tabAreaRect.width - - incrDims.width - decrDims.width, - tabAreaRect.y, decrDims.width, - tabAreaRect.height); - incrButton.setBounds(tabAreaRect.x + tabAreaRect.width - - incrDims.width, tabAreaRect.y, - decrDims.width, tabAreaRect.height); - + if (tabPlacement == SwingConstants.BOTTOM) + { + // Align scroll buttons with the bottom border of the tabbed + // pane's content area. + decrButton.setBounds(tabAreaRect.x + tabAreaRect.width + - incrDims.width - decrDims.width, + tabAreaRect.y, decrDims.width, + decrDims.height); + incrButton.setBounds(tabAreaRect.x + tabAreaRect.width + - incrDims.width, tabAreaRect.y, + incrDims.width, incrDims.height); + } + else + { + // Align scroll buttons with the top border of the tabbed + // pane's content area. + decrButton.setBounds(tabAreaRect.x + tabAreaRect.width + - incrDims.width - decrDims.width, + tabAreaRect.y + tabAreaRect.height + - decrDims.height, decrDims.width, + decrDims.height); + incrButton.setBounds(tabAreaRect.x + tabAreaRect.width + - incrDims.width, + tabAreaRect.y + tabAreaRect.height + - incrDims.height, + incrDims.width, incrDims.height); + } + tabAreaRect.width -= decrDims.width + incrDims.width; + + updateButtons(); + incrButton.setVisible(true); decrButton.setVisible(true); } + else + { + incrButton.setVisible(false); + decrButton.setVisible(false); + + currentScrollOffset = 0; + currentScrollLocation = 0; + } } if (tabPlacement == SwingConstants.LEFT @@ -1149,34 +1393,54 @@ public class BasicTabbedPaneUI extends TabbedPaneUI implements SwingConstants Dimension incrDims = incrButton.getPreferredSize(); Dimension decrDims = decrButton.getPreferredSize(); - decrButton.setBounds(tabAreaRect.x, - tabAreaRect.y + tabAreaRect.height - - incrDims.height - decrDims.height, - tabAreaRect.width, decrDims.height); - incrButton.setBounds(tabAreaRect.x, - tabAreaRect.y + tabAreaRect.height - - incrDims.height, tabAreaRect.width, - incrDims.height); + if (tabPlacement == SwingConstants.RIGHT) + { + // Align scroll buttons with the right border of the tabbed + // pane's content area. + decrButton.setBounds(tabAreaRect.x, + tabAreaRect.y + tabAreaRect.height + - incrDims.height - decrDims.height, + decrDims.width, decrDims.height); + incrButton.setBounds(tabAreaRect.x, + tabAreaRect.y + tabAreaRect.height + - incrDims.height, incrDims.width, + incrDims.height); + } + else + { + // Align scroll buttons with the left border of the tabbed + // pane's content area. + decrButton.setBounds(tabAreaRect.x + tabAreaRect.width + - decrDims.width, + tabAreaRect.y + tabAreaRect.height + - incrDims.height - decrDims.height, + decrDims.width, decrDims.height); + incrButton.setBounds(tabAreaRect.x + tabAreaRect.width + - incrDims.width, + tabAreaRect.y + tabAreaRect.height + - incrDims.height, incrDims.width, + incrDims.height); + } tabAreaRect.height -= decrDims.height + incrDims.height; + incrButton.setVisible(true); decrButton.setVisible(true); } + else + { + incrButton.setVisible(false); + decrButton.setVisible(false); + + currentScrollOffset = 0; + currentScrollLocation = 0; + } } viewport.setBounds(tabAreaRect.x, tabAreaRect.y, tabAreaRect.width, tabAreaRect.height); - int tabC = tabPane.getTabCount() - 1; - if (tabCount > 0) - { - int w = Math.max(rects[tabC].width + rects[tabC].x, tabAreaRect.width); - int h = Math.max(rects[tabC].height, tabAreaRect.height); - p = findPointForIndex(currentScrollLocation); - - // we want to cover that entire space so that borders that run under - // the tab area don't show up when we move the viewport around. - panel.setSize(w + p.x, h + p.y); - } - viewport.setViewPosition(p); + + updateViewPosition(); + viewport.repaint(); } } @@ -1200,7 +1464,9 @@ public class BasicTabbedPaneUI extends TabbedPaneUI implements SwingConstants { selectedRun = getRunForTab(tabPane.getTabCount(), tabPane.getSelectedIndex()); - tabPane.revalidate(); + + if (tabPane.getTabLayoutPolicy() == JTabbedPane.WRAP_TAB_LAYOUT) + tabPane.revalidate(); tabPane.repaint(); } } @@ -1226,7 +1492,17 @@ public class BasicTabbedPaneUI extends TabbedPaneUI implements SwingConstants */ public void paint(Graphics g, JComponent c) { - paintTabArea(g, tabPane.getTabPlacement(), tabPane.getSelectedIndex()); + int placement = tabPane.getTabPlacement(); + g.setColor(highlight); + if (placement == SwingUtilities.TOP + || placement == SwingUtilities.BOTTOM) + g.fillRect(currentScrollOffset, 0, + tabAreaRect.width, tabAreaRect.height); + else + g.fillRect(0, currentScrollOffset, + tabAreaRect.width, tabAreaRect.height); + + paintTabArea(g, placement, tabPane.getSelectedIndex()); } } @@ -1287,6 +1563,8 @@ public class BasicTabbedPaneUI extends TabbedPaneUI implements SwingConstants /** The starting visible tab in the run in SCROLL_TAB_MODE. * This is package-private to avoid an accessor method. */ transient int currentScrollLocation; + + transient int currentScrollOffset; /** A reusable rectangle. */ protected Rectangle calcRect; @@ -1342,16 +1620,11 @@ public class BasicTabbedPaneUI extends TabbedPaneUI implements SwingConstants /** The gap between text and label */ protected int textIconGap; - // Keeps track of tab runs. - // The organization of this array is as follows (lots of experimentation to - // figure this out) - // index 0 = furthest away from the component area (aka outer run) - // index 1 = closest to component area (aka selected run) - // index > 1 = listed in order leading from selected run to outer run. - // each int in the array is the tab index + 1 (counting starts at 1) - // for the last tab in the run. (same as the rects array) - - /** This array keeps track of which tabs are in which run. See above. */ + /** This array keeps track of which tabs are in which run. + * <p>The value at index i denotes the index of the first tab in run i.</p> + * <p>If the value for any index (i > 0) is 0 then (i - 1) is the last + * run.</p> + */ protected int[] tabRuns; /** @@ -1430,7 +1703,13 @@ public class BasicTabbedPaneUI extends TabbedPaneUI implements SwingConstants * The currently visible component. */ private Component visibleComponent; - + + private Color selectedColor; + + private Rectangle tempTextRect = new Rectangle(); + + private Rectangle tempIconRect = new Rectangle(); + /** * Creates a new BasicTabbedPaneUI object. */ @@ -1519,8 +1798,115 @@ public class BasicTabbedPaneUI extends TabbedPaneUI implements SwingConstants Point p = new Point(w, h); return p; } + + /** TabbedPanes in scrolling mode should use this method to + * scroll properly to the tab given by the index argument. + * + * @param index The tab to scroll to. + * @param placement The tab's placement. + */ + final void scrollTab(int index, int placement) + { + int diff; + if (index >= 0 && tabPane.isEnabledAt(index)) + { + // If the user clicked on the last tab and that one was + // only partially visible shift the scroll offset to make + // it completely visible. + switch (placement) + { + case JTabbedPane.TOP: + case JTabbedPane.BOTTOM: + if ((diff = rects[index].x + + rects[index].width + - decrButton.getX() - currentScrollOffset) > 0) + currentScrollOffset += diff; + else if ((diff = rects[index].x - currentScrollOffset) < 0) + { + if (index == 0) + currentScrollOffset = 0; + else + currentScrollOffset += diff; + } + + currentScrollLocation = tabForCoordinate(tabPane, + currentScrollOffset, + rects[index].y); + break; + default: + if ((diff = rects[index].y + rects[index].height + - decrButton.getY() - currentScrollOffset) > 0) + currentScrollOffset += diff; + else if ((diff = rects[index].y - currentScrollOffset) < 0) + { + if (index == 0) + currentScrollOffset = 0; + else + currentScrollOffset += diff; + } + + currentScrollLocation = tabForCoordinate(tabPane, + rects[index].x, + currentScrollOffset); + } + + updateViewPosition(); + updateButtons(); + } + } + + /** Sets the enabled state of the increase and decrease button + * according to the current scrolling offset and tab pane width + * (or height in TOP/BOTTOM placement). + */ + final void updateButtons() + { + int tc = tabPane.getTabCount(); + + // The increase button should be enabled as long as the + // right/bottom border of the last tab is under the left/top + // border of the decrease button. + switch (tabPane.getTabPlacement()) + { + case JTabbedPane.BOTTOM: + case JTabbedPane.TOP: + incrButton.setEnabled(currentScrollLocation + 1 < tc + && rects[tc-1].x + rects[tc-1].width + - currentScrollOffset > decrButton.getX()); + break; + default: + incrButton.setEnabled(currentScrollLocation + 1 < tc + && rects[tc-1].y + rects[tc-1].height + - currentScrollOffset > decrButton.getY()); + } + + // The decrease button is enabled when the tab pane is scrolled in any way. + decrButton.setEnabled(currentScrollOffset > 0); + + } /** + * Updates the position of the scrolling viewport's view + * according to the current scroll offset. + */ + final void updateViewPosition() + { + Point p = viewport.getViewPosition(); + + switch (tabPane.getTabPlacement()) + { + case JTabbedPane.LEFT: + case JTabbedPane.RIGHT: + p.y = currentScrollOffset; + break; + default: + p.x = currentScrollOffset; + } + + viewport.setViewPosition(p); + } + + /** * This method creates a new BasicTabbedPaneUI. * * @param c The JComponent to create a UI for. @@ -1585,22 +1971,30 @@ public class BasicTabbedPaneUI extends TabbedPaneUI implements SwingConstants return new TabbedPaneLayout(); else { + runCount = 1; + tabRuns[0] = 0; + incrButton = createIncreaseButton(); + incrButton.addMouseListener(mouseListener); + decrButton = createDecreaseButton(); - viewport = new ScrollingViewport(); - viewport.setLayout(null); + decrButton.addMouseListener(mouseListener); + decrButton.setEnabled(false); + panel = new ScrollingPanel(); + panel.setSize(Integer.MAX_VALUE, Integer.MAX_VALUE); + panel.addMouseListener(mouseListener); + panel.addFocusListener(focusListener); + + viewport = new ScrollingViewport(); + viewport.setBackground(Color.LIGHT_GRAY); viewport.setView(panel); + viewport.setLayout(null); + tabPane.add(incrButton); tabPane.add(decrButton); tabPane.add(viewport); - currentScrollLocation = 0; - decrButton.setEnabled(false); - panel.addMouseListener(mouseListener); - incrButton.addMouseListener(mouseListener); - decrButton.addMouseListener(mouseListener); - viewport.setBackground(Color.LIGHT_GRAY); - + return new TabbedPaneScrollLayout(); } } @@ -1618,7 +2012,14 @@ public class BasicTabbedPaneUI extends TabbedPaneUI implements SwingConstants */ protected void uninstallComponents() { - // Nothing to be done. + if (incrButton != null) + tabPane.remove(incrButton); + + if (decrButton != null) + tabPane.remove(decrButton); + + if (viewport != null) + tabPane.remove(viewport); } /** @@ -1631,8 +2032,8 @@ public class BasicTabbedPaneUI extends TabbedPaneUI implements SwingConstants "TabbedPane.font"); tabPane.setOpaque(false); - highlight = UIManager.getColor("TabbedPane.highlight"); - lightHighlight = UIManager.getColor("TabbedPane.lightHighlight"); + lightHighlight = UIManager.getColor("TabbedPane.highlight"); + highlight = UIManager.getColor("TabbedPane.light"); shadow = UIManager.getColor("TabbedPane.shadow"); darkShadow = UIManager.getColor("TabbedPane.darkShadow"); @@ -1643,10 +2044,18 @@ public class BasicTabbedPaneUI extends TabbedPaneUI implements SwingConstants tabRunOverlay = UIManager.getInt("TabbedPane.tabRunOverlay"); tabInsets = UIManager.getInsets("TabbedPane.tabInsets"); - selectedTabPadInsets = UIManager.getInsets("TabbedPane.tabbedPaneTabPadInsets"); + selectedTabPadInsets + = UIManager.getInsets("TabbedPane.selectedTabPadInsets"); tabAreaInsets = UIManager.getInsets("TabbedPane.tabAreaInsets"); - contentBorderInsets = UIManager.getInsets("TabbedPane.tabbedPaneContentBorderInsets"); + contentBorderInsets + = UIManager.getInsets("TabbedPane.contentBorderInsets"); tabsOpaque = UIManager.getBoolean("TabbedPane.tabsOpaque"); + + // Although 'TabbedPane.contentAreaColor' is not defined in the defaults + // of BasicLookAndFeel it is used by this class. + selectedColor = UIManager.getColor("TabbedPane.contentAreaColor"); + if (selectedColor == null) + selectedColor = UIManager.getColor("control"); calcRect = new Rectangle(); tabRuns = new int[10]; @@ -1663,6 +2072,9 @@ public class BasicTabbedPaneUI extends TabbedPaneUI implements SwingConstants tabAreaRect = null; contentRect = null; tabRuns = null; + + tempIconRect = null; + tempTextRect = null; contentBorderInsets = null; tabAreaInsets = null; @@ -1674,11 +2086,8 @@ public class BasicTabbedPaneUI extends TabbedPaneUI implements SwingConstants shadow = null; lightHighlight = null; highlight = null; - - // Install UI colors and fonts. - LookAndFeel.installColorsAndFont(tabPane, "TabbedPane.background", - "TabbedPane.foreground", - "TabbedPane.font"); + + selectedColor = null; } /** @@ -1706,6 +2115,18 @@ public class BasicTabbedPaneUI extends TabbedPaneUI implements SwingConstants tabPane.removePropertyChangeListener(propertyChangeListener); tabPane.removeChangeListener(tabChangeListener); tabPane.removeMouseListener(mouseListener); + + if (incrButton != null) + incrButton.removeMouseListener(mouseListener); + + if (decrButton != null) + decrButton.removeMouseListener(mouseListener); + + if (panel != null) + { + panel.removeMouseListener(mouseListener); + panel.removeFocusListener(focusListener); + } focusListener = null; propertyChangeListener = null; @@ -1757,18 +2178,31 @@ public class BasicTabbedPaneUI extends TabbedPaneUI implements SwingConstants * This method installs keyboard actions for the JTabbedPane. */ protected void installKeyboardActions() - throws NotImplementedException { - // FIXME: Implement. + InputMap keyMap = (InputMap) UIManager.get("TabbedPane.focusInputMap"); + SwingUtilities.replaceUIInputMap(tabPane, JComponent.WHEN_FOCUSED, keyMap); + + keyMap = (InputMap) UIManager.get("TabbedPane.ancestorInputMap"); + SwingUtilities + .replaceUIInputMap(tabPane, + JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT, + keyMap); + + ActionMap map = getActionMap(); + SwingUtilities.replaceUIActionMap(tabPane, map); } /** * This method uninstalls keyboard actions for the JTabbedPane. */ protected void uninstallKeyboardActions() - throws NotImplementedException { - // FIXME: Implement. + SwingUtilities.replaceUIActionMap(tabPane, null); + SwingUtilities.replaceUIInputMap(tabPane, JComponent.WHEN_FOCUSED, null); + SwingUtilities + .replaceUIInputMap(tabPane, + JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT, + null); } /** @@ -1808,9 +2242,25 @@ public class BasicTabbedPaneUI extends TabbedPaneUI implements SwingConstants if (tabPane.getTabCount() == 0) return; + + int index = tabPane.getSelectedIndex(); + if (index < 0) + index = 0; + + int tabPlacement = tabPane.getTabPlacement(); + + // Paint the tab area only in WRAP_TAB_LAYOUT Mode from this method + // because it is done through the ScrollingViewport.paint() method + // for the SCROLL_TAB_LAYOUT mode. if (tabPane.getTabLayoutPolicy() == JTabbedPane.WRAP_TAB_LAYOUT) - paintTabArea(g, tabPane.getTabPlacement(), tabPane.getSelectedIndex()); - paintContentBorder(g, tabPane.getTabPlacement(), tabPane.getSelectedIndex()); + { + g.setColor(highlight); + g.fillRect(tabAreaRect.x, tabAreaRect.y, + tabAreaRect.width, tabAreaRect.height); + paintTabArea(g, tabPlacement, index); + } + + paintContentBorder(g, tabPlacement, index); } /** @@ -1823,14 +2273,12 @@ public class BasicTabbedPaneUI extends TabbedPaneUI implements SwingConstants */ protected void paintTabArea(Graphics g, int tabPlacement, int selectedIndex) { - Rectangle ir = new Rectangle(); - Rectangle tr = new Rectangle(); - - boolean isScroll = tabPane.getTabLayoutPolicy() == JTabbedPane.SCROLL_TAB_LAYOUT; - // Please note: the ordering of the painting is important. // we WANT to paint the outermost run first and then work our way in. + + // The following drawing code works for both tab layouts. int tabCount = tabPane.getTabCount(); + for (int i = runCount - 1; i >= 0; --i) { int start = tabRuns[i]; @@ -1844,14 +2292,16 @@ public class BasicTabbedPaneUI extends TabbedPaneUI implements SwingConstants { if (j != selectedIndex) { - paintTab(g, tabPlacement, rects, j, ir, tr); + paintTab(g, tabPlacement, rects, j, + tempIconRect, tempTextRect); } } } - + // Paint selected tab in front of every other tab. if (selectedIndex >= 0) - paintTab(g, tabPlacement, rects, selectedIndex, ir, tr); + paintTab(g, tabPlacement, rects, selectedIndex, + tempIconRect, tempTextRect); } /** @@ -1891,8 +2341,10 @@ public class BasicTabbedPaneUI extends TabbedPaneUI implements SwingConstants // Paint the text. paintText(g, tabPlacement, tabPane.getFont(), fm, tabIndex, title, textRect, isSelected); + // Paint icon if necessary. paintIcon(g, tabPlacement, tabIndex, icon, iconRect, isSelected); + // Paint focus indicator. paintFocusIndicator(g, tabPlacement, rects, tabIndex, iconRect, textRect, isSelected); @@ -2032,8 +2484,17 @@ public class BasicTabbedPaneUI extends TabbedPaneUI implements SwingConstants protected int getTabLabelShiftX(int tabPlacement, int tabIndex, boolean isSelected) { - // No reason to shift. - return 0; + switch (tabPlacement) + { + default: + case SwingUtilities.TOP: + case SwingUtilities.BOTTOM: + return 1; + case SwingUtilities.LEFT: + return (isSelected) ? -1 : 1; + case SwingUtilities.RIGHT: + return (isSelected) ? 1 : -1; + } } /** @@ -2049,8 +2510,17 @@ public class BasicTabbedPaneUI extends TabbedPaneUI implements SwingConstants protected int getTabLabelShiftY(int tabPlacement, int tabIndex, boolean isSelected) { - // No reason to shift. - return 0; + switch (tabPlacement) + { + default: + case SwingUtilities.TOP: + return (isSelected) ? -1 : 1; + case SwingUtilities.BOTTOM: + return (isSelected) ? 1 : -1; + case SwingUtilities.LEFT: + case SwingUtilities.RIGHT: + return 0; + } } /** @@ -2080,32 +2550,33 @@ public class BasicTabbedPaneUI extends TabbedPaneUI implements SwingConstants g.setColor(focus); switch (tabPlacement) - { - case LEFT: - x = rect.x + 3; - y = rect.y + 3; - w = rect.width - 5; - h = rect.height - 6; - break; - case RIGHT: - x = rect.x + 2; - y = rect.y + 3; - w = rect.width - 6; - h = rect.height - 5; - break; - case BOTTOM: - x = rect.x + 3; - y = rect.y + 2; - w = rect.width - 6; - h = rect.height - 5; - break; - case TOP: - default: - x = rect.x + 3; - y = rect.y + 3; - w = rect.width - 6; - h = rect.height - 5; - } + { + case LEFT: + x = rect.x + 3; + y = rect.y + 3; + w = rect.width - 5; + h = rect.height - 6; + break; + case RIGHT: + x = rect.x + 2; + y = rect.y + 3; + w = rect.width - 6; + h = rect.height - 5; + break; + case BOTTOM: + x = rect.x + 3; + y = rect.y + 2; + w = rect.width - 6; + h = rect.height - 5; + break; + case TOP: + default: + x = rect.x + 3; + y = rect.y + 3; + w = rect.width - 6; + h = rect.height - 5; + } + BasicGraphicsUtils.drawDashedRect(g, x, y, w, h); } } @@ -2127,34 +2598,109 @@ public class BasicTabbedPaneUI extends TabbedPaneUI implements SwingConstants { Color saved = g.getColor(); - if (! isSelected || tabPlacement != SwingConstants.TOP) - { + switch (tabPlacement) + { + case SwingConstants.TOP: g.setColor(shadow); - g.drawLine(x + 1, y + h - 1, x + w - 1, y + h - 1); - g.setColor(darkShadow); - g.drawLine(x, y + h, x + w, y + h); - } + // Inner right line. + g.drawLine(x + w - 2, y + 2, x + w - 2, y + h); - if (! isSelected || tabPlacement != SwingConstants.LEFT) - { g.setColor(darkShadow); - g.drawLine(x + w, y, x + w, y + h); + // Outer right line. + g.drawLine(x + w - 1, y + 2, x + w - 1, y + h); + + // Upper right corner. + g.drawLine(x + w - 2, y + 1, x + w - 1, y + 2); + + g.setColor(lightHighlight); + + // Left line. + g.drawLine(x, y + 3, x, y + h); + + // Upper line. + g.drawLine(x + 3, y, x + w - 3, y); + + // Upper left corner. + g.drawLine(x, y + 2, x + 2, y); + + break; + case SwingConstants.LEFT: + g.setColor(lightHighlight); + // Top line. + g.drawLine(x + 3, y, x + w - 1, y); + + // Top left border. + g.drawLine(x + 2, y, x, y + 2); + + // Left line. + g.drawLine(x, y + 3, x, y + h - 4); + + // Bottom left corner. + g.drawLine(x, y + h - 3, x + 1, y + h - 2); + + g.setColor(darkShadow); + // Outer bottom line. + g.drawLine(x + 2, y + h - 1, x + w - 1, y + h - 1); + g.setColor(shadow); - g.drawLine(x + w - 1, y + 1, x + w - 1, y + h - 1); - } + // Inner bottom line. + g.drawLine(x + 2, y + h - 2, x + w - 1, y + h - 2); + + break; + case SwingConstants.BOTTOM: + g.setColor(shadow); + // Inner right line. + g.drawLine(x + w - 2, y, x + w - 2, y + h - 2); - if (! isSelected || tabPlacement != SwingConstants.RIGHT) - { - g.setColor(lightHighlight); - g.drawLine(x, y, x, y + h); - } + // Inner bottom line. + g.drawLine(x + 2, y + h - 1, x + w - 3, y + h - 1); - if (! isSelected || tabPlacement != SwingConstants.BOTTOM) - { + g.setColor(darkShadow); + // Outer right line. + g.drawLine(x + w - 1, y, x + w - 1, y + h - 3); + + // Bottom right corner. + g.drawLine(x + w - 1, y + h - 2, x + w - 3, y + h); + + // Bottom line. + g.drawLine(x + 2, y + h, x + w - 4, y + h); + g.setColor(lightHighlight); - g.drawLine(x, y, x + w, y); - } - + // Left line. + g.drawLine(x, y, x, y + h - 3); + + // Bottom left corner. + g.drawLine(x, y + h - 2, x + 1, y + h - 1); + break; + case SwingConstants.RIGHT: + g.setColor(lightHighlight); + // Top line. + g.drawLine(x, y, x + w - 3, y); + + g.setColor(darkShadow); + // Top right corner. + g.drawLine(x + w - 2, y + 1, x + w - 1, y + 2); + + // Outer right line. + g.drawLine(x + w - 1, y + 3, x + w - 1, y + h - 3); + + // Bottom right corner. + g.drawLine(x + w - 2, y + h - 2, x + w - 3, y + h - 1); + + // Bottom line. + g.drawLine(x, y + h - 1, x + w - 4, y + h - 1); + + g.setColor(shadow); + + // Inner right line. + g.drawLine(x + w - 2, y + 2, x + w - 2, y + h - 3); + + // Inner bottom line. + g.drawLine(x, y + h - 2, x + w - 3, y + h - 2); + + break; + } + g.setColor(saved); } @@ -2175,17 +2721,32 @@ public class BasicTabbedPaneUI extends TabbedPaneUI implements SwingConstants boolean isSelected) { Color saved = g.getColor(); + if (isSelected) - g.setColor(Color.LIGHT_GRAY); + g.setColor(selectedColor); else { Color bg = tabPane.getBackgroundAt(tabIndex); if (bg == null) - bg = Color.GRAY; + bg = Color.LIGHT_GRAY; g.setColor(bg); } - g.fillRect(x, y, w, h); + switch (tabPlacement) + { + case SwingConstants.TOP: + g.fillRect(x + 1, y + 1, w - 1, h - 1); + break; + case SwingConstants.BOTTOM: + g.fillRect(x, y, w - 1, h - 1); + break; + case SwingConstants.LEFT: + g.fillRect(x + 1, y + 1, w - 1, h - 2); + break; + case SwingConstants.RIGHT: + g.fillRect(x, y + 1, w - 1, h - 2); + break; + } g.setColor(saved); } @@ -2262,25 +2823,27 @@ public class BasicTabbedPaneUI extends TabbedPaneUI implements SwingConstants Color saved = g.getColor(); g.setColor(lightHighlight); - int startgap = rects[selectedIndex].x; - int endgap = rects[selectedIndex].x + rects[selectedIndex].width; - - int diff = 0; + int startgap = rects[selectedIndex].x - currentScrollOffset; + int endgap = rects[selectedIndex].x + rects[selectedIndex].width + - currentScrollOffset; - if (tabPlacement == SwingConstants.TOP) + // Paint the highlight line with a gap if the tabs are at the top + // and the selected tab is inside the visible area. + if (tabPlacement == SwingConstants.TOP && startgap >= 0) { - if (tabPane.getTabLayoutPolicy() == JTabbedPane.SCROLL_TAB_LAYOUT) - { - Point p = findPointForIndex(currentScrollLocation); - diff = p.x; - } - - g.drawLine(x, y, startgap - diff, y); - g.drawLine(endgap - diff, y, x + w, y); + g.drawLine(x, y, startgap, y); + g.drawLine(endgap, y, x + w - 1, y); + + g.setColor(selectedColor); + g.drawLine(startgap, y, endgap - 1, y); } else g.drawLine(x, y, x + w, y); - + + g.setColor(selectedColor); + g.drawLine(x, y + 1, x + w - 1, y + 1); + g.drawLine(x, y + 2, x + w - 1, y + 2); + g.setColor(saved); } @@ -2302,24 +2865,25 @@ public class BasicTabbedPaneUI extends TabbedPaneUI implements SwingConstants Color saved = g.getColor(); g.setColor(lightHighlight); - int startgap = rects[selectedIndex].y; - int endgap = rects[selectedIndex].y + rects[selectedIndex].height; + int startgap = rects[selectedIndex].y - currentScrollOffset; + int endgap = rects[selectedIndex].y + rects[selectedIndex].height + - currentScrollOffset; int diff = 0; - if (tabPlacement == SwingConstants.LEFT) + if (tabPlacement == SwingConstants.LEFT && startgap >= 0) { - if (tabPane.getTabLayoutPolicy() == JTabbedPane.SCROLL_TAB_LAYOUT) - { - Point p = findPointForIndex(currentScrollLocation); - diff = p.y; - } - - g.drawLine(x, y, x, startgap - diff); - g.drawLine(x, endgap - diff, x, y + h); + g.drawLine(x, y, x, startgap); + g.drawLine(x, endgap, x, y + h - 1); + + g.setColor(selectedColor); + g.drawLine(x, startgap, x, endgap - 1); } else - g.drawLine(x, y, x, y + h); + g.drawLine(x, y, x, y + h - 1); + + g.setColor(selectedColor); + g.drawLine(x + 1, y + 1, x + 1, y + h - 4); g.setColor(saved); } @@ -2341,34 +2905,34 @@ public class BasicTabbedPaneUI extends TabbedPaneUI implements SwingConstants { Color saved = g.getColor(); - int startgap = rects[selectedIndex].x; - int endgap = rects[selectedIndex].x + rects[selectedIndex].width; - - int diff = 0; + int startgap = rects[selectedIndex].x - currentScrollOffset; + int endgap = rects[selectedIndex].x + rects[selectedIndex].width + - currentScrollOffset; - if (tabPlacement == SwingConstants.BOTTOM) + if (tabPlacement == SwingConstants.BOTTOM && startgap >= 0) { - if (tabPane.getTabLayoutPolicy() == JTabbedPane.SCROLL_TAB_LAYOUT) - { - Point p = findPointForIndex(currentScrollLocation); - diff = p.x; - } - g.setColor(shadow); - g.drawLine(x + 1, y + h - 1, startgap - diff, y + h - 1); - g.drawLine(endgap - diff, y + h - 1, x + w - 1, y + h - 1); + g.drawLine(x + 1, y + h - 2, startgap, y + h - 2); + g.drawLine(endgap, y + h - 2, x + w - 2, y + h - 2); g.setColor(darkShadow); - g.drawLine(x, y + h, startgap - diff, y + h); - g.drawLine(endgap - diff, y + h, x + w, y + h); + g.drawLine(x, y + h - 1, startgap , y + h - 1); + g.drawLine(endgap, y + h - 1, x + w - 1, y + h - 1); + + g.setColor(selectedColor); + g.drawLine(startgap, y + h - 1, endgap - 1, y + h - 1); + g.drawLine(startgap, y + h - 2, endgap - 1, y + h - 2); } else { g.setColor(shadow); - g.drawLine(x + 1, y + h - 1, x + w - 1, y + h - 1); + g.drawLine(x + 1, y + h - 2, x + w - 1, y + h - 2); g.setColor(darkShadow); - g.drawLine(x, y + h, x + w, y + h); + g.drawLine(x, y + h - 1, x + w - 1, y + h - 1); } + + g.setColor(selectedColor); + g.drawLine(x + 1, y + h - 3, x + w - 2, y + h - 3); g.setColor(saved); } @@ -2389,34 +2953,36 @@ public class BasicTabbedPaneUI extends TabbedPaneUI implements SwingConstants int w, int h) { Color saved = g.getColor(); - int startgap = rects[selectedIndex].y; - int endgap = rects[selectedIndex].y + rects[selectedIndex].height; + int startgap = rects[selectedIndex].y - currentScrollOffset; + int endgap = rects[selectedIndex].y + rects[selectedIndex].height + - currentScrollOffset; int diff = 0; - if (tabPlacement == SwingConstants.RIGHT) + if (tabPlacement == SwingConstants.RIGHT && startgap >= 0) { - if (tabPane.getTabLayoutPolicy() == JTabbedPane.SCROLL_TAB_LAYOUT) - { - Point p = findPointForIndex(currentScrollLocation); - diff = p.y; - } - g.setColor(shadow); - g.drawLine(x + w - 1, y + 1, x + w - 1, startgap - diff); - g.drawLine(x + w - 1, endgap - diff, x + w - 1, y + h - 1); + g.drawLine(x + w - 2, y + 1, x + w - 2, startgap); + g.drawLine(x + w - 2, endgap, x + w - 2, y + h - 2); g.setColor(darkShadow); - g.drawLine(x + w, y, x + w, startgap - diff); - g.drawLine(x + w, endgap - diff, x + w, y + h); + g.drawLine(x + w - 1, y, x + w - 1, startgap); + g.drawLine(x + w - 1, endgap, x + w - 1, y + h - 2); + + g.setColor(selectedColor); + g.drawLine(x + w - 2, startgap, x + w - 2, endgap - 1); + g.drawLine(x + w - 1, startgap, x + w - 1, endgap - 1); } else { g.setColor(shadow); - g.drawLine(x + w - 1, y + 1, x + w - 1, y + h - 1); + g.drawLine(x + w - 2, y + 1, x + w - 2, y + h - 2); g.setColor(darkShadow); - g.drawLine(x + w, y, x + w, y + h); + g.drawLine(x + w - 1, y, x + w - 1, y + h - 2); } + + g.setColor(selectedColor); + g.drawLine(x + w - 3, y + 1, x + w - 3, y + h - 4); g.setColor(saved); } @@ -2460,11 +3026,15 @@ public class BasicTabbedPaneUI extends TabbedPaneUI implements SwingConstants */ public int tabForCoordinate(JTabbedPane pane, int x, int y) { + // Note: This code is tab layout mode agnostic. if (! tabPane.isValid()) tabPane.validate(); - + int tabCount = tabPane.getTabCount(); - int index = -1; + + // If the user clicked outside of any tab rect the + // selection should not change. + int index = tabPane.getSelectedIndex(); for (int i = 0; i < tabCount; ++i) { if (rects[i].contains(x, y)) @@ -2474,8 +3044,6 @@ public class BasicTabbedPaneUI extends TabbedPaneUI implements SwingConstants } } - // FIXME: Handle scrollable tab layout. - return index; } @@ -2571,7 +3139,7 @@ public class BasicTabbedPaneUI extends TabbedPaneUI implements SwingConstants protected int getRunForTab(int tabCount, int tabIndex) { if (runCount == 1 && tabIndex < tabCount && tabIndex >= 0) - return 1; + return 0; for (int i = 0; i < runCount; i++) { int first = lastTabInRun(tabCount, getPreviousTabRun(i)) + 1; @@ -2690,6 +3258,8 @@ public class BasicTabbedPaneUI extends TabbedPaneUI implements SwingConstants */ protected View getTextViewForTab(int tabIndex) { + // FIXME: When the label contains HTML this should return something + // non-null. return null; } @@ -2706,7 +3276,7 @@ public class BasicTabbedPaneUI extends TabbedPaneUI implements SwingConstants protected int calculateTabHeight(int tabPlacement, int tabIndex, int fontHeight) { - // FIXME: Handle HTML somehow. + // FIXME: Handle HTML by using the view (see getTextViewForTab). int height = fontHeight; Icon icon = getIconForTab(tabIndex); @@ -2923,8 +3493,9 @@ public class BasicTabbedPaneUI extends TabbedPaneUI implements SwingConstants { int offset = getTabRunOffset(tabPlacement, tabPane.getTabCount(), tabPane.getSelectedIndex(), - (tabPlacement == SwingConstants.RIGHT) - ? true : false); + (tabPlacement == SwingConstants.TOP) + ? direction == SwingConstants.NORTH + : direction == SwingConstants.SOUTH); selectAdjacentRunTab(tabPlacement, tabPane.getSelectedIndex(), offset); } @@ -2940,8 +3511,9 @@ public class BasicTabbedPaneUI extends TabbedPaneUI implements SwingConstants { int offset = getTabRunOffset(tabPlacement, tabPane.getTabCount(), tabPane.getSelectedIndex(), - (tabPlacement == SwingConstants.RIGHT) - ? true : false); + (tabPlacement == SwingConstants.LEFT) + ? direction == SwingConstants.WEST + : direction == SwingConstants.EAST); selectAdjacentRunTab(tabPlacement, tabPane.getSelectedIndex(), offset); } @@ -2955,8 +3527,13 @@ public class BasicTabbedPaneUI extends TabbedPaneUI implements SwingConstants */ protected void selectNextTabInRun(int current) { - tabPane.setSelectedIndex(getNextTabIndexInRun(tabPane.getTabCount(), - current)); + current = getNextTabIndexInRun(tabPane.getTabCount(), + current); + + if (tabPane.getTabLayoutPolicy() == JTabbedPane.SCROLL_TAB_LAYOUT) + scrollTab(current, tabPane.getTabPlacement()); + + tabPane.setSelectedIndex(current); } /** @@ -2966,8 +3543,13 @@ public class BasicTabbedPaneUI extends TabbedPaneUI implements SwingConstants */ protected void selectPreviousTabInRun(int current) { - tabPane.setSelectedIndex(getPreviousTabIndexInRun(tabPane.getTabCount(), - current)); + current = getPreviousTabIndexInRun(tabPane.getTabCount(), + current); + + if (tabPane.getTabLayoutPolicy() == JTabbedPane.SCROLL_TAB_LAYOUT) + scrollTab(current, tabPane.getTabPlacement()); + + tabPane.setSelectedIndex(current); } /** @@ -2977,7 +3559,12 @@ public class BasicTabbedPaneUI extends TabbedPaneUI implements SwingConstants */ protected void selectNextTab(int current) { - tabPane.setSelectedIndex(getNextTabIndex(current)); + current = getNextTabIndex(current); + + if (tabPane.getTabLayoutPolicy() == JTabbedPane.SCROLL_TAB_LAYOUT) + scrollTab(current, tabPane.getTabPlacement()); + + tabPane.setSelectedIndex(current); } /** @@ -2987,7 +3574,12 @@ public class BasicTabbedPaneUI extends TabbedPaneUI implements SwingConstants */ protected void selectPreviousTab(int current) { - tabPane.setSelectedIndex(getPreviousTabIndex(current)); + current = getPreviousTabIndex(current); + + if (tabPane.getTabLayoutPolicy() == JTabbedPane.SCROLL_TAB_LAYOUT) + scrollTab(current, tabPane.getTabPlacement()); + + tabPane.setSelectedIndex(current); } /** @@ -3021,7 +3613,11 @@ public class BasicTabbedPaneUI extends TabbedPaneUI implements SwingConstants int index = tabForCoordinate(tabPane, x, y); if (index != -1) - tabPane.setSelectedIndex(index); + { + if (tabPane.getTabLayoutPolicy() == JTabbedPane.SCROLL_TAB_LAYOUT) + scrollTab(index, tabPlacement); + tabPane.setSelectedIndex(index); + } } // This method is called when you press up/down to cycle through tab runs. @@ -3058,6 +3654,7 @@ public class BasicTabbedPaneUI extends TabbedPaneUI implements SwingConstants else offset = rects[lastTabInRun(tabCount, nextRun)].x - rects[lastTabInRun(tabCount, currRun)].x; + return offset; } @@ -3104,9 +3701,12 @@ public class BasicTabbedPaneUI extends TabbedPaneUI implements SwingConstants { int index = getNextTabIndex(base); int run = getRunForTab(tabCount, base); - if (index == lastTabInRun(tabCount, run) + 1) - index = lastTabInRun(tabCount, getPreviousTabRun(run)) + 1; - return getNextTabIndex(base); + if (base == lastTabInRun(tabCount, run)) + index = (run > 0) + ? lastTabInRun(tabCount, getPreviousTabRun(run)) + 1 + : 0; + + return index; } /** @@ -3124,7 +3724,8 @@ public class BasicTabbedPaneUI extends TabbedPaneUI implements SwingConstants int run = getRunForTab(tabCount, base); if (index == lastTabInRun(tabCount, getPreviousTabRun(run))) index = lastTabInRun(tabCount, run); - return getPreviousTabIndex(base); + + return index; } /** @@ -3182,6 +3783,7 @@ public class BasicTabbedPaneUI extends TabbedPaneUI implements SwingConstants // so I won't check it either. switch (targetPlacement) { + default: case SwingConstants.TOP: targetInsets.top = topInsets.top; targetInsets.left = topInsets.left; @@ -3208,6 +3810,44 @@ public class BasicTabbedPaneUI extends TabbedPaneUI implements SwingConstants break; } } + + ActionMap getActionMap() + { + ActionMap map = (ActionMap) UIManager.get("TabbedPane.actionMap"); + + if (map == null) // first time here + { + map = createActionMap(); + if (map != null) + UIManager.put("TabbedPane.actionMap", map); + } + return map; + } + + ActionMap createActionMap() + { + ActionMap map = new ActionMapUIResource(); + + map.put("navigatePageDown", new NavigatePageDownAction()); + map.put("navigatePageUp", new NavigatePageUpAction()); + map.put("navigateDown", + new NavigateAction("navigateDown", SwingConstants.SOUTH)); + + map.put("navigateUp", + new NavigateAction("navigateUp", SwingConstants.NORTH)); + + map.put("navigateLeft", + new NavigateAction("navigateLeft", SwingConstants.WEST)); + + map.put("navigateRight", + new NavigateAction("navigateRight", SwingConstants.EAST)); + + map.put("requestFocusForVisibleComponent", + new RequestFocusForVisibleComponentAction()); + map.put("requestFocus", new RequestFocusAction()); + + return map; + } /** * Sets the tab which should be highlighted when in rollover mode. And diff --git a/javax/swing/plaf/basic/BasicTableUI.java b/javax/swing/plaf/basic/BasicTableUI.java index 85c6b574d..15be4d57e 100644 --- a/javax/swing/plaf/basic/BasicTableUI.java +++ b/javax/swing/plaf/basic/BasicTableUI.java @@ -38,8 +38,6 @@ exception statement from your version. */ package javax.swing.plaf.basic; -import gnu.classpath.NotImplementedException; - import java.awt.Color; import java.awt.Component; import java.awt.ComponentOrientation; @@ -48,7 +46,6 @@ import java.awt.Graphics; import java.awt.Point; import java.awt.Rectangle; import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; import java.awt.event.FocusEvent; import java.awt.event.FocusListener; import java.awt.event.KeyEvent; @@ -58,6 +55,7 @@ import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import javax.swing.AbstractAction; +import javax.swing.Action; import javax.swing.ActionMap; import javax.swing.CellRendererPane; import javax.swing.DefaultCellEditor; @@ -65,16 +63,16 @@ import javax.swing.DefaultListSelectionModel; import javax.swing.InputMap; import javax.swing.JComponent; import javax.swing.JTable; -import javax.swing.KeyStroke; import javax.swing.ListSelectionModel; import javax.swing.LookAndFeel; +import javax.swing.SwingUtilities; +import javax.swing.TransferHandler; import javax.swing.UIManager; import javax.swing.border.Border; import javax.swing.event.ChangeEvent; import javax.swing.event.MouseInputListener; import javax.swing.plaf.ActionMapUIResource; import javax.swing.plaf.ComponentUI; -import javax.swing.plaf.InputMapUIResource; import javax.swing.plaf.TableUI; import javax.swing.table.TableCellEditor; import javax.swing.table.TableCellRenderer; @@ -164,14 +162,37 @@ public class BasicTableUI extends TableUI public class FocusHandler implements FocusListener { - public void focusGained(FocusEvent e) throws NotImplementedException + public void focusGained(FocusEvent e) { - // TODO: Implement this properly. + // The only thing that is affected by a focus change seems to be + // how the lead cell is painted. So we repaint this cell. + repaintLeadCell(); } - public void focusLost(FocusEvent e) throws NotImplementedException + public void focusLost(FocusEvent e) + { + // The only thing that is affected by a focus change seems to be + // how the lead cell is painted. So we repaint this cell. + repaintLeadCell(); + } + + /** + * Repaints the lead cell in response to a focus change, to refresh + * the display of the focus indicator. + */ + private void repaintLeadCell() { - // TODO: Implement this properly. + int rowCount = table.getRowCount(); + int columnCount = table.getColumnCount(); + int rowLead = table.getSelectionModel().getLeadSelectionIndex(); + int columnLead = table.getColumnModel().getSelectionModel(). + getLeadSelectionIndex(); + if (rowLead >= 0 && rowLead < rowCount && columnLead >= 0 + && columnLead < columnCount) + { + Rectangle dirtyRect = table.getCellRect(rowLead, columnLead, false); + table.repaint(dirtyRect); + } } } @@ -242,20 +263,19 @@ public class BasicTableUI extends TableUI } } - public void mouseEntered(MouseEvent e) - throws NotImplementedException + public void mouseEntered(MouseEvent e) { - // TODO: What should be done here, if anything? + // Nothing to do here. } - public void mouseExited(MouseEvent e) throws NotImplementedException + public void mouseExited(MouseEvent e) { - // TODO: What should be done here, if anything? + // Nothing to do here. } - public void mouseMoved(MouseEvent e) throws NotImplementedException + public void mouseMoved(MouseEvent e) { - // TODO: What should be done here, if anything? + // Nothing to do here. } public void mousePressed(MouseEvent e) @@ -288,6 +308,9 @@ public class BasicTableUI extends TableUI colLead != colModel.getLeadSelectionIndex()) if (table.isEditing()) table.editingStopped(new ChangeEvent(e)); + + // Must request focus explicitly. + table.requestFocusInWindow(); } } @@ -457,66 +480,100 @@ public class BasicTableUI extends TableUI table.setOpaque(true); } + /** + * Installs keyboard actions on the table. + */ protected void installKeyboardActions() { - InputMap ancestorMap = (InputMap) UIManager.get("Table.ancestorInputMap"); - InputMapUIResource parentInputMap = new InputMapUIResource(); - // FIXME: The JDK uses a LazyActionMap for parentActionMap - ActionMap parentActionMap = new ActionMapUIResource(); - action = new TableAction(); - Object keys[] = ancestorMap.allKeys(); - // Register key bindings in the UI InputMap-ActionMap pair - for (int i = 0; i < keys.length; i++) - { - KeyStroke stroke = (KeyStroke) keys[i]; - String actionString = (String) ancestorMap.get(stroke); + // Install the input map. + InputMap inputMap = + (InputMap) SharedUIDefaults.get("Table.ancestorInputMap"); + SwingUtilities.replaceUIInputMap(table, + JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT, + inputMap); - parentInputMap.put(KeyStroke.getKeyStroke(stroke.getKeyCode(), - stroke.getModifiers()), - actionString); - - parentActionMap.put(actionString, - new ActionListenerProxy(action, actionString)); + // FIXME: The JDK uses a LazyActionMap for parentActionMap + SwingUtilities.replaceUIActionMap(table, getActionMap()); - } - // Set the UI InputMap-ActionMap pair to be the parents of the - // JTable's InputMap-ActionMap pair - parentInputMap.setParent(table.getInputMap( - JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).getParent()); - parentActionMap.setParent(table.getActionMap().getParent()); - table.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT). - setParent(parentInputMap); - table.getActionMap().setParent(parentActionMap); } /** - * This class is used to mimmic the behaviour of the JDK when registering - * keyboard actions. It is the same as the private class used in JComponent - * for the same reason. This class receives an action event and dispatches - * it to the true receiver after altering the actionCommand property of the - * event. + * Fetches the action map from the UI defaults, or create a new one + * if the action map hasn't been initialized. + * + * @return the action map */ - private static class ActionListenerProxy - extends AbstractAction + private ActionMap getActionMap() { - ActionListener target; - String bindingCommandName; - - public ActionListenerProxy(ActionListener li, - String cmd) - { - target = li; - bindingCommandName = cmd; - } + ActionMap am = (ActionMap) UIManager.get("Table.actionMap"); + if (am == null) + { + am = createDefaultActions(); + UIManager.getLookAndFeelDefaults().put("Table.actionMap", am); + } + return am; + } - public void actionPerformed(ActionEvent e) - { - ActionEvent derivedEvent = new ActionEvent(e.getSource(), - e.getID(), - bindingCommandName, - e.getModifiers()); - target.actionPerformed(derivedEvent); - } + private ActionMap createDefaultActions() + { + ActionMapUIResource am = new ActionMapUIResource(); + Action action = new TableAction(); + + am.put("cut", TransferHandler.getCutAction()); + am.put("copy", TransferHandler.getCopyAction()); + am.put("paste", TransferHandler.getPasteAction()); + + am.put("cancel", action); + am.put("selectAll", action); + am.put("clearSelection", action); + am.put("startEditing", action); + + am.put("selectNextRow", action); + am.put("selectNextRowCell", action); + am.put("selectNextRowExtendSelection", action); + am.put("selectNextRowChangeLead", action); + + am.put("selectPreviousRow", action); + am.put("selectPreviousRowCell", action); + am.put("selectPreviousRowExtendSelection", action); + am.put("selectPreviousRowChangeLead", action); + + am.put("selectNextColumn", action); + am.put("selectNextColumnCell", action); + am.put("selectNextColumnExtendSelection", action); + am.put("selectNextColumnChangeLead", action); + + am.put("selectPreviousColumn", action); + am.put("selectPreviousColumnCell", action); + am.put("selectPreviousColumnExtendSelection", action); + am.put("selectPreviousColumnChangeLead", action); + + am.put("scrollLeftChangeSelection", action); + am.put("scrollLeftExtendSelection", action); + am.put("scrollRightChangeSelection", action); + am.put("scrollRightExtendSelection", action); + + am.put("scrollUpChangeSelection", action); + am.put("scrollUpExtendSelection", action); + am.put("scrollDownChangeSelection", action); + am.put("scrolldownExtendSelection", action); + + am.put("selectFirstColumn", action); + am.put("selectFirstColumnExtendSelection", action); + am.put("selectLastColumn", action); + am.put("selectLastColumnExtendSelection", action); + + am.put("selectFirstRow", action); + am.put("selectFirstRowExtendSelection", action); + am.put("selectLastRow", action); + am.put("selectLastRowExtendSelection", action); + + am.put("addToSelection", action); + am.put("toggleAndAnchor", action); + am.put("extendTo", action); + am.put("moveSelectionTo", action); + + return am; } /** @@ -525,7 +582,8 @@ public class BasicTableUI extends TableUI * method is called when a key that has been registered for the JTable * is received. */ - class TableAction extends AbstractAction + private static class TableAction + extends AbstractAction { /** * What to do when this action is called. @@ -534,6 +592,8 @@ public class BasicTableUI extends TableUI */ public void actionPerformed(ActionEvent e) { + JTable table = (JTable) e.getSource(); + DefaultListSelectionModel rowModel = (DefaultListSelectionModel) table.getSelectionModel(); DefaultListSelectionModel colModel @@ -544,9 +604,11 @@ public class BasicTableUI extends TableUI int colLead = colModel.getLeadSelectionIndex(); int colMax = table.getModel().getColumnCount() - 1; - - String command = e.getActionCommand(); - + + // The command with which the action has been called is stored + // in this undocumented action value. This allows us to have only + // one Action instance to serve all keyboard input for JTable. + String command = (String) getValue("__command__"); if (command.equals("selectPreviousRowExtendSelection")) { rowModel.setLeadSelectionIndex(Math.max(rowLead - 1, 0)); @@ -604,11 +666,11 @@ public class BasicTableUI extends TableUI else if (command.equals("scrollUpExtendSelection")) { int target; - if (rowLead == getFirstVisibleRowIndex()) - target = Math.max(0, rowLead - (getLastVisibleRowIndex() - - getFirstVisibleRowIndex() + 1)); + if (rowLead == getFirstVisibleRowIndex(table)) + target = Math.max(0, rowLead - (getLastVisibleRowIndex(table) + - getFirstVisibleRowIndex(table) + 1)); else - target = getFirstVisibleRowIndex(); + target = getFirstVisibleRowIndex(table); rowModel.setLeadSelectionIndex(target); colModel.setLeadSelectionIndex(colLead); @@ -621,11 +683,12 @@ public class BasicTableUI extends TableUI else if (command.equals("scrollRightChangeSelection")) { int target; - if (colLead == getLastVisibleColumnIndex()) - target = Math.min(colMax, colLead + (getLastVisibleColumnIndex() - - getFirstVisibleColumnIndex() + 1)); + if (colLead == getLastVisibleColumnIndex(table)) + target = Math.min(colMax, colLead + + (getLastVisibleColumnIndex(table) + - getFirstVisibleColumnIndex(table) + 1)); else - target = getLastVisibleColumnIndex(); + target = getLastVisibleColumnIndex(table); colModel.setSelectionInterval(target, target); rowModel.setSelectionInterval(rowLead, rowLead); @@ -638,11 +701,11 @@ public class BasicTableUI extends TableUI else if (command.equals("scrollLeftChangeSelection")) { int target; - if (colLead == getFirstVisibleColumnIndex()) - target = Math.max(0, colLead - (getLastVisibleColumnIndex() - - getFirstVisibleColumnIndex() + 1)); + if (colLead == getFirstVisibleColumnIndex(table)) + target = Math.max(0, colLead - (getLastVisibleColumnIndex(table) + - getFirstVisibleColumnIndex(table) + 1)); else - target = getFirstVisibleColumnIndex(); + target = getFirstVisibleColumnIndex(table); colModel.setSelectionInterval(target, target); rowModel.setSelectionInterval(rowLead, rowLead); @@ -724,14 +787,18 @@ public class BasicTableUI extends TableUI // If there are multiple rows and columns selected, select the next // cell and wrap at the edges of the selection. if (command.indexOf("Column") != -1) - advanceMultipleSelection(colModel, colMinSelected, colMaxSelected, - rowModel, rowMinSelected, rowMaxSelected, - command.equals("selectPreviousColumnCell"), true); + advanceMultipleSelection(table, colModel, colMinSelected, + colMaxSelected, rowModel, rowMinSelected, + rowMaxSelected, + command.equals("selectPreviousColumnCell"), + true); else - advanceMultipleSelection(rowModel, rowMinSelected, rowMaxSelected, - colModel, colMinSelected, colMaxSelected, - command.equals("selectPreviousRowCell"), false); + advanceMultipleSelection(table, rowModel, rowMinSelected, + rowMaxSelected, colModel, colMinSelected, + colMaxSelected, + command.equals("selectPreviousRowCell"), + false); } else if (command.equals("selectNextColumn")) { @@ -741,11 +808,11 @@ public class BasicTableUI extends TableUI else if (command.equals("scrollLeftExtendSelection")) { int target; - if (colLead == getFirstVisibleColumnIndex()) - target = Math.max(0, colLead - (getLastVisibleColumnIndex() - - getFirstVisibleColumnIndex() + 1)); + if (colLead == getFirstVisibleColumnIndex(table)) + target = Math.max(0, colLead - (getLastVisibleColumnIndex(table) + - getFirstVisibleColumnIndex(table) + 1)); else - target = getFirstVisibleColumnIndex(); + target = getFirstVisibleColumnIndex(table); colModel.setLeadSelectionIndex(target); rowModel.setLeadSelectionIndex(rowLead); @@ -753,11 +820,11 @@ public class BasicTableUI extends TableUI else if (command.equals("scrollDownChangeSelection")) { int target; - if (rowLead == getLastVisibleRowIndex()) - target = Math.min(rowMax, rowLead + (getLastVisibleRowIndex() - - getFirstVisibleRowIndex() + 1)); + if (rowLead == getLastVisibleRowIndex(table)) + target = Math.min(rowMax, rowLead + (getLastVisibleRowIndex(table) + - getFirstVisibleRowIndex(table) + 1)); else - target = getLastVisibleRowIndex(); + target = getLastVisibleRowIndex(table); rowModel.setSelectionInterval(target, target); colModel.setSelectionInterval(colLead, colLead); @@ -765,11 +832,11 @@ public class BasicTableUI extends TableUI else if (command.equals("scrollRightExtendSelection")) { int target; - if (colLead == getLastVisibleColumnIndex()) - target = Math.min(colMax, colLead + (getLastVisibleColumnIndex() - - getFirstVisibleColumnIndex() + 1)); + if (colLead == getLastVisibleColumnIndex(table)) + target = Math.min(colMax, colLead + (getLastVisibleColumnIndex(table) + - getFirstVisibleColumnIndex(table) + 1)); else - target = getLastVisibleColumnIndex(); + target = getLastVisibleColumnIndex(table); colModel.setLeadSelectionIndex(target); rowModel.setLeadSelectionIndex(rowLead); @@ -786,11 +853,11 @@ public class BasicTableUI extends TableUI else if (command.equals("scrollDownExtendSelection")) { int target; - if (rowLead == getLastVisibleRowIndex()) - target = Math.min(rowMax, rowLead + (getLastVisibleRowIndex() - - getFirstVisibleRowIndex() + 1)); + if (rowLead == getLastVisibleRowIndex(table)) + target = Math.min(rowMax, rowLead + (getLastVisibleRowIndex(table) + - getFirstVisibleRowIndex(table) + 1)); else - target = getLastVisibleRowIndex(); + target = getLastVisibleRowIndex(table); rowModel.setLeadSelectionIndex(target); colModel.setLeadSelectionIndex(colLead); @@ -798,11 +865,11 @@ public class BasicTableUI extends TableUI else if (command.equals("scrollUpChangeSelection")) { int target; - if (rowLead == getFirstVisibleRowIndex()) - target = Math.max(0, rowLead - (getLastVisibleRowIndex() - - getFirstVisibleRowIndex() + 1)); + if (rowLead == getFirstVisibleRowIndex(table)) + target = Math.max(0, rowLead - (getLastVisibleRowIndex(table) + - getFirstVisibleRowIndex(table) + 1)); else - target = getFirstVisibleRowIndex(); + target = getFirstVisibleRowIndex(table); rowModel.setSelectionInterval(target, target); colModel.setSelectionInterval(colLead, colLead); @@ -927,7 +994,7 @@ public class BasicTableUI extends TableUI * Returns the column index of the first visible column. * @return the column index of the first visible column. */ - int getFirstVisibleColumnIndex() + int getFirstVisibleColumnIndex(JTable table) { ComponentOrientation or = table.getComponentOrientation(); Rectangle r = table.getVisibleRect(); @@ -940,7 +1007,7 @@ public class BasicTableUI extends TableUI * Returns the column index of the last visible column. * */ - int getLastVisibleColumnIndex() + int getLastVisibleColumnIndex(JTable table) { ComponentOrientation or = table.getComponentOrientation(); Rectangle r = table.getVisibleRect(); @@ -953,7 +1020,7 @@ public class BasicTableUI extends TableUI * Returns the row index of the first visible row. * */ - int getFirstVisibleRowIndex() + int getFirstVisibleRowIndex(JTable table) { ComponentOrientation or = table.getComponentOrientation(); Rectangle r = table.getVisibleRect(); @@ -966,7 +1033,7 @@ public class BasicTableUI extends TableUI * Returns the row index of the last visible row. * */ - int getLastVisibleRowIndex() + int getLastVisibleRowIndex(JTable table) { ComponentOrientation or = table.getComponentOrientation(); Rectangle r = table.getVisibleRect(); @@ -978,7 +1045,7 @@ public class BasicTableUI extends TableUI // area is larger than the table) if (table.rowAtPoint(r.getLocation()) == -1) { - if (getFirstVisibleRowIndex() == -1) + if (getFirstVisibleRowIndex(table) == -1) return -1; else return table.getModel().getRowCount() - 1; @@ -1004,7 +1071,8 @@ public class BasicTableUI extends TableUI * @param reverse true if shift was held for the event * @param eventIsTab true if TAB was pressed, false if ENTER pressed */ - void advanceMultipleSelection(ListSelectionModel firstModel, int firstMin, + void advanceMultipleSelection(JTable table, ListSelectionModel firstModel, + int firstMin, int firstMax, ListSelectionModel secondModel, int secondMin, int secondMax, boolean reverse, boolean eventIsTab) @@ -1168,29 +1236,24 @@ public class BasicTableUI extends TableUI table.addPropertyChangeListener(propertyChangeListener); } - protected void uninstallDefaults() throws NotImplementedException + /** + * Uninstalls UI defaults that have been installed by + * {@link #installDefaults()}. + */ + protected void uninstallDefaults() { - // TODO: this method used to do the following which is not - // quite right (at least it breaks apps that run fine with the - // JDK): - // - // table.setFont(null); - // table.setGridColor(null); - // table.setForeground(null); - // table.setBackground(null); - // table.setSelectionForeground(null); - // table.setSelectionBackground(null); - // - // This would leave the component in a corrupt state, which is - // not acceptable. A possible solution would be to have component - // level defaults installed, that get overridden by the UI defaults - // and get restored in this method. I am not quite sure about this - // though. / Roman Kennke + // Nothing to do here for now. } - protected void uninstallKeyboardActions() throws NotImplementedException + /** + * Uninstalls the keyboard actions that have been installed by + * {@link #installKeyboardActions()}. + */ + protected void uninstallKeyboardActions() { - // TODO: Implement this properly. + SwingUtilities.replaceUIInputMap(table, JComponent. + WHEN_ANCESTOR_OF_FOCUSED_COMPONENT, null); + SwingUtilities.replaceUIActionMap(table, null); } protected void uninstallListeners() diff --git a/javax/swing/plaf/basic/BasicTextUI.java b/javax/swing/plaf/basic/BasicTextUI.java index 5febe141d..f201984db 100644 --- a/javax/swing/plaf/basic/BasicTextUI.java +++ b/javax/swing/plaf/basic/BasicTextUI.java @@ -83,7 +83,6 @@ import javax.swing.text.Highlighter; import javax.swing.text.JTextComponent; import javax.swing.text.Keymap; import javax.swing.text.Position; -import javax.swing.text.Utilities; import javax.swing.text.View; import javax.swing.text.ViewFactory; @@ -242,6 +241,12 @@ public abstract class BasicTextUI extends TextUI return Integer.MAX_VALUE; } + public void setSize(float w, float h) + { + if (view != null) + view.setSize(w, h); + } + /** * Paints the view. This is delegated to the real root view. * @@ -861,6 +866,7 @@ public abstract class BasicTextUI extends TextUI protected void uninstallListeners() { textComponent.removeFocusListener(focuslistener); + textComponent.getDocument().removeDocumentListener(documentHandler); } /** @@ -892,14 +898,19 @@ public abstract class BasicTextUI extends TextUI */ public Dimension getPreferredSize(JComponent c) { - View v = getRootView(textComponent); - - float w = v.getPreferredSpan(View.X_AXIS); - float h = v.getPreferredSpan(View.Y_AXIS); - + Dimension d = c.getSize(); Insets i = c.getInsets(); - return new Dimension((int) w + i.left + i.right, + if (d.width > (i.left + i.right) && d.height > (i.top + i.bottom)) + { + rootView.setSize(d.width - i.left - i.right, + d.height - i.top - i.bottom); + } + float w = rootView.getPreferredSpan(View.X_AXIS); + float h = rootView.getPreferredSpan(View.Y_AXIS); + + Dimension size = new Dimension((int) w + i.left + i.right, (int) h + i.top + i.bottom); + return size; } /** @@ -1042,7 +1053,7 @@ public abstract class BasicTextUI extends TextUI */ public void damageRange(JTextComponent t, int p0, int p1) { - damageRange(t, p0, p1, null, null); + damageRange(t, p0, p1, Position.Bias.Forward, Position.Bias.Backward); } /** @@ -1062,106 +1073,36 @@ public abstract class BasicTextUI extends TextUI public void damageRange(JTextComponent t, int p0, int p1, Position.Bias firstBias, Position.Bias secondBias) { - // Do nothing if the component cannot be properly displayed. - if (t.getWidth() == 0 || t.getHeight() == 0) - return; - - try + Rectangle alloc = getVisibleEditorRect(); + if (alloc != null) { - // Limit p0 and p1 to sane values to prevent unfriendly - // BadLocationExceptions. This makes it possible for the highlighter - // to send us illegal values which can happen when a large number - // of selected characters are removed (eg. by pressing delete - // or backspace). - // The reference implementation does not throw an exception, too. - p0 = Math.min(p0, t.getDocument().getLength()); - p1 = Math.min(p1, t.getDocument().getLength()); - - Rectangle l1 = modelToView(t, p0, firstBias); - Rectangle l2 = modelToView(t, p1, secondBias); - if (l1 == null || l2 == null) + Document doc = t.getDocument(); + + // Acquire lock here to avoid structural changes in between. + if (doc instanceof AbstractDocument) + ((AbstractDocument) doc).readLock(); + try { - // Unable to determine the start or end of the selection. - t.repaint(); + rootView.setSize(alloc.width, alloc.height); + Shape damage = rootView.modelToView(p0, firstBias, p1, secondBias, + alloc); + Rectangle r = damage instanceof Rectangle ? (Rectangle) damage + : damage.getBounds(); + textComponent.repaint(r.x, r.y, r.width, r.height); } - else if (l1.y == l2.y) + catch (BadLocationException ex) { - SwingUtilities.computeUnion(l2.x, l2.y, l2.width, l2.height, l1); - t.repaint(l1); + // Lets ignore this as it causes no serious problems. + // For debugging, comment this out. + // ex.printStackTrace(); } - else + finally { - // The two rectangles lie on different lines and we need a - // different algorithm to calculate the damaged area: - // 1. The line of p0 is damaged from the position of p0 - // to the right border. - // 2. All lines between the ones where p0 and p1 lie on - // are completely damaged. Use the allocation area to find - // out the bounds. - // 3. The final line is damaged from the left bound to the - // position of p1. - Insets insets = t.getInsets(); - - // Damage first line until the end. - l1.width = insets.right + t.getWidth() - l1.x; - t.repaint(l1); - - // Note: Utilities.getPositionBelow() may return the offset - // that was put in. In that case there is no next line and - // we should stop searching for one. - - int posBelow = Utilities.getPositionBelow(t, p0, l1.x); - int p1RowStart = Utilities.getRowStart(t, p1); - - if (posBelow != -1 - && posBelow != p0 - && Utilities.getRowStart(t, posBelow) != p1RowStart) - { - // Take the rectangle of the offset we just found and grow it - // to the maximum width. Retain y because this is our start - // height. - Rectangle grow = modelToView(t, posBelow); - grow.x = insets.left; - grow.width = t.getWidth() + insets.right; - - // Find further lines which have to be damaged completely. - int nextPosBelow = posBelow; - while (nextPosBelow != -1 - && posBelow != nextPosBelow - && Utilities.getRowStart(t, nextPosBelow) != p1RowStart) - { - posBelow = nextPosBelow; - nextPosBelow = Utilities.getPositionBelow(t, posBelow, - l1.x); - - if (posBelow == nextPosBelow) - break; - } - // Now posBelow is an offset on the last line which has to be - // damaged completely. (newPosBelow is on the same line as p1) - - // Retrieve the rectangle of posBelow and use its y and height - // value to calculate the final height of the multiple line - // spanning rectangle. - Rectangle end = modelToView(t, posBelow); - grow.height = end.y + end.height - grow.y; - - // Mark that area as damage. - t.repaint(grow); - } - - // Damage last line from its beginning to the position of p1. - l2.width += l2.x; - l2.x = insets.left; - t.repaint(l2); + // Release lock. + if (doc instanceof AbstractDocument) + ((AbstractDocument) doc).readUnlock(); } } - catch (BadLocationException ex) - { - AssertionError err = new AssertionError("Unexpected bad location"); - err.initCause(ex); - throw err; - } } /** @@ -1258,10 +1199,29 @@ public abstract class BasicTextUI extends TextUI public Rectangle modelToView(JTextComponent t, int pos, Position.Bias bias) throws BadLocationException { - Rectangle r = getVisibleEditorRect(); - - return (r != null) ? rootView.modelToView(pos, r, bias).getBounds() - : null; + // We need to read-lock here because we depend on the document + // structure not beeing changed in between. + Document doc = textComponent.getDocument(); + if (doc instanceof AbstractDocument) + ((AbstractDocument) doc).readLock(); + Rectangle rect = null; + try + { + Rectangle r = getVisibleEditorRect(); + if (r != null) + { + rootView.setSize(r.width, r.height); + Shape s = rootView.modelToView(pos, r, bias); + if (s != null) + rect = s.getBounds(); + } + } + finally + { + if (doc instanceof AbstractDocument) + ((AbstractDocument) doc).readUnlock(); + } + return rect; } /** @@ -1276,7 +1236,7 @@ public abstract class BasicTextUI extends TextUI */ public int viewToModel(JTextComponent t, Point pt) { - return viewToModel(t, pt, null); + return viewToModel(t, pt, new Position.Bias[1]); } /** diff --git a/javax/swing/plaf/basic/BasicToolBarUI.java b/javax/swing/plaf/basic/BasicToolBarUI.java index a4ed0c87d..1c36b408d 100644 --- a/javax/swing/plaf/basic/BasicToolBarUI.java +++ b/javax/swing/plaf/basic/BasicToolBarUI.java @@ -38,8 +38,6 @@ exception statement from your version. */ package javax.swing.plaf.basic; -import gnu.classpath.NotImplementedException; - import java.awt.BorderLayout; import java.awt.Color; import java.awt.Component; @@ -50,6 +48,7 @@ import java.awt.Insets; import java.awt.Point; import java.awt.Rectangle; import java.awt.Window; +import java.awt.event.ActionEvent; import java.awt.event.ContainerEvent; import java.awt.event.ContainerListener; import java.awt.event.FocusEvent; @@ -62,7 +61,11 @@ import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.util.Hashtable; +import javax.swing.AbstractAction; import javax.swing.AbstractButton; +import javax.swing.Action; +import javax.swing.ActionMap; +import javax.swing.InputMap; import javax.swing.JButton; import javax.swing.JComponent; import javax.swing.JDialog; @@ -77,6 +80,7 @@ import javax.swing.UIManager; import javax.swing.border.Border; import javax.swing.border.CompoundBorder; import javax.swing.event.MouseInputListener; +import javax.swing.plaf.ActionMapUIResource; import javax.swing.plaf.ComponentUI; import javax.swing.plaf.ToolBarUI; import javax.swing.plaf.UIResource; @@ -87,6 +91,35 @@ import javax.swing.plaf.basic.BasicBorders.ButtonBorder; */ public class BasicToolBarUI extends ToolBarUI implements SwingConstants { + + /** + * Implements the keyboard actions for JToolBar. + */ + static class ToolBarAction + extends AbstractAction + { + /** + * Performs the action. + */ + public void actionPerformed(ActionEvent event) + { + Object cmd = getValue("__command__"); + JToolBar toolBar = (JToolBar) event.getSource(); + BasicToolBarUI ui = (BasicToolBarUI) toolBar.getUI(); + + if (cmd.equals("navigateRight")) + ui.navigateFocusedComp(EAST); + else if (cmd.equals("navigateLeft")) + ui.navigateFocusedComp(WEST); + else if (cmd.equals("navigateUp")) + ui.navigateFocusedComp(NORTH); + else if (cmd.equals("navigateDown")) + ui.navigateFocusedComp(SOUTH); + else + assert false : "Shouldn't reach here"; + } + } + /** Static owner of all DragWindows. * This is package-private to avoid an accessor method. */ static JFrame owner = new JFrame(); @@ -619,9 +652,46 @@ public class BasicToolBarUI extends ToolBarUI implements SwingConstants * by the look and feel. */ protected void installKeyboardActions() - throws NotImplementedException { - // FIXME: implement. + // Install the input map. + InputMap inputMap = + (InputMap) SharedUIDefaults.get("ToolBar.ancestorInputMap"); + SwingUtilities.replaceUIInputMap(toolBar, + JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT, + inputMap); + + // FIXME: The JDK uses a LazyActionMap for parentActionMap + SwingUtilities.replaceUIActionMap(toolBar, getActionMap()); + } + + /** + * Fetches the action map from the UI defaults, or create a new one + * if the action map hasn't been initialized. + * + * @return the action map + */ + private ActionMap getActionMap() + { + ActionMap am = (ActionMap) UIManager.get("ToolBar.actionMap"); + if (am == null) + { + am = createDefaultActions(); + UIManager.getLookAndFeelDefaults().put("ToolBar.actionMap", am); + } + return am; + } + + private ActionMap createDefaultActions() + { + ActionMapUIResource am = new ActionMapUIResource(); + Action action = new ToolBarAction(); + + am.put("navigateLeft", action); + am.put("navigateRight", action); + am.put("navigateUp", action); + am.put("navigateDown", action); + + return am; } /** @@ -643,7 +713,12 @@ public class BasicToolBarUI extends ToolBarUI implements SwingConstants floatFrame.addWindowListener(windowListener); toolBarFocusListener = createToolBarFocusListener(); - toolBar.addFocusListener(toolBarFocusListener); + if (toolBarFocusListener != null) + { + int count = toolBar.getComponentCount(); + for (int i = 0; i < count; i++) + toolBar.getComponent(i).addFocusListener(toolBarFocusListener); + } } /** @@ -758,9 +833,55 @@ public class BasicToolBarUI extends ToolBarUI implements SwingConstants * @param direction The direction to give focus to. */ protected void navigateFocusedComp(int direction) - throws NotImplementedException { - // FIXME: Implement. + int count = toolBar.getComponentCount(); + switch (direction) + { + case EAST: + case SOUTH: + if (focusedCompIndex >= 0 && focusedCompIndex < count) + { + int i = focusedCompIndex + 1; + boolean focusRequested = false; + // Find component to focus and request focus on it. + while (i != focusedCompIndex && ! focusRequested) + { + if (i >= count) + i = 0; + Component comp = toolBar.getComponentAtIndex(i++); + if (comp != null && comp.isFocusable() + && comp.isEnabled()) + { + comp.requestFocus(); + focusRequested = true; + } + } + } + break; + case WEST: + case NORTH: + if (focusedCompIndex >= 0 && focusedCompIndex < count) + { + int i = focusedCompIndex - 1; + boolean focusRequested = false; + // Find component to focus and request focus on it. + while (i != focusedCompIndex && ! focusRequested) + { + if (i < 0) + i = count - 1; + Component comp = toolBar.getComponentAtIndex(i--); + if (comp != null && comp.isFocusable() + && comp.isEnabled()) + { + comp.requestFocus(); + focusRequested = true; + } + } + } + break; + default: + break; + } } /** @@ -925,9 +1046,10 @@ public class BasicToolBarUI extends ToolBarUI implements SwingConstants * This method uninstalls keyboard actions installed by the UI. */ protected void uninstallKeyboardActions() - throws NotImplementedException { - // FIXME: implement. + SwingUtilities.replaceUIInputMap(toolBar, JComponent. + WHEN_ANCESTOR_OF_FOCUSED_COMPONENT, null); + SwingUtilities.replaceUIActionMap(toolBar, null); } /** @@ -935,8 +1057,13 @@ public class BasicToolBarUI extends ToolBarUI implements SwingConstants */ protected void uninstallListeners() { - toolBar.removeFocusListener(toolBarFocusListener); - toolBarFocusListener = null; + if (toolBarFocusListener != null) + { + int count = toolBar.getComponentCount(); + for (int i = 0; i < count; i++) + toolBar.getComponent(i).removeFocusListener(toolBarFocusListener); + toolBarFocusListener = null; + } floatFrame.removeWindowListener(windowListener); windowListener = null; @@ -1294,6 +1421,10 @@ public class BasicToolBarUI extends ToolBarUI implements SwingConstants cachedBounds = toolBar.getPreferredSize(); cachedOrientation = toolBar.getOrientation(); + + Component c = e.getChild(); + if (toolBarFocusListener != null) + c.addFocusListener(toolBarFocusListener); } /** @@ -1307,6 +1438,10 @@ public class BasicToolBarUI extends ToolBarUI implements SwingConstants setBorderToNormal(e.getChild()); cachedBounds = toolBar.getPreferredSize(); cachedOrientation = toolBar.getOrientation(); + + Component c = e.getChild(); + if (toolBarFocusListener != null) + c.removeFocusListener(toolBarFocusListener); } } @@ -1334,29 +1469,32 @@ public class BasicToolBarUI extends ToolBarUI implements SwingConstants /** * Creates a new ToolBarFocusListener object. */ - protected ToolBarFocusListener() throws NotImplementedException + protected ToolBarFocusListener() { - // FIXME: implement. + // Nothing to do here. } /** - * DOCUMENT ME! + * Receives notification when the toolbar or one of it's component + * receives the keyboard input focus. * - * @param e DOCUMENT ME! + * @param e the focus event */ - public void focusGained(FocusEvent e) throws NotImplementedException + public void focusGained(FocusEvent e) { - // FIXME: implement. + Component c = e.getComponent(); + focusedCompIndex = toolBar.getComponentIndex(c); } /** - * DOCUMENT ME! + * Receives notification when the toolbar or one of it's component + * looses the keyboard input focus. * - * @param e DOCUMENT ME! + * @param e the focus event */ - public void focusLost(FocusEvent e) throws NotImplementedException + public void focusLost(FocusEvent e) { - // FIXME: implement. + // Do nothing here. } } diff --git a/javax/swing/plaf/basic/BasicToolTipUI.java b/javax/swing/plaf/basic/BasicToolTipUI.java index 5cec2e333..94e7bc322 100644 --- a/javax/swing/plaf/basic/BasicToolTipUI.java +++ b/javax/swing/plaf/basic/BasicToolTipUI.java @@ -40,19 +40,20 @@ package javax.swing.plaf.basic; import java.awt.Color; import java.awt.Dimension; +import java.awt.Font; import java.awt.FontMetrics; import java.awt.Graphics; import java.awt.Insets; import java.awt.Rectangle; -import java.awt.Toolkit; +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; import javax.swing.JComponent; import javax.swing.JToolTip; import javax.swing.LookAndFeel; -import javax.swing.SwingConstants; -import javax.swing.SwingUtilities; import javax.swing.plaf.ComponentUI; import javax.swing.plaf.ToolTipUI; +import javax.swing.text.View; /** * This is the Basic Look and Feel UI class for JToolTip. @@ -60,6 +61,28 @@ import javax.swing.plaf.ToolTipUI; public class BasicToolTipUI extends ToolTipUI { + /** + * Receives notification when a property of the JToolTip changes. + * This updates the HTML renderer if appropriate. + */ + private class PropertyChangeHandler + implements PropertyChangeListener + { + + public void propertyChange(PropertyChangeEvent e) + { + String prop = e.getPropertyName(); + if (prop.equals("tiptext") || prop.equals("font") + || prop.equals("foreground")) + { + JToolTip tip = (JToolTip) e.getSource(); + String text = tip.getTipText(); + BasicHTML.updateRenderer(tip, text); + } + } + + } + /** The shared instance of BasicToolTipUI used for all ToolTips. */ private static BasicToolTipUI shared; @@ -67,6 +90,11 @@ public class BasicToolTipUI extends ToolTipUI private String text; /** + * Handles property changes. + */ + private PropertyChangeListener propertyChangeHandler; + + /** * Creates a new BasicToolTipUI object. */ public BasicToolTipUI() @@ -98,7 +126,12 @@ public class BasicToolTipUI extends ToolTipUI */ public Dimension getMaximumSize(JComponent c) { - return getPreferredSize(c); + Dimension d = getPreferredSize(c); + View view = (View) c.getClientProperty(BasicHTML.propertyKey); + if (view != null) + d.width += view.getMaximumSpan(View.X_AXIS) + - view.getPreferredSpan(View.X_AXIS); + return d; } /** @@ -110,7 +143,12 @@ public class BasicToolTipUI extends ToolTipUI */ public Dimension getMinimumSize(JComponent c) { - return getPreferredSize(c); + Dimension d = getPreferredSize(c); + View view = (View) c.getClientProperty(BasicHTML.propertyKey); + if (view != null) + d.width -= view.getPreferredSpan(View.X_AXIS) + - view.getMinimumSpan(View.X_AXIS); + return d; } /** @@ -123,22 +161,25 @@ public class BasicToolTipUI extends ToolTipUI public Dimension getPreferredSize(JComponent c) { JToolTip tip = (JToolTip) c; - FontMetrics fm; - Toolkit g = tip.getToolkit(); - text = tip.getTipText(); - - Rectangle vr = new Rectangle(); - Rectangle ir = new Rectangle(); - Rectangle tr = new Rectangle(); - Insets insets = tip.getInsets(); - fm = g.getFontMetrics(tip.getFont()); - SwingUtilities.layoutCompoundLabel(tip, fm, text, null, - SwingConstants.CENTER, - SwingConstants.CENTER, - SwingConstants.CENTER, - SwingConstants.CENTER, vr, ir, tr, 0); - return new Dimension(insets.left + tr.width + insets.right, - insets.top + tr.height + insets.bottom); + String str = tip.getTipText(); + FontMetrics fm = c.getFontMetrics(c.getFont()); + Insets i = c.getInsets(); + Dimension d = new Dimension(i.left + i.right, i.top + i.bottom); + if (str != null && ! str.equals("")) + { + View view = (View) c.getClientProperty(BasicHTML.propertyKey); + if (view != null) + { + d.width += (int) view.getPreferredSpan(View.X_AXIS); + d.height += (int) view.getPreferredSpan(View.Y_AXIS); + } + else + { + d.width += fm.stringWidth(str) + 6; + d.height += fm.getHeight(); + } + } + return d; } /** @@ -160,7 +201,8 @@ public class BasicToolTipUI extends ToolTipUI */ protected void installListeners(JComponent c) { - // TODO: Implement this properly. + propertyChangeHandler = new PropertyChangeHandler(); + c.addPropertyChangeListener(propertyChangeHandler); } /** @@ -172,6 +214,7 @@ public class BasicToolTipUI extends ToolTipUI { c.setOpaque(true); installDefaults(c); + BasicHTML.updateRenderer(c, ((JToolTip) c).getTipText()); installListeners(c); } @@ -186,26 +229,25 @@ public class BasicToolTipUI extends ToolTipUI JToolTip tip = (JToolTip) c; String text = tip.getTipText(); - Toolkit t = tip.getToolkit(); - if (text == null) - return; - - Rectangle vr = new Rectangle(); - vr = SwingUtilities.calculateInnerArea(tip, vr); - Rectangle ir = new Rectangle(); - Rectangle tr = new Rectangle(); - FontMetrics fm = t.getFontMetrics(tip.getFont()); + Font font = c.getFont(); + FontMetrics fm = c.getFontMetrics(font); int ascent = fm.getAscent(); - SwingUtilities.layoutCompoundLabel(tip, fm, text, null, - SwingConstants.CENTER, - SwingConstants.CENTER, - SwingConstants.CENTER, - SwingConstants.CENTER, vr, ir, tr, 0); + Insets i = c.getInsets(); + Dimension size = c.getSize(); + Rectangle paintR = new Rectangle(i.left, i.top, + size.width - i.left - i.right, + size.height - i.top - i.bottom); Color saved = g.getColor(); + Font oldFont = g.getFont(); g.setColor(Color.BLACK); - g.drawString(text, vr.x, vr.y + ascent); + View view = (View) c.getClientProperty(BasicHTML.propertyKey); + if (view != null) + view.paint(g, paintR); + else + g.drawString(text, paintR.x + 3, paintR.y + ascent); + g.setFont(oldFont); g.setColor(saved); } @@ -229,7 +271,11 @@ public class BasicToolTipUI extends ToolTipUI */ protected void uninstallListeners(JComponent c) { - // TODO: Implement this properly. + if (propertyChangeHandler != null) + { + c.removePropertyChangeListener(propertyChangeHandler); + propertyChangeHandler = null; + } } /** @@ -240,6 +286,7 @@ public class BasicToolTipUI extends ToolTipUI public void uninstallUI(JComponent c) { uninstallDefaults(c); + BasicHTML.updateRenderer(c, ""); uninstallListeners(c); } } diff --git a/javax/swing/plaf/basic/BasicTreeUI.java b/javax/swing/plaf/basic/BasicTreeUI.java index 0ca30b7ad..9a193986a 100644 --- a/javax/swing/plaf/basic/BasicTreeUI.java +++ b/javax/swing/plaf/basic/BasicTreeUI.java @@ -238,9 +238,6 @@ public class BasicTreeUI /** Set to true if the editor has a different size than the renderer. */ protected boolean editorHasDifferentSize; - /** The action bound to KeyStrokes. */ - TreeAction action; - /** Boolean to keep track of editing. */ boolean isEditing; @@ -477,8 +474,18 @@ public class BasicTreeUI */ protected void setCellRenderer(TreeCellRenderer tcr) { - currentCellRenderer = tcr; + // Finish editing before changing the renderer. + completeEditing(); + + // The renderer is set in updateRenderer. updateRenderer(); + + // Refresh the layout if necessary. + if (treeState != null) + { + treeState.invalidateSizes(); + updateSize(); + } } /** @@ -1071,7 +1078,6 @@ public class BasicTreeUI */ protected void uninstallKeyboardActions() { - action = null; tree.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).setParent( null); tree.getActionMap().setParent(null); @@ -1172,10 +1178,26 @@ public class BasicTreeUI protected void updateRenderer() { if (tree != null) - currentCellRenderer = tree.getCellRenderer(); - - if (currentCellRenderer == null) - currentCellRenderer = createDefaultCellRenderer(); + { + TreeCellRenderer rend = tree.getCellRenderer(); + if (rend != null) + { + createdRenderer = false; + currentCellRenderer = rend; + if (createdCellEditor) + tree.setCellEditor(null); + } + else + { + tree.setCellRenderer(createDefaultCellRenderer()); + createdRenderer = true; + } + } + else + { + currentCellRenderer = null; + createdRenderer = false; + } updateCellEditor(); } @@ -1272,8 +1294,6 @@ public class BasicTreeUI JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT, ancestorInputMap); - action = new TreeAction(); - SwingUtilities.replaceUIActionMap(tree, getActionMap()); } @@ -1303,9 +1323,6 @@ public class BasicTreeUI ActionMapUIResource am = new ActionMapUIResource(); Action action; - action = new TreeAction(); - am.put(action.getValue(Action.NAME), action); - // TreeHomeAction. action = new TreeHomeAction(-1, "selectFirst"); am.put(action.getValue(Action.NAME), action); @@ -2034,94 +2051,6 @@ public class BasicTreeUI } /** - * This class implements the actions that we want to happen when specific keys - * are pressed for the JTree. The actionPerformed method is called when a key - * that has been registered for the JTree is received. - */ - class TreeAction - extends AbstractAction - { - - /** - * What to do when this action is called. - * - * @param e the ActionEvent that caused this action. - */ - public void actionPerformed(ActionEvent e) - { - String command = e.getActionCommand(); - TreePath lead = tree.getLeadSelectionPath(); - - if (command.equals("selectPreviousChangeLead") - || command.equals("selectPreviousExtendSelection") - || command.equals("selectPrevious") || command.equals("selectNext") - || command.equals("selectNextExtendSelection") - || command.equals("selectNextChangeLead")) - (new TreeIncrementAction(0, "")).actionPerformed(e); - else if (command.equals("selectParent") || command.equals("selectChild")) - (new TreeTraverseAction(0, "")).actionPerformed(e); - else if (command.equals("selectAll")) - { - TreePath[] paths = new TreePath[treeState.getRowCount()]; - for (int i = 0; i < paths.length; i++) - paths[i] = treeState.getPathForRow(i); - tree.addSelectionPaths(paths); - } - else if (command.equals("startEditing")) - tree.startEditingAtPath(lead); - else if (command.equals("toggle")) - { - if (tree.isEditing()) - tree.stopEditing(); - else - { - Object last = lead.getLastPathComponent(); - TreePath path = new TreePath(getPathToRoot(last, 0)); - if (! treeModel.isLeaf(last)) - toggleExpandState(path); - } - } - else if (command.equals("clearSelection")) - tree.clearSelection(); - - if (tree.isEditing() && ! command.equals("startEditing")) - tree.stopEditing(); - - tree.scrollPathToVisible(tree.getLeadSelectionPath()); - } - } - - /** - * This class is used to mimic the behaviour of the JDK when registering - * keyboard actions. It is the same as the private class used in JComponent - * for the same reason. This class receives an action event and dispatches it - * to the true receiver after altering the actionCommand property of the - * event. - */ - private static class ActionListenerProxy - extends AbstractAction - { - ActionListener target; - - String bindingCommandName; - - public ActionListenerProxy(ActionListener li, String cmd) - { - target = li; - bindingCommandName = cmd; - } - - public void actionPerformed(ActionEvent e) - { - ActionEvent derivedEvent = new ActionEvent(e.getSource(), e.getID(), - bindingCommandName, - e.getModifiers()); - - target.actionPerformed(derivedEvent); - } - } - - /** * Updates the preferred size when scrolling, if necessary. */ public class ComponentHandler @@ -2503,6 +2432,9 @@ public class BasicTreeUI } } } + + // We need to request the focus. + tree.requestFocusInWindow(); } /** |