Refactor SearchableAndCategorizableComboBox to group instead of category

This commit is contained in:
SiboVG 2024-08-09 00:05:48 +02:00
parent 117d7d79df
commit e4ca56f8e8
3 changed files with 57 additions and 57 deletions

View File

@ -4,7 +4,7 @@ import info.openrocket.core.material.MaterialGroup;
import info.openrocket.core.util.Invalidatable; import info.openrocket.core.util.Invalidatable;
import info.openrocket.swing.gui.dialogs.preferences.PreferencesDialog; import info.openrocket.swing.gui.dialogs.preferences.PreferencesDialog;
import info.openrocket.swing.gui.main.BasicFrame; import info.openrocket.swing.gui.main.BasicFrame;
import info.openrocket.swing.gui.widgets.SearchableAndCategorizableComboBox; import info.openrocket.swing.gui.widgets.SearchableAndGroupableComboBox;
import net.miginfocom.swing.MigLayout; import net.miginfocom.swing.MigLayout;
import info.openrocket.core.document.OpenRocketDocument; import info.openrocket.core.document.OpenRocketDocument;
@ -36,7 +36,7 @@ import java.util.List;
public class MaterialPanel extends JPanel implements Invalidatable, InvalidatingWidget { public class MaterialPanel extends JPanel implements Invalidatable, InvalidatingWidget {
private static final Translator trans = Application.getTranslator(); private static final Translator trans = Application.getTranslator();
private final List<Invalidatable> invalidatables = new ArrayList<>(); private final List<Invalidatable> invalidatables = new ArrayList<>();
private SearchableAndCategorizableComboBox<MaterialGroup, Material> materialCombo = null; private SearchableAndGroupableComboBox<MaterialGroup, Material> materialCombo = null;
public MaterialPanel(RocketComponent component, OpenRocketDocument document, public MaterialPanel(RocketComponent component, OpenRocketDocument document,
Material.Type type, String materialString, String finishString, Material.Type type, String materialString, String finishString,
@ -160,9 +160,9 @@ public class MaterialPanel extends JPanel implements Invalidatable, Invalidating
public static class MaterialComboBox extends JComboBox<Material> { public static class MaterialComboBox extends JComboBox<Material> {
private static final Translator trans = Application.getTranslator(); private static final Translator trans = Application.getTranslator();
public static SearchableAndCategorizableComboBox<MaterialGroup, Material> createComboBox( public static SearchableAndGroupableComboBox<MaterialGroup, Material> createComboBox(
MaterialModel mm, Component... extraCategoryWidgets) { MaterialModel mm, Component... extraCategoryWidgets) {
return new SearchableAndCategorizableComboBox<>(mm, return new SearchableAndGroupableComboBox<>(mm,
trans.get("MaterialPanel.MaterialComboBox.placeholder"), extraCategoryWidgets) { trans.get("MaterialPanel.MaterialComboBox.placeholder"), extraCategoryWidgets) {
@Override @Override
public String getDisplayString(Material item) { public String getDisplayString(Material item) {

View File

@ -1,6 +1,6 @@
package info.openrocket.swing.gui.simulation; package info.openrocket.swing.gui.simulation;
import info.openrocket.swing.gui.widgets.SearchableAndCategorizableComboBox; import info.openrocket.swing.gui.widgets.SearchableAndGroupableComboBox;
import info.openrocket.core.l10n.Translator; import info.openrocket.core.l10n.Translator;
import info.openrocket.core.simulation.FlightDataType; import info.openrocket.core.simulation.FlightDataType;
import info.openrocket.core.simulation.FlightDataTypeGroup; import info.openrocket.core.simulation.FlightDataTypeGroup;
@ -17,8 +17,8 @@ import java.util.Map;
public class FlightDataComboBox extends JComboBox<FlightDataType> { public class FlightDataComboBox extends JComboBox<FlightDataType> {
private static final Translator trans = Application.getTranslator(); private static final Translator trans = Application.getTranslator();
public static SearchableAndCategorizableComboBox<FlightDataTypeGroup, FlightDataType> createComboBox(List<FlightDataType> types) { public static SearchableAndGroupableComboBox<FlightDataTypeGroup, FlightDataType> createComboBox(List<FlightDataType> types) {
return new SearchableAndCategorizableComboBox<>(types, trans.get("FlightDataComboBox.placeholder")); return new SearchableAndGroupableComboBox<>(types, trans.get("FlightDataComboBox.placeholder"));
} }
/** /**

View File

@ -53,23 +53,23 @@ import java.util.Vector;
/** /**
* A combo box that has a search box for searching the items in the combobox. * A combo box that has a search box for searching the items in the combobox.
* If no text is entered, the combobox items are displayed in a categorized popup menu, grouped according to their groups. * If no text is entered, the combobox items are displayed in a grouped popup menu, grouped according to their groups.
* @param <G> The type of the group * @param <G> The type of the group
* @param <T> The type of the groupable items * @param <T> The type of the groupable items
* *
* @author Sibo Van Gool <sibo.vangool@hotmail.com> * @author Sibo Van Gool <sibo.vangool@hotmail.com>
*/ */
public class SearchableAndCategorizableComboBox<G extends Group, T extends Groupable<G>> extends JComboBox<T> { public class SearchableAndGroupableComboBox<G extends Group, T extends Groupable<G>> extends JComboBox<T> {
private static final String CHECKMARK = "\u2713"; private static final String CHECKMARK = "\u2713";
private static final int CHECKMARK_X_OFFSET = 5; private static final int CHECKMARK_X_OFFSET = 5;
private static final int CHECKMARK_Y_OFFSET = 5; private static final int CHECKMARK_Y_OFFSET = 5;
private String placeHolderText; private String placeHolderText;
private JPopupMenu categoryPopup; private JPopupMenu groupsPopup;
private JPopupMenu searchPopup; private JPopupMenu searchPopup;
private PlaceholderTextField searchFieldCategory; private PlaceholderTextField searchFieldGroups;
private PlaceholderTextField searchFieldSearch; private PlaceholderTextField searchFieldSearch;
private Component[] extraCategoryWidgets; private Component[] extraGroupPopupWidgets;
private JList<T> filteredList; private JList<T> filteredList;
private Map<G, List<T>> itemGroupMap; private Map<G, List<T>> itemGroupMap;
@ -84,33 +84,33 @@ public class SearchableAndCategorizableComboBox<G extends Group, T extends Group
} }
/** /**
* Create a searchable and categorizable combo box. * Create a searchable and groupable combo box.
* @param placeHolderText the placeholder text for the search field (when no text is entered) * @param placeHolderText the placeholder text for the search field (when no text is entered)
* @param extraCategoryWidgets extra widgets to add to the category popup. Each widget will be added as a separate menu item. * @param extraGroupPopupWidgets extra widgets to add to the groups popup. Each widget will be added as a separate menu item.
*/ */
public SearchableAndCategorizableComboBox(ComboBoxModel<T> model, String placeHolderText, public SearchableAndGroupableComboBox(ComboBoxModel<T> model, String placeHolderText,
Component... extraCategoryWidgets) { Component... extraGroupPopupWidgets) {
super(model != null ? model : new DefaultComboBoxModel<>()); super(model != null ? model : new DefaultComboBoxModel<>());
List<T> items = new ArrayList<>(); List<T> items = new ArrayList<>();
for (int i = 0; i < Objects.requireNonNull(model).getSize(); i++) { for (int i = 0; i < Objects.requireNonNull(model).getSize(); i++) {
items.add(model.getElementAt(i)); items.add(model.getElementAt(i));
} }
init(model, constructItemGroupMapFromList(items), placeHolderText, extraCategoryWidgets); init(model, constructItemGroupMapFromList(items), placeHolderText, extraGroupPopupWidgets);
} }
public SearchableAndCategorizableComboBox(List<T> allItems, String placeHolderText, Component... extraCategoryWidgets) { public SearchableAndGroupableComboBox(List<T> allItems, String placeHolderText, Component... extraGroupPopupWidgets) {
super(); super();
init(null, constructItemGroupMapFromList(allItems), placeHolderText, extraCategoryWidgets); init(null, constructItemGroupMapFromList(allItems), placeHolderText, extraGroupPopupWidgets);
} }
private void init(ComboBoxModel<T> model, Map<G, List<T>> itemGroupMap, String placeHolderText, Component... extraCategoryWidgets) { private void init(ComboBoxModel<T> model, Map<G, List<T>> itemGroupMap, String placeHolderText, Component... extraGroupsPopupWidgets) {
setEditable(false); setEditable(false);
initColors(); initColors();
this.extraCategoryWidgets = extraCategoryWidgets; this.extraGroupPopupWidgets = extraGroupsPopupWidgets;
this.placeHolderText = placeHolderText; this.placeHolderText = placeHolderText;
this.itemGroupMap = itemGroupMap; this.itemGroupMap = itemGroupMap;
updateItems(itemGroupMap); updateItems(itemGroupMap);
@ -123,7 +123,7 @@ public class SearchableAndCategorizableComboBox<G extends Group, T extends Group
private static void initColors() { private static void initColors() {
updateColors(); updateColors();
UITheme.Theme.addUIThemeChangeListener(SearchableAndCategorizableComboBox::updateColors); UITheme.Theme.addUIThemeChangeListener(SearchableAndGroupableComboBox::updateColors);
} }
private static void updateColors() { private static void updateColors() {
@ -170,9 +170,9 @@ public class SearchableAndCategorizableComboBox<G extends Group, T extends Group
} }
// Recreate the search fields only if they don't exist // Recreate the search fields only if they don't exist
if (this.searchFieldCategory == null) { if (this.searchFieldGroups == null) {
this.searchFieldCategory = new PlaceholderTextField(); this.searchFieldGroups = new PlaceholderTextField();
this.searchFieldCategory.setPlaceholder(this.placeHolderText); this.searchFieldGroups.setPlaceholder(this.placeHolderText);
} }
if (this.searchFieldSearch == null) { if (this.searchFieldSearch == null) {
this.searchFieldSearch = new PlaceholderTextField(); this.searchFieldSearch = new PlaceholderTextField();
@ -180,9 +180,9 @@ public class SearchableAndCategorizableComboBox<G extends Group, T extends Group
// Recreate the filtered list and popups // Recreate the filtered list and popups
this.filteredList = createFilteredList(); this.filteredList = createFilteredList();
this.categoryPopup = createCategoryPopup(); this.groupsPopup = createGroupsPopup();
this.searchPopup = createSearchPopup(); this.searchPopup = createSearchPopup();
this.searchPopup.setPreferredSize(this.categoryPopup.getPreferredSize()); this.searchPopup.setPreferredSize(this.groupsPopup.getPreferredSize());
revalidate(); revalidate();
repaint(); repaint();
@ -211,11 +211,11 @@ public class SearchableAndCategorizableComboBox<G extends Group, T extends Group
return new ArrayList<>(uniqueItems); return new ArrayList<>(uniqueItems);
} }
private JPopupMenu createCategoryPopup() { private JPopupMenu createGroupsPopup() {
final JPopupMenu menu = new JPopupMenu(); final JPopupMenu menu = new JPopupMenu();
// Add the search field at the top // Add the search field at the top
menu.add(searchFieldCategory); menu.add(searchFieldGroups);
menu.addSeparator(); // Separator between search field and menu items menu.addSeparator(); // Separator between search field and menu items
// Fill the menu with the groups // Fill the menu with the groups
@ -225,7 +225,7 @@ public class SearchableAndCategorizableComboBox<G extends Group, T extends Group
public void paintComponent(Graphics g) { public void paintComponent(Graphics g) {
super.paintComponent(g); super.paintComponent(g);
// If the group contains the selected item, draw a checkbox // If the group contains the selected item, draw a checkbox
if (containsSelectedItem(group, (T) SearchableAndCategorizableComboBox.this.getSelectedItem())) { if (containsSelectedItem(group, (T) SearchableAndGroupableComboBox.this.getSelectedItem())) {
g.drawString(CHECKMARK, CHECKMARK_X_OFFSET, getHeight() - CHECKMARK_Y_OFFSET); // Unicode for checked checkbox g.drawString(CHECKMARK, CHECKMARK_X_OFFSET, getHeight() - CHECKMARK_Y_OFFSET); // Unicode for checked checkbox
} }
} }
@ -239,7 +239,7 @@ public class SearchableAndCategorizableComboBox<G extends Group, T extends Group
public void paintComponent(Graphics g) { public void paintComponent(Graphics g) {
super.paintComponent(g); super.paintComponent(g);
// If the item is currently selected, draw a checkmark before it // If the item is currently selected, draw a checkmark before it
if (item == SearchableAndCategorizableComboBox.this.getSelectedItem()) { if (item == SearchableAndGroupableComboBox.this.getSelectedItem()) {
g.drawString(CHECKMARK + " ", CHECKMARK_X_OFFSET, getHeight() - CHECKMARK_Y_OFFSET); g.drawString(CHECKMARK + " ", CHECKMARK_X_OFFSET, getHeight() - CHECKMARK_Y_OFFSET);
} }
} }
@ -256,8 +256,8 @@ public class SearchableAndCategorizableComboBox<G extends Group, T extends Group
} }
// Extra widgets // Extra widgets
if (extraCategoryWidgets != null) { if (extraGroupPopupWidgets != null) {
for (Component widget : extraCategoryWidgets) { for (Component widget : extraGroupPopupWidgets) {
menu.add(widget); menu.add(widget);
} }
} }
@ -304,25 +304,25 @@ public class SearchableAndCategorizableComboBox<G extends Group, T extends Group
public void run() { public void run() {
T selectedItem = filteredList.getSelectedValue(); T selectedItem = filteredList.getSelectedValue();
if (selectedItem != null) { if (selectedItem != null) {
SearchableAndCategorizableComboBox.this.setSelectedItem(selectedItem); SearchableAndGroupableComboBox.this.setSelectedItem(selectedItem);
} }
} }
}); });
} }
private void hidePopups() { private void hidePopups() {
hideCategoryPopup(); hideGroupsPopup();
hideSearchPopup(); hideSearchPopup();
} }
private void showCategoryPopup() { private void showGroupsPopup() {
categoryPopup.show(this, 0, getHeight()); groupsPopup.show(this, 0, getHeight());
searchFieldSearch.setText(""); searchFieldSearch.setText("");
searchFieldCategory.setText(""); searchFieldGroups.setText("");
} }
private void hideCategoryPopup() { private void hideGroupsPopup() {
categoryPopup.setVisible(false); groupsPopup.setVisible(false);
} }
private void showSearchPopup() { private void showSearchPopup() {
@ -377,7 +377,7 @@ public class SearchableAndCategorizableComboBox<G extends Group, T extends Group
@Override @Override
public boolean isPopupVisible() { public boolean isPopupVisible() {
return categoryPopup.isVisible() || searchPopup.isVisible(); return groupsPopup.isVisible() || searchPopup.isVisible();
} }
/** /**
@ -422,14 +422,14 @@ public class SearchableAndCategorizableComboBox<G extends Group, T extends Group
addActionListener(new ActionListener() { addActionListener(new ActionListener() {
@Override @Override
public void actionPerformed(ActionEvent e) { public void actionPerformed(ActionEvent e) {
model.setSelectedItem(SearchableAndCategorizableComboBox.this.getSelectedItem()); model.setSelectedItem(SearchableAndGroupableComboBox.this.getSelectedItem());
} }
}); });
} }
private void setupSearchFieldListeners() { private void setupSearchFieldListeners() {
searchFieldCategory.addKeyListener(new SearchFieldKeyAdapter(searchFieldCategory, searchFieldSearch, true)); searchFieldGroups.addKeyListener(new SearchFieldKeyAdapter(searchFieldGroups, searchFieldSearch, true));
searchFieldSearch.addKeyListener(new SearchFieldKeyAdapter(searchFieldSearch, searchFieldCategory, false)); searchFieldSearch.addKeyListener(new SearchFieldKeyAdapter(searchFieldSearch, searchFieldGroups, false));
// Fix a bug where the first character would get selected when the search field gets focus (thus deleting it on // Fix a bug where the first character would get selected when the search field gets focus (thus deleting it on
// the next key press) // the next key press)
@ -459,7 +459,7 @@ public class SearchableAndCategorizableComboBox<G extends Group, T extends Group
@Override @Override
public void run() { public void run() {
if (!isPopupVisible()) { if (!isPopupVisible()) {
showCategoryPopup(); showGroupsPopup();
} }
} }
}); });
@ -478,7 +478,7 @@ public class SearchableAndCategorizableComboBox<G extends Group, T extends Group
@Override @Override
public void run() { public void run() {
if (!isPopupVisible()) { if (!isPopupVisible()) {
showCategoryPopup(); showGroupsPopup();
} }
} }
}); });
@ -508,12 +508,12 @@ public class SearchableAndCategorizableComboBox<G extends Group, T extends Group
private class SearchFieldKeyAdapter extends KeyAdapter { private class SearchFieldKeyAdapter extends KeyAdapter {
private final PlaceholderTextField primaryField; private final PlaceholderTextField primaryField;
private final PlaceholderTextField secondaryField; private final PlaceholderTextField secondaryField;
private final boolean isCategory; private final boolean isGroupsPopup;
SearchFieldKeyAdapter(PlaceholderTextField primary, PlaceholderTextField secondary, boolean isCategory) { SearchFieldKeyAdapter(PlaceholderTextField primary, PlaceholderTextField secondary, boolean isGroupsPopup) {
this.primaryField = primary; this.primaryField = primary;
this.secondaryField = secondary; this.secondaryField = secondary;
this.isCategory = isCategory; this.isGroupsPopup = isGroupsPopup;
} }
@Override @Override
@ -527,26 +527,26 @@ public class SearchableAndCategorizableComboBox<G extends Group, T extends Group
String text = primaryField.getText(); String text = primaryField.getText();
highlightedListIdx = 0; highlightedListIdx = 0;
secondaryField.setText(text); secondaryField.setText(text);
if (isCategory) { if (isGroupsPopup) {
handleCategorySearch(text); handleGroupsPopupSearch(text);
} else { } else {
handleGeneralSearch(text); handleSearchPopupSearch(text);
} }
filter(text); filter(text);
}); });
} }
private void handleCategorySearch(String text) { private void handleGroupsPopupSearch(String text) {
if (!text.isEmpty() && !searchPopup.isVisible()) { if (!text.isEmpty() && !searchPopup.isVisible()) {
hideCategoryPopup(); hideGroupsPopup();
showSearchPopup(); showSearchPopup();
} }
} }
private void handleGeneralSearch(String text) { private void handleSearchPopupSearch(String text) {
if (text.isEmpty() && !categoryPopup.isVisible()) { if (text.isEmpty() && !groupsPopup.isVisible()) {
hideSearchPopup(); hideSearchPopup();
showCategoryPopup(); showGroupsPopup();
} }
} }