This is an automated email from the ASF dual-hosted git repository. asf-gitbox-commits pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/cayenne.git
commit 7f4305129f49c83b87885bf3cb777ff2bc71a913 Author: Andrus Adamchik <[email protected]> AuthorDate: Thu May 7 09:55:17 2026 -0400 CAY-2934 Modeler: dropdown data trunction --- RELEASE-NOTES.txt | 1 + .../modeler/toolkit/combobox/AutoCompletion.java | 42 +++++++-------- .../modeler/toolkit/combobox/SuggestionList.java | 21 ++++++-- .../toolkit/table/CMComboBoxCellEditor.java | 60 ++++++++++++++++++++++ 4 files changed, 100 insertions(+), 24 deletions(-) diff --git a/RELEASE-NOTES.txt b/RELEASE-NOTES.txt index 02da2510d..7e7984591 100644 --- a/RELEASE-NOTES.txt +++ b/RELEASE-NOTES.txt @@ -63,6 +63,7 @@ CAY-2919 Exception in "Generate Database Schema" CAY-2921 Modeler: consistent duplicate name handling CAY-2926 Modeler: renaming an ObjEntity adds it to cgen excludeEntities CAY-2931 Modeler: default encoding detected as UTF8, should be UTF-8 +CAY-2934 Modeler: dropdown data trunction ---------------------------------- Release: 5.0-M1 diff --git a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/toolkit/combobox/AutoCompletion.java b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/toolkit/combobox/AutoCompletion.java index b436be59e..e433b646c 100644 --- a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/toolkit/combobox/AutoCompletion.java +++ b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/toolkit/combobox/AutoCompletion.java @@ -40,24 +40,24 @@ public class AutoCompletion implements FocusListener, KeyListener, Runnable { * Property to mark combobox as 'auto-completing' */ public static final String AUTOCOMPLETION_PROPERTY = "JComboBox.autoCompletion"; - + /** * A list with matching items */ - private final SuggestionList suggestionList; + private final SuggestionList suggestionList; /** * Combo with auto-completion */ private final JComboBox comboBox; - + private final JTextComponent textEditor; - + private final boolean allowsUserValues; - - protected AutoCompletion(final JComboBox comboBox, boolean strict, boolean allowsUserValues, Supplier<MappingNamespace> namespaceSupplier) { + + protected AutoCompletion(JComboBox comboBox, boolean strict, boolean allowsUserValues, Supplier<MappingNamespace> namespaceSupplier) { this.comboBox = comboBox; - textEditor = ((JTextComponent)comboBox.getEditor().getEditorComponent()); + textEditor = ((JTextComponent) comboBox.getEditor().getEditorComponent()); this.allowsUserValues = allowsUserValues; suggestionList = new SuggestionList(comboBox, strict, namespaceSupplier); @@ -68,9 +68,9 @@ public class AutoCompletion implements FocusListener, KeyListener, Runnable { /** * Enables auto-completion for specified combobox * - * @param comboBox Combo to be featured - * @param strict Whether strict matching (check 'startWith' or 'contains') should be used - * @param allowsUserValues Whether non-present items are allowed + * @param comboBox Combo to be featured + * @param strict Whether strict matching (check 'startWith' or 'contains') should be used + * @param allowsUserValues Whether non-present items are allowed * @param namespaceSupplier Supplies the {@link MappingNamespace} (typically the currently selected DataMap) * used to render entity labels. */ @@ -98,7 +98,8 @@ public class AutoCompletion implements FocusListener, KeyListener, Runnable { enable(comboBox, true, false, namespaceSupplier); } - public void focusGained(FocusEvent e) {} + public void focusGained(FocusEvent e) { + } public void focusLost(FocusEvent e) { suggestionList.hide(); @@ -119,7 +120,8 @@ public class AutoCompletion implements FocusListener, KeyListener, Runnable { } } - public void keyTyped(KeyEvent e) {} + public void keyTyped(KeyEvent e) { + } public void run() { String text = textEditor.getText(); @@ -136,7 +138,7 @@ public class AutoCompletion implements FocusListener, KeyListener, Runnable { } } } - + /** * Calculates next selection row, according to a pressed key and selects it. * This might affect either suggestion list or original popup @@ -154,7 +156,7 @@ public class AutoCompletion implements FocusListener, KeyListener, Runnable { suggestionListScrolling(); } - private void processKeyPressedWhenSuggestionListIsInvisible(KeyEvent e){ + private void processKeyPressedWhenSuggestionListIsInvisible(KeyEvent e) { int sel = comboBox.getSelectedIndex(); int max = comboBox.getItemCount() - 1; @@ -189,10 +191,10 @@ public class AutoCompletion implements FocusListener, KeyListener, Runnable { return; } e.consume(); - handleNavigationKeys(false,next,sel,max); + handleNavigationKeys(false, next, sel, max); } - private void processKeyPressedWhenSuggestionListIsVisible(KeyEvent e){ + private void processKeyPressedWhenSuggestionListIsVisible(KeyEvent e) { int sel = suggestionList.getSelectedIndex(); int max = suggestionList.getItemCount() - 1; int next; @@ -229,10 +231,10 @@ public class AutoCompletion implements FocusListener, KeyListener, Runnable { return; } e.consume(); - handleNavigationKeys(true,next,sel,max); + handleNavigationKeys(true, next, sel, max); } - private void processEnterPressed(){ + private void processEnterPressed() { Object value = suggestionList.getSelectedValue(); if (!allowsUserValues && value == null && suggestionList.getItemCount() > 0) { value = suggestionList.getItemAt(0); @@ -244,7 +246,7 @@ public class AutoCompletion implements FocusListener, KeyListener, Runnable { suggestionList.hide(); } - private void handleNavigationKeys(boolean suggest, int next, int sel, int max){ + private void handleNavigationKeys(boolean suggest, int next, int sel, int max) { if (!suggest && !comboBox.isPopupVisible()) { comboBox.setPopupVisible(true); return; @@ -271,7 +273,7 @@ public class AutoCompletion implements FocusListener, KeyListener, Runnable { } } - private void suggestionListScrolling(){ + private void suggestionListScrolling() { JList list = suggestionList.getList(); int selectedIndex = suggestionList.getSelectedIndex(); list.ensureIndexIsVisible(selectedIndex); diff --git a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/toolkit/combobox/SuggestionList.java b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/toolkit/combobox/SuggestionList.java index 5cb499922..d78f6c42a 100644 --- a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/toolkit/combobox/SuggestionList.java +++ b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/toolkit/combobox/SuggestionList.java @@ -31,11 +31,11 @@ import java.awt.event.MouseListener; import java.util.function.Supplier; /** - * SuggestionList is a combo-popup displaying all items matching for - * autocompletion. - * + * SuggestionList is a combo-popup displaying all items matching for autocompletion. */ -public class SuggestionList extends BasicComboPopup { +class SuggestionList extends BasicComboPopup { + + private static final int MAX_POPUP_WIDTH = 450; /** * 'Strict' matching, i.e. whether 'startWith' or 'contains' function @@ -96,6 +96,19 @@ public class SuggestionList extends BasicComboPopup { return super.getPopupHeightForRowCount(Math.min(maxRowCount, list.getModel().getSize())); } + /** + * Expands the popup width to fit the widest filtered item. + * Called by BasicComboPopup.show() before the scroll pane is constrained, + * so getScrollPane().getPreferredSize() still reflects natural content width. + */ + @Override + protected Rectangle computePopupBounds(int px, int py, int pw, int ph) { + Rectangle bounds = super.computePopupBounds(px, py, pw, ph); + int naturalWidth = scroller.getPreferredSize().width; + int targetWidth = Math.min(Math.max(naturalWidth, pw), MAX_POPUP_WIDTH); + return new Rectangle(bounds.x, bounds.y, targetWidth, bounds.height); + } + /** * @return selected index in popup */ diff --git a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/toolkit/table/CMComboBoxCellEditor.java b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/toolkit/table/CMComboBoxCellEditor.java index de331f335..219bc47d3 100644 --- a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/toolkit/table/CMComboBoxCellEditor.java +++ b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/toolkit/table/CMComboBoxCellEditor.java @@ -23,9 +23,15 @@ import org.apache.cayenne.modeler.toolkit.combobox.AutoCompletion; import javax.swing.AbstractCellEditor; import javax.swing.JComboBox; +import javax.swing.JPopupMenu; +import javax.swing.JScrollPane; import javax.swing.JTable; +import javax.swing.event.PopupMenuEvent; +import javax.swing.event.PopupMenuListener; import javax.swing.table.TableCellEditor; import java.awt.Component; +import java.awt.Container; +import java.awt.Dimension; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.MouseEvent; @@ -45,6 +51,8 @@ public class CMComboBoxCellEditor extends AbstractCellEditor // is read by Swing's combo UI to keep the popup behavior table-friendly. static final String IS_TABLE_CELL_EDITOR_PROPERTY = "JComboBox.isTableCellEditor"; + private static final int MAX_POPUP_WIDTH = 450; + private final JComboBox<?> comboBox; private final boolean autocomplete; @@ -57,6 +65,15 @@ public class CMComboBoxCellEditor extends AbstractCellEditor comboBox.putClientProperty(IS_TABLE_CELL_EDITOR_PROPERTY, Boolean.TRUE); comboBox.addActionListener(this); } + + comboBox.addPopupMenuListener(new PopupMenuListener() { + @Override + public void popupMenuWillBecomeVisible(PopupMenuEvent e) { + adjustPopupWidth(); + } + @Override public void popupMenuWillBecomeInvisible(PopupMenuEvent e) {} + @Override public void popupMenuCanceled(PopupMenuEvent e) {} + }); } @Override @@ -71,6 +88,49 @@ public class CMComboBoxCellEditor extends AbstractCellEditor return comboBox; } + private void adjustPopupWidth() { + Object child = comboBox.getUI().getAccessibleChild(comboBox, 0); + if (!(child instanceof JPopupMenu)) { + return; + } + JPopupMenu popup = (JPopupMenu) child; + JScrollPane scrollPane = findScrollPane(popup); + if (scrollPane == null) { + return; + } + + // BasicComboPopup.show() constrains the scroll pane's preferredSize and maximumSize + // to the column width before firing this listener. Reset them so the scroll pane + // reports its natural content-based width — which already incorporates item metrics, + // list insets, scrollbar width, and scroll pane borders — no manual overhead needed. + scrollPane.setPreferredSize(null); + scrollPane.setMaximumSize(null); + popup.setPreferredSize(null); + + int naturalWidth = scrollPane.getPreferredSize().width; + int targetWidth = Math.min(Math.max(naturalWidth, comboBox.getWidth()), MAX_POPUP_WIDTH); + + Dimension scrollSize = new Dimension(targetWidth, scrollPane.getPreferredSize().height); + scrollPane.setPreferredSize(scrollSize); + scrollPane.setMaximumSize(scrollSize); + popup.setPreferredSize(new Dimension(targetWidth, popup.getPreferredSize().height)); + } + + private static JScrollPane findScrollPane(Container container) { + for (Component c : container.getComponents()) { + if (c instanceof JScrollPane) { + return (JScrollPane) c; + } + if (c instanceof Container) { + JScrollPane found = findScrollPane((Container) c); + if (found != null) { + return found; + } + } + } + return null; + } + @Override public boolean stopCellEditing() { if (autocomplete && comboBox.isEditable()) {
