From 495c58fc32ac899bd6c3bb54ccb15cc3a5333c9a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Darri=20Sigur=C3=B0sson?= Date: Mon, 6 Nov 2023 12:21:42 +0000 Subject: [PATCH 1/4] EntitySearchModel.limit() added, along with builder method and configuration value --- changelog.txt | 3 ++- .../model/DefaultEntitySearchModel.java | 26 ++++++++++++++++--- .../framework/model/EntitySearchModel.java | 20 ++++++++++++++ .../codion/framework/model/package-info.java | 1 + .../model/DefaultEntitySearchModelTest.java | 10 +++++++ 5 files changed, 55 insertions(+), 5 deletions(-) diff --git a/changelog.txt b/changelog.txt index 189b1686fc..4bbcb0a1ae 100644 --- a/changelog.txt +++ b/changelog.txt @@ -9845,4 +9845,5 @@ EntitySearchField.setSelectionProvider() replaced with setSelectionProviderFactory(), selectionProvider now dynamically created instead of having it hang around in memory, SelectionProvider.updateUI() removed EntitySearchField.SelectionProvider.selectEntities() renamed select() EntitySearchModel.SelectionProvider renamed Selector - SelectionProvider renamed Selector and moved to SelectionDialogBuilder, select() parameter now JComponent dialogOwner \ No newline at end of file + SelectionProvider renamed Selector and moved to SelectionDialogBuilder, select() parameter now JComponent dialogOwner + EntitySearchModel.limit() added, along with builder method and configuration value \ No newline at end of file diff --git a/framework/model/src/main/java/is/codion/framework/model/DefaultEntitySearchModel.java b/framework/model/src/main/java/is/codion/framework/model/DefaultEntitySearchModel.java index 8474893250..2ea2e10284 100644 --- a/framework/model/src/main/java/is/codion/framework/model/DefaultEntitySearchModel.java +++ b/framework/model/src/main/java/is/codion/framework/model/DefaultEntitySearchModel.java @@ -53,6 +53,7 @@ final class DefaultEntitySearchModel implements EntitySearchModel { private final Value wildcard = Value.value(Text.WILDCARD_CHARACTER.get(), Text.WILDCARD_CHARACTER.get()); private final Value> condition = Value.value(NULL_CONDITION, NULL_CONDITION); private final Value> stringFunction = Value.value(DEFAULT_TO_STRING, DEFAULT_TO_STRING); + private final Value limit; private final State selectionEmpty = State.state(true); private final String description; @@ -66,6 +67,7 @@ private DefaultEntitySearchModel(DefaultBuilder builder) { this.description = builder.description == null ? createDescription() : builder.description; this.singleSelection = builder.singleSelection; this.selectedEntities.addValidator(new EntityValidator()); + this.limit = Value.value(builder.limit, builder.limit); bindEventsInternal(); } @@ -109,6 +111,11 @@ public Value wildcard() { return wildcard; } + @Override + public Value limit() { + return limit; + } + @Override public Value> condition() { return condition; @@ -185,13 +192,17 @@ private Select select() { } } } + + return Select.where(createCombinedCondition(conditions)) + .limit(limit.get()) + .build(); + } + + private Condition createCombinedCondition(Collection conditions) { Condition conditionCombination = or(conditions); Condition additionalCondition = condition.get().get(); - Select.Builder selectBuilder = additionalCondition == null ? - Select.where(conditionCombination) : - Select.where(and(additionalCondition, conditionCombination)); - return selectBuilder.build(); + return additionalCondition == null ? conditionCombination : and(additionalCondition, conditionCombination); } private String prepareSearchString(String rawSearchString, SearchSettings searchSettings) { @@ -284,6 +295,7 @@ static final class DefaultBuilder implements Builder { private String description; private boolean singleSelection = false; private String separator = DEFAULT_SEPARATOR; + private int limit = LIMIT.get(); DefaultBuilder(EntityType entityType, EntityConnectionProvider connectionProvider) { this.entityType = requireNonNull(entityType); @@ -325,6 +337,12 @@ public Builder separator(String separator) { return this; } + @Override + public Builder limit(int limit) { + this.limit = limit; + return this; + } + @Override public EntitySearchModel build() { return new DefaultEntitySearchModel(this); diff --git a/framework/model/src/main/java/is/codion/framework/model/EntitySearchModel.java b/framework/model/src/main/java/is/codion/framework/model/EntitySearchModel.java index 37f29df870..af4ff9f7b5 100644 --- a/framework/model/src/main/java/is/codion/framework/model/EntitySearchModel.java +++ b/framework/model/src/main/java/is/codion/framework/model/EntitySearchModel.java @@ -3,6 +3,8 @@ */ package is.codion.framework.model; +import is.codion.common.Configuration; +import is.codion.common.property.PropertyValue; import is.codion.common.state.State; import is.codion.common.state.StateObserver; import is.codion.common.value.Value; @@ -25,6 +27,13 @@ */ public interface EntitySearchModel { + /** + * Specifies the default search result limit, that is, the maximum number of results, -1 meaning no limit
+ * Value type: Integer
+ * Default value: -1 + */ + PropertyValue LIMIT = Configuration.integerValue("is.codion.framework.model.EntitySearchModel.limit", -1); + /** * @return the type of the entity this search model is based on */ @@ -65,6 +74,11 @@ public interface EntitySearchModel { */ Value wildcard(); + /** + * @return the value controlling the search result limit + */ + Value limit(); + /** * Performs a query based on the current search configuration * @return a list containing the entities fulfilling the current condition @@ -178,6 +192,12 @@ interface Builder { */ Builder separator(String multipleItemSeparator); + /** + * @param limit the search result limit + * @return this builder + */ + Builder limit(int limit); + /** * @return a new {@link EntitySearchModel} based on this builder */ diff --git a/framework/model/src/main/java/is/codion/framework/model/package-info.java b/framework/model/src/main/java/is/codion/framework/model/package-info.java index 7a8c8ff628..5e7e3325c2 100644 --- a/framework/model/src/main/java/is/codion/framework/model/package-info.java +++ b/framework/model/src/main/java/is/codion/framework/model/package-info.java @@ -13,5 +13,6 @@ * {@link is.codion.framework.model.ForeignKeyDetailModelLink#SEARCH_BY_INSERTED_ENTITY}
* {@link is.codion.framework.model.ForeignKeyDetailModelLink#REFRESH_ON_SELECTION}
* {@link is.codion.framework.model.ForeignKeyDetailModelLink#CLEAR_FOREIGN_KEY_ON_EMPTY_SELECTION}
+ * {@link is.codion.framework.model.EntitySearchModel#LIMIT}
*/ package is.codion.framework.model; \ No newline at end of file diff --git a/framework/model/src/test/java/is/codion/framework/model/DefaultEntitySearchModelTest.java b/framework/model/src/test/java/is/codion/framework/model/DefaultEntitySearchModelTest.java index 3f94f792d4..081de2f060 100644 --- a/framework/model/src/test/java/is/codion/framework/model/DefaultEntitySearchModelTest.java +++ b/framework/model/src/test/java/is/codion/framework/model/DefaultEntitySearchModelTest.java @@ -213,6 +213,16 @@ void condition() { assertTrue(result.isEmpty()); } + @Test + void limit() { + searchModel.searchString().set("j"); + assertEquals(4, searchModel.search().size()); + searchModel.limit().set(3); + assertEquals(3, searchModel.search().size()); + searchModel.limit().set(null); + assertEquals(4, searchModel.search().size()); + } + @BeforeEach void setUp() throws Exception { searchColumns = asList(Employee.NAME, Employee.JOB); From 0c9f90601a3779db4c36d15f1b34bd7aee627197 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Darri=20Sigur=C3=B0sson?= Date: Mon, 6 Nov 2023 13:11:53 +0000 Subject: [PATCH 2/4] EntitySearchField now displays a message in case the search result limit has been reached --- changelog.txt | 3 +- .../ui/component/EntitySearchField.java | 30 +++++++++++++++++-- .../ui/component/EntitySearchField.properties | 1 + .../EntitySearchField_is_IS.properties | 1 + 4 files changed, 32 insertions(+), 3 deletions(-) diff --git a/changelog.txt b/changelog.txt index 4bbcb0a1ae..12f78c3a40 100644 --- a/changelog.txt +++ b/changelog.txt @@ -9846,4 +9846,5 @@ EntitySearchField.SelectionProvider.selectEntities() renamed select() EntitySearchModel.SelectionProvider renamed Selector SelectionProvider renamed Selector and moved to SelectionDialogBuilder, select() parameter now JComponent dialogOwner - EntitySearchModel.limit() added, along with builder method and configuration value \ No newline at end of file + EntitySearchModel.limit() added, along with builder method and configuration value + EntitySearchField now displays a message in case the search result limit has been reached \ No newline at end of file diff --git a/swing/framework-ui/src/main/java/is/codion/swing/framework/ui/component/EntitySearchField.java b/swing/framework-ui/src/main/java/is/codion/swing/framework/ui/component/EntitySearchField.java index 817a57c0b4..94c036805b 100644 --- a/swing/framework-ui/src/main/java/is/codion/swing/framework/ui/component/EntitySearchField.java +++ b/swing/framework-ui/src/main/java/is/codion/swing/framework/ui/component/EntitySearchField.java @@ -60,6 +60,7 @@ import javax.swing.KeyStroke; import javax.swing.ListSelectionModel; import javax.swing.SortOrder; +import javax.swing.SwingConstants; import javax.swing.UIManager; import java.awt.BorderLayout; import java.awt.CardLayout; @@ -88,6 +89,7 @@ import static java.awt.event.InputEvent.CTRL_DOWN_MASK; import static java.awt.event.InputEvent.SHIFT_DOWN_MASK; import static java.awt.event.KeyEvent.*; +import static java.text.MessageFormat.format; import static java.util.Collections.emptyList; import static java.util.Objects.requireNonNull; @@ -582,14 +584,19 @@ public static TableSelector tableSelector(EntitySearchModel searchModel) { private static final class DefaultListSelector implements ListSelector { + private final EntitySearchModel searchModel; private final DefaultListModel listModel = new DefaultListModel<>(); private final JList list = new JList<>(listModel); private final JScrollPane scrollPane = new JScrollPane(list); private final JPanel basePanel = new JPanel(borderLayout()); + private final JLabel resultLimitLabel = Components.label() + .horizontalAlignment(SwingConstants.RIGHT) + .build(); private final Control selectControl; private DefaultListSelector(EntitySearchModel searchModel) { - selectControl = Control.builder(new SelectCommand(requireNonNull(searchModel), list)) + this.searchModel = requireNonNull(searchModel); + selectControl = Control.builder(new SelectCommand(searchModel, list)) .name(Messages.ok()) .build(); list.setSelectionMode(searchModel.singleSelection() ? @@ -605,6 +612,9 @@ public void mouseClicked(MouseEvent e) { } }); basePanel.add(scrollPane, BorderLayout.CENTER); + basePanel.add(resultLimitLabel, BorderLayout.SOUTH); + int gap = Layouts.HORIZONTAL_VERTICAL_GAP.get(); + basePanel.setBorder(BorderFactory.createEmptyBorder(gap, gap, 0, gap)); } @Override @@ -616,6 +626,7 @@ public JList list() { public void select(JComponent dialogOwner, List entities) { requireNonNull(entities).forEach(listModel::addElement); list.scrollRectToVisible(list.getCellBounds(0, 0)); + initializeResultLimitMessage(resultLimitLabel, searchModel.limit().get(), entities.size()); Dialogs.okCancelDialog(basePanel) .owner(dialogOwner) @@ -651,14 +662,18 @@ public void perform() { private static final class DefaultTableSelector implements TableSelector { + private final EntitySearchModel searchModel; private final FilteredTable> table; private final JScrollPane scrollPane; private final JPanel searchPanel = new JPanel(borderLayout()); private final JPanel basePanel = new JPanel(borderLayout()); + private final JLabel resultLimitLabel = Components.label() + .horizontalAlignment(SwingConstants.RIGHT) + .build(); private final Control selectControl; private DefaultTableSelector(EntitySearchModel searchModel) { - requireNonNull(searchModel); + this.searchModel = requireNonNull(searchModel); SwingEntityTableModel tableModel = new SwingEntityTableModel(searchModel.entityType(), searchModel.connectionProvider()) { @Override protected Collection refreshItems() { @@ -691,6 +706,7 @@ protected Collection refreshItems() { tableModel.sortModel().setSortOrder(searchColumns.iterator().next(), SortOrder.ASCENDING); scrollPane = new JScrollPane(table); searchPanel.add(table.searchField(), BorderLayout.WEST); + searchPanel.add(resultLimitLabel, BorderLayout.CENTER); basePanel.add(scrollPane, BorderLayout.CENTER); basePanel.add(searchPanel, BorderLayout.SOUTH); int gap = Layouts.HORIZONTAL_VERTICAL_GAP.get(); @@ -708,6 +724,7 @@ public FilteredTable> table() { public void select(JComponent dialogOwner, List entities) { table.getModel().addItemsAtSorted(0, requireNonNull(entities)); table.scrollRectToVisible(table.getCellRect(0, 0, true)); + initializeResultLimitMessage(resultLimitLabel, searchModel.limit().get(), entities.size()); Dialogs.okCancelDialog(basePanel) .owner(dialogOwner) @@ -736,6 +753,15 @@ private void configureColumn(FilteredTableColumn> column) { } } + private static void initializeResultLimitMessage(JLabel label, int limit, int resultSize) { + boolean resultLimitReached = limit == resultSize; + if (resultLimitReached) { + label.setText(format(MESSAGES.getString("result_limited"), limit)); + label.setVisible(true); + } + label.setVisible(resultLimitReached); + } + private static final class SingleSelectionValue extends AbstractComponentValue { private SingleSelectionValue(EntitySearchField searchField) { diff --git a/swing/framework-ui/src/main/resources/is/codion/swing/framework/ui/component/EntitySearchField.properties b/swing/framework-ui/src/main/resources/is/codion/swing/framework/ui/component/EntitySearchField.properties index b6148b1892..9598daa09f 100644 --- a/swing/framework-ui/src/main/resources/is/codion/swing/framework/ui/component/EntitySearchField.properties +++ b/swing/framework-ui/src/main/resources/is/codion/swing/framework/ui/component/EntitySearchField.properties @@ -5,3 +5,4 @@ postfix_wildcard=Auto-postfix wildcard prefix_wildcard=Auto-prefix wildcard select_entity=Select record searching=Searching +result_limited=Result limited to {0, choice, 1#record|1<{0, number, integer} records} diff --git a/swing/framework-ui/src/main/resources/is/codion/swing/framework/ui/component/EntitySearchField_is_IS.properties b/swing/framework-ui/src/main/resources/is/codion/swing/framework/ui/component/EntitySearchField_is_IS.properties index fb3521678c..1548f80008 100644 --- a/swing/framework-ui/src/main/resources/is/codion/swing/framework/ui/component/EntitySearchField_is_IS.properties +++ b/swing/framework-ui/src/main/resources/is/codion/swing/framework/ui/component/EntitySearchField_is_IS.properties @@ -5,3 +5,4 @@ postfix_wildcard=Sj\u00E1lfkrafa algildi fyrir aftan prefix_wildcard=Sj\u00E1lfkrafa algildi fyrir framan select_entity=Veldu f\u00E6rslu searching=Leita +result_limited=Ni\u00F0urst\u00F6\u00F0ur takmarka\u00F0ar vi\u00F0 {0, choice, 1#f\u00E6rslu|1<{0, number, integer} f\u00E6rslur} From d25a4fa789ab3af62b0aea94ccfb0a36938a42c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Darri=20Sigur=C3=B0sson?= Date: Mon, 6 Nov 2023 13:16:07 +0000 Subject: [PATCH 3/4] EntitySearchField.Builder.limit() convenience method added --- changelog.txt | 3 ++- .../framework/ui/component/EntitySearchField.java | 12 ++++++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/changelog.txt b/changelog.txt index 12f78c3a40..cc5d4425ca 100644 --- a/changelog.txt +++ b/changelog.txt @@ -9847,4 +9847,5 @@ EntitySearchModel.SelectionProvider renamed Selector SelectionProvider renamed Selector and moved to SelectionDialogBuilder, select() parameter now JComponent dialogOwner EntitySearchModel.limit() added, along with builder method and configuration value - EntitySearchField now displays a message in case the search result limit has been reached \ No newline at end of file + EntitySearchField now displays a message in case the search result limit has been reached + EntitySearchField.Builder.limit() convenience method added \ No newline at end of file diff --git a/swing/framework-ui/src/main/java/is/codion/swing/framework/ui/component/EntitySearchField.java b/swing/framework-ui/src/main/java/is/codion/swing/framework/ui/component/EntitySearchField.java index 94c036805b..302a522165 100644 --- a/swing/framework-ui/src/main/java/is/codion/swing/framework/ui/component/EntitySearchField.java +++ b/swing/framework-ui/src/main/java/is/codion/swing/framework/ui/component/EntitySearchField.java @@ -313,6 +313,12 @@ public interface Builder extends ComponentBuilder selectorFactory); + + /** + * @param limit the search result limit + * @return this builder instance + */ + Builder limit(int limit); } private void bindEvents() { @@ -947,6 +953,12 @@ public Builder selectorFactory(Function selectorFac return this; } + @Override + public Builder limit(int limit) { + this.searchModel.limit().set(limit); + return this; + } + @Override protected EntitySearchField createComponent() { return new EntitySearchField(this); From 668fbf679bd58137f37280000b578cb3bda37cfa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Darri=20Sigur=C3=B0sson?= Date: Mon, 6 Nov 2023 13:28:15 +0000 Subject: [PATCH 4/4] EntitySearchField, limit added to settings panel --- changelog.txt | 3 +- .../ui/component/EntitySearchField.java | 33 +++++++++++++++---- .../ui/component/EntitySearchField.properties | 1 + .../EntitySearchField_is_IS.properties | 1 + 4 files changed, 31 insertions(+), 7 deletions(-) diff --git a/changelog.txt b/changelog.txt index cc5d4425ca..bbdc746528 100644 --- a/changelog.txt +++ b/changelog.txt @@ -9848,4 +9848,5 @@ SelectionProvider renamed Selector and moved to SelectionDialogBuilder, select() parameter now JComponent dialogOwner EntitySearchModel.limit() added, along with builder method and configuration value EntitySearchField now displays a message in case the search result limit has been reached - EntitySearchField.Builder.limit() convenience method added \ No newline at end of file + EntitySearchField.Builder.limit() convenience method added + EntitySearchField, limit added to settings panel \ No newline at end of file diff --git a/swing/framework-ui/src/main/java/is/codion/swing/framework/ui/component/EntitySearchField.java b/swing/framework-ui/src/main/java/is/codion/swing/framework/ui/component/EntitySearchField.java index 302a522165..b5ec7971a1 100644 --- a/swing/framework-ui/src/main/java/is/codion/swing/framework/ui/component/EntitySearchField.java +++ b/swing/framework-ui/src/main/java/is/codion/swing/framework/ui/component/EntitySearchField.java @@ -83,6 +83,7 @@ import static is.codion.swing.common.ui.Colors.darker; import static is.codion.swing.common.ui.Utilities.linkToEnabledState; import static is.codion.swing.common.ui.border.Borders.emptyBorder; +import static is.codion.swing.common.ui.component.Components.gridLayoutPanel; import static is.codion.swing.common.ui.component.Components.menu; import static is.codion.swing.common.ui.component.text.TextComponents.selectAllOnFocusGained; import static is.codion.swing.common.ui.layout.Layouts.borderLayout; @@ -476,9 +477,7 @@ private void initializeUI(EntitySearchModel searchModel) { setLayout(borderLayout()); setBorder(emptyBorder()); add(createSearchColumnPanel(searchModel), BorderLayout.CENTER); - if (!searchModel.singleSelection()) { - add(createSeparatorPanel(searchModel), BorderLayout.SOUTH); - } + add(createSouthPanel(searchModel), BorderLayout.SOUTH); } private static JPanel createSearchColumnPanel(EntitySearchModel searchModel) { @@ -503,17 +502,39 @@ private static JPanel createSearchColumnPanel(EntitySearchModel searchModel) { .build(); } + private static JPanel createSouthPanel(EntitySearchModel searchModel) { + PanelBuilder southPanelBuilder = gridLayoutPanel(1, 0) + .border(BorderFactory.createTitledBorder("")); + if (!searchModel.singleSelection()) { + southPanelBuilder.add(createSeparatorPanel(searchModel)); + } + else { + southPanelBuilder.add(new JLabel()); + } + southPanelBuilder.add(createLimitPanel(searchModel)); + + return southPanelBuilder.build(); + } + private static JPanel createSeparatorPanel(EntitySearchModel searchModel) { return Components.borderLayoutPanel() - .border(BorderFactory.createTitledBorder("")) - .centerComponent(new JLabel(MESSAGES.getString("multiple_item_separator"))) - .westComponent(Components.textField(searchModel.separator()) + .westComponent(new JLabel(MESSAGES.getString("multiple_item_separator"))) + .centerComponent(Components.textField(searchModel.separator()) .columns(1) .maximumLength(1) .build()) .build(); } + private static JPanel createLimitPanel(EntitySearchModel searchModel) { + return Components.borderLayoutPanel() + .westComponent(new JLabel(MESSAGES.getString("result_limit"))) + .centerComponent(Components.integerField(searchModel.limit()) + .columns(4) + .build()) + .build(); + } + private static JPanel createColumnSettingsPanel(EntitySearchModel.SearchSettings settings) { return Components.gridLayoutPanel(3, 1) .add(Components.checkBox(settings.caseSensitive()) diff --git a/swing/framework-ui/src/main/resources/is/codion/swing/framework/ui/component/EntitySearchField.properties b/swing/framework-ui/src/main/resources/is/codion/swing/framework/ui/component/EntitySearchField.properties index 9598daa09f..e4c43b630a 100644 --- a/swing/framework-ui/src/main/resources/is/codion/swing/framework/ui/component/EntitySearchField.properties +++ b/swing/framework-ui/src/main/resources/is/codion/swing/framework/ui/component/EntitySearchField.properties @@ -6,3 +6,4 @@ prefix_wildcard=Auto-prefix wildcard select_entity=Select record searching=Searching result_limited=Result limited to {0, choice, 1#record|1<{0, number, integer} records} +result_limit=Result limit diff --git a/swing/framework-ui/src/main/resources/is/codion/swing/framework/ui/component/EntitySearchField_is_IS.properties b/swing/framework-ui/src/main/resources/is/codion/swing/framework/ui/component/EntitySearchField_is_IS.properties index 1548f80008..7bfc8848d8 100644 --- a/swing/framework-ui/src/main/resources/is/codion/swing/framework/ui/component/EntitySearchField_is_IS.properties +++ b/swing/framework-ui/src/main/resources/is/codion/swing/framework/ui/component/EntitySearchField_is_IS.properties @@ -6,3 +6,4 @@ prefix_wildcard=Sj\u00E1lfkrafa algildi fyrir framan select_entity=Veldu f\u00E6rslu searching=Leita result_limited=Ni\u00F0urst\u00F6\u00F0ur takmarka\u00F0ar vi\u00F0 {0, choice, 1#f\u00E6rslu|1<{0, number, integer} f\u00E6rslur} +result_limit=Fj\u00F6ldatakm\u00F6rkun