Package uk.ac.starlink.topcat

# Class FixedJComboBox<E>

java.lang.Object
java.awt.Component
java.awt.Container
javax.swing.JComponent
javax.swing.JComboBox<E>
uk.ac.starlink.topcat.FixedJComboBox<E>

All Implemented Interfaces:
  `ActionListener`, `ImageObserver`, `ItemSelectable`, `MenuContainer`, `Serializable`, `EventListener`, `Accessible`, `ListDataListener`

Direct Known Subclasses:
  `ColumnDataComboBox`

public class FixedJComboBox<E> extends JComboBox<E>
JComboBox drop-in replacement that fixes an action firing misfeature in the standard implementation.

The problem is to do with firing ActionEvents when the ComboBoxModel is changed. According to the `JComboBox.addActionListener(java.awt.event.ActionListener)` documentation, ActionListeners should only be informed when a selection is made, that is when the selection changes. In general that's true, an ActionEvent is only fired when the model selection changes, but at least in Oracle Java 8/OpenJDK 8 such an event can be fired if the model content (list data) changes but the current selection is null both before and after that change (if the selection is an particular non-null value before and after, no ActionEvent is fired, as expected). Since there are a lot of listeners on these selectors in topcat, and certain user actions can cause a large number of changes to the model without changing the selection, this can cause a lot of unnecessary events to be fired, which can in some circumstances result in genuine responsiveness issues.

This class overrides the `contentsChanged(javax.swing.event.ListDataEvent)` method, and enough other methods to make that work, so that no event is fired in the event of a null->null selection change.

Since:
  6 Oct 2023
Author:
  Mark Taylor
See Also:
  - Serialized Form

## Nested Class Summary

## Nested classes/interfaces inherited from class javax.swing.JComboBox

`JComboBox.AccessibleJComboBox, JComboBox.KeySelectionManager`

## Nested classes/interfaces inherited from class javax.swing.JComponent

`JComponent.AccessibleJComponent`

## Nested classes/interfaces inherited from class java.awt.Container

`Container.AccessibleAWTContainer`

## Nested classes/interfaces inherited from class java.awt.Component

`Component.AccessibleAWTComponent, Component.BaselineResizeBehavior, Component.BltBufferStrategy, Component.FlipBufferStrategy`

## Field Summary

### Fields inherited from class javax.swing.JComboBox

`actionCommand, dataModel, editor, isEditable, keySelectionManager, lightWeightPopupEnabled, maximumRowCount, renderer, selectedItemReminder`

### Fields inherited from class javax.swing.JComponent

`listenerList, TOOL_TIP_TEXT_KEY, ui, UNDEFINED_CONDITION, WHEN_ANCESTOR_OF_FOCUSED_COMPONENT, WHEN_FOCUSED, WHEN_IN_FOCUSED_WINDOW`

### Fields inherited from class java.awt.Component

`accessibleContext, BOTTOM_ALIGNMENT, CENTER_ALIGNMENT, LEFT_ALIGNMENT, RIGHT_ALIGNMENT, TOP_ALIGNMENT`

### Fields inherited from interface java.awt.image.ImageObserver

`ABORT, ALLBITS, ERROR, FRAMEBITS, HEIGHT, PROPERTIES, SOMEBITS, WIDTH`

## Constructor Summary

Constructors
Constructor
Description
`FixedJComboBox()`
Creates a JComboBox with a default data model.
`FixedJComboBox(E[] items)`
Creates a JComboBox that contains the elements in the specified array.
`FixedJComboBox(ComboBoxModel<E> model)`
Creates a JComboBox that takes its items from an existing ComboBoxModel.

## Method Summary

Modifier and Type
Method
Description
`void`
`contentsChanged(ListDataEvent evt)`
Overriding this method is questionable, since the OpenJDK javadoc says: "This method is public as an implementation side effect.
`void`
`removeAllItems()`
`protected void`
`selectedItemChanged()`
`void`
`setModel(ComboBoxModel<E> model)`

### Methods inherited from class javax.swing.JComboBox

`actionPerformed, actionPropertyChanged, addActionListener, addItem, addItemListener, addPopupMenuListener, configureEditor, configurePropertiesFromAction, createActionPropertyChangeListener, createDefaultKeySelectionManager, fireActionEvent, fireItemStateChanged, firePopupMenuCanceled, firePopupMenuWillBecomeInvisible, firePopupMenuWillBecomeVisible, getAccessibleContext, getAction, getActionCommand, getActionListeners, getEditor, getItemAt, getItemCount, getItemListeners, getKeySelectionManager, getMaximumRowCount, getModel, getPopupMenuListeners, getPrototypeDisplayValue, getRenderer, getSelectedIndex, getSelectedItem, getSelectedObjects, getUI, getUIClassID, hidePopup, insertItemAt, installAncestorListener, intervalAdded, intervalRemoved, isEditable, isLightWeightPopupEnabled, isPopupVisible, paramString, processKeyBinding, processKeyEvent, removeActionListener, removeItem, removeItemAt, removeItemListener, removePopupMenuListener, selectWithKeyChar, setAction, setActionCommand, setEditable, setEditor, setEnabled, setKeySelectionManager, setLightWeightPopupEnabled, setMaximumRowCount, setPopupVisible, setPrototypeDisplayValue, setRenderer, setSelectedIndex, setSelectedItem, setUI, showPopup, updateUI`

### Methods inherited from class javax.swing.JComponent

`addAncestorListener, addNotify, addVetoableChangeListener, computeVisibleRect, contains, createToolTip, disable, enable, firePropertyChange, firePropertyChange, firePropertyChange, fireVetoableChange, getActionForKeyStroke, getActionMap, getAlignmentX, getAlignmentY, getAncestorListeners, getAutoscrolls, getBaseline, getBaselineResizeBehavior, getBorder, getBounds, getClientProperty, getComponentGraphics, getComponentPopupMenu, getConditionForKeyStroke, getDebugGraphicsOptions, getDefaultLocale, getFontMetrics, getGraphics, getHeight, getInheritsPopupMenu, getInputMap, getInputMap, getInputVerifier, getInsets, getInsets, getListeners, getLocation, getMaximumSize, getMinimumSize, getNextFocusableComponent, getPopupLocation, getPreferredSize, getRegisteredKeyStrokes, getRootPane, getSize, getToolTipLocation, getToolTipText, getToolTipText, getTopLevelAncestor, getTransferHandler, getVerifyInputWhenFocusTarget, getVetoableChangeListeners, getVisibleRect, getWidth, getX, getY, grabFocus, hide, isDoubleBuffered, isLightweightComponent, isManagingFocus, isOpaque, isOptimizedDrawingEnabled, isPaintingForPrint, isPaintingOrigin, isPaintingTile, isRequestFocusEnabled, isValidateRoot, paint, paintBorder, paintChildren, paintComponent, paintImmediately, paintImmediately, print, printAll, printBorder, printChildren, printComponent, processComponentKeyEvent, processMouseEvent, processMouseMotionEvent, putClientProperty, registerKeyboardAction, registerKeyboardAction, removeAncestorListener, removeNotify, removeVetoableChangeListener, repaint, repaint, requestDefaultFocus, requestFocus, requestFocus, requestFocusInWindow, requestFocusInWindow, resetKeyboardActions, reshape, revalidate, scrollRectToVisible, setActionMap, setAlignmentX, setAlignmentY, setAutoscrolls, setBackground, setBorder, setComponentPopupMenu, setDebugGraphicsOptions, setDefaultLocale, setDoubleBuffered, setFocusTraversalKeys, setFont, setForeground, setInheritsPopupMenu, setInputMap, setInputVerifier, setMaximumSize, setMinimumSize, setNextFocusableComponent, setOpaque, setPreferredSize, setRequestFocusEnabled, setToolTipText, setTransferHandler, setUI, setVerifyInputWhenFocusTarget, setVisible, unregisterKeyboardAction, update`

### Methods inherited from class java.awt.Container

`add, add, add, add, add, addContainerListener, addImpl, addPropertyChangeListener, addPropertyChangeListener, applyComponentOrientation, areFocusTraversalKeysSet, countComponents, deliverEvent, doLayout, findComponentAt, findComponentAt, getComponent, getComponentAt, getComponentAt, getComponentCount, getComponents, getComponentZOrder, getContainerListeners, getFocusTraversalKeys, getFocusTraversalPolicy, getLayout, getMousePosition, insets, invalidate, isAncestorOf, isFocusCycleRoot, isFocusCycleRoot, isFocusTraversalPolicyProvider, isFocusTraversalPolicySet, layout, list, list, locate, minimumSize, paintComponents, preferredSize, printComponents, processContainerEvent, processEvent, remove, remove, removeAll, removeContainerListener, setComponentZOrder, setFocusCycleRoot, setFocusTraversalPolicy, setFocusTraversalPolicyProvider, setLayout, transferFocusDownCycle, validate, validateTree`

### Methods inherited from class java.awt.Component

`action, add, addComponentListener, addFocusListener, addHierarchyBoundsListener, addHierarchyListener, addInputMethodListener, addKeyListener, addMouseListener, addMouseMotionListener, addMouseWheelListener, bounds, checkImage, checkImage, coalesceEvents, contains, createImage, createImage, createVolatileImage, createVolatileImage, disableEvents, dispatchEvent, enable, enableEvents, enableInputMethods, firePropertyChange, firePropertyChange, firePropertyChange, firePropertyChange, firePropertyChange, firePropertyChange, getBackground, getBounds, getColorModel, getComponentListeners, getComponentOrientation, getCursor, getDropTarget, getFocusCycleRootAncestor, getFocusListeners, getFocusTraversalKeysEnabled, getFont, getForeground, getGraphicsConfiguration, getHierarchyBoundsListeners, getHierarchyListeners, getIgnoreRepaint, getInputContext, getInputMethodListeners, getInputMethodRequests, getKeyListeners, getLocale, getLocation, getLocationOnScreen, getMouseListeners, getMouseMotionListeners, getMousePosition, getMouseWheelListeners, getName, getParent, getPropertyChangeListeners, getPropertyChangeListeners, getSize, getToolkit, getTreeLock, gotFocus, handleEvent, hasFocus, imageUpdate, inside, isBackgroundSet, isCursorSet, isDisplayable, isEnabled, isFocusable, isFocusOwner, isFocusTraversable, isFontSet, isForegroundSet, isLightweight, isMaximumSizeSet, isMinimumSizeSet, isPreferredSizeSet, isShowing, isValid, isVisible, keyDown, keyUp, list, list, list, location, lostFocus, mouseDown, mouseDrag, mouseEnter, mouseExit, mouseMove, mouseUp, move, nextFocus, paintAll, postEvent, prepareImage, prepareImage, processComponentEvent, processFocusEvent, processHierarchyBoundsEvent, processHierarchyEvent, processInputMethodEvent, processMouseWheelEvent, remove, removeComponentListener, removeFocusListener, removeHierarchyBoundsListener, removeHierarchyListener, removeInputMethodListener, removeKeyListener, removeMouseListener, removeMouseMotionListener, removeMouseWheelListener, removePropertyChangeListener, removePropertyChangeListener, repaint, repaint, repaint, requestFocus, requestFocus, requestFocusInWindow, resize, resize, setBounds, setBounds, setComponentOrientation, setCursor, setDropTarget, setFocusable, setFocusTraversalKeysEnabled, setIgnoreRepaint, setLocale, setLocation, setLocation, setMixingCutoutShape, setName, setSize, setSize, show, show, size, toString, transferFocus, transferFocusBackward, transferFocusUpCycle`

### Methods inherited from class java.lang.Object

`clone, equals, finalize, getClass, hashCode, notify, notifyAll, wait, wait, wait`

## Constructor Details

### FixedJComboBox

public FixedJComboBox()
Creates a JComboBox with a default data model.

### FixedJComboBox

public FixedJComboBox(ComboBoxModel<E> model)
Creates a JComboBox that takes its items from an existing ComboBoxModel.

Parameters:
  `model` - model

### FixedJComboBox

public FixedJComboBox(E[] items)
Creates a JComboBox that contains the elements in the specified array.

Parameters:
  `items` - array populating default combo box model

## Method Details

### contentsChanged

public void contentsChanged(ListDataEvent evt)
Overriding this method is questionable, since the OpenJDK javadoc says: "This method is public as an implementation side effect. do not call or override.". So there could be problems. But if the method exists at all to be overridden, this implementation shouldn't change its behaviour in a bad way, and if it doesn't exist it shouldn't have any effect, so I don't *expect* runtime problems with either OpenJDK or other JDKs.

However if an attempt is made to compile this class against a JDK in which JComboBox lacks the `contentsChanged` method, there will be trouble. It could be averted by removing the `@Override` annotation, and invoking the superclass method using reflection.

Specified by:
  `contentsChanged` in interface `ListDataListener`
Overrides:
  `contentsChanged` in class `JComboBox<E>`

### setModel

public void setModel(ComboBoxModel<E> model)

Overrides:
  `setModel` in class `JComboBox<E>`

### removeAllItems

public void removeAllItems()

Overrides:
  `removeAllItems` in class `JComboBox<E>`

### selectedItemChanged

protected void selectedItemChanged()

Overrides:
  `selectedItemChanged` in class `JComboBox<E>`