diff --git a/src/main/java/org/codinjutsu/tools/jenkins/view/extension/JobParameterRenderers.java b/src/main/java/org/codinjutsu/tools/jenkins/view/extension/JobParameterRenderers.java index 624cf699..e7098593 100644 --- a/src/main/java/org/codinjutsu/tools/jenkins/view/extension/JobParameterRenderers.java +++ b/src/main/java/org/codinjutsu/tools/jenkins/view/extension/JobParameterRenderers.java @@ -17,6 +17,7 @@ import org.codinjutsu.tools.jenkins.model.JobParameter; import org.codinjutsu.tools.jenkins.model.JobParameterType; import org.codinjutsu.tools.jenkins.model.ProjectJob; +import org.codinjutsu.tools.jenkins.view.inputfilter.InputFilterList; import org.codinjutsu.tools.jenkins.view.parameter.JobParameterComponent; import org.codinjutsu.tools.jenkins.view.parameter.PasswordComponent; import org.jetbrains.annotations.NotNull; @@ -24,6 +25,7 @@ import javax.swing.*; import javax.swing.text.JTextComponent; +import java.util.List; import java.util.function.BiFunction; import java.util.function.Function; @@ -32,6 +34,7 @@ public final class JobParameterRenderers { public static final Icon ERROR_ICON = AllIcons.General.BalloonError; public static final String MISSING_NAME_LABEL = ""; + public static final int NUMBER_OF_REQUIRING_INPUT_FILTER_LIST = 10; @NotNull public static JobParameterComponent createFileUpload(JobParameter jobParameter, String defaultValue) { @@ -84,6 +87,18 @@ public static JobParameterComponent createCheckBox(JobParameter jobParam return new JobParameterComponent<>(jobParameter, checkBox, asString(JCheckBox::isSelected)); } + @NotNull + public static JobParameterComponent createInputFilterList(@NotNull JobParameter jobParameter, String defaultValue) { + final String[] choices = jobParameter.getChoices().toArray(new String[0]); + final InputFilterList list = new InputFilterList(defaultValue, List.of(choices), + (text, textToFilter) -> { + if (textToFilter.isEmpty()) return true; + return text.toLowerCase().contains(textToFilter.toLowerCase()); + }); + return new JobParameterComponent<>(jobParameter, list, InputFilterList::getSelectedItem, + () -> defaultValue != null && list.getSelectedItem() == null); + } + @NotNull public static JobParameterComponent createComboBox(@NotNull JobParameter jobParameter, String defaultValue) { final String[] choices = jobParameter.getChoices().toArray(new String[0]); @@ -143,6 +158,34 @@ public static JobParameterComponent createComboBoxIfChoicesExists(@NotNu return renderer.apply(jobParameter, defaultValue); } + @NotNull + public static JobParameterComponent createComboBoxOrInputFilterListIfChoicesExists(@NotNull JobParameter jobParameter, + String defaultValue) { + final BiFunction> renderer; + List choices = jobParameter.getChoices(); + if (choices.isEmpty()) { + renderer = JobParameterRenderers::createTextField; + } else if (choices.size() <= NUMBER_OF_REQUIRING_INPUT_FILTER_LIST) { + renderer = JobParameterRenderers::createComboBox; + } else { + renderer = JobParameterRenderers::createInputFilterList; + } + return renderer.apply(jobParameter, defaultValue); + } + + @NotNull + public static JobParameterComponent createInputFilterListIfChoicesExists(@NotNull JobParameter jobParameter, + String defaultValue) { + final BiFunction> renderer; + List choices = jobParameter.getChoices(); + if (choices.isEmpty()) { + renderer = JobParameterRenderers::createTextField; + } else { + renderer = JobParameterRenderers::createInputFilterList; + } + return renderer.apply(jobParameter, defaultValue); + } + @NotNull public static Function> createGitParameterChoices( @NotNull ProjectJob projectJob) { @@ -162,9 +205,9 @@ public static JobParameterComponent createGitParameterChoices(@NotNull P .defaultValue(jobParameter.getDefaultValue()) .choices(requestManager.getGitParameterChoices(projectJob.getJob(), jobParameter)) .build(); - return createComboBoxIfChoicesExists(gitParameter, defaultValue); + return createInputFilterListIfChoicesExists(gitParameter, defaultValue); } else { - return createComboBoxIfChoicesExists(jobParameter, defaultValue); + return createInputFilterListIfChoicesExists(jobParameter, defaultValue); } } diff --git a/src/main/java/org/codinjutsu/tools/jenkins/view/inputfilter/HintTextField.java b/src/main/java/org/codinjutsu/tools/jenkins/view/inputfilter/HintTextField.java new file mode 100644 index 00000000..549aa397 --- /dev/null +++ b/src/main/java/org/codinjutsu/tools/jenkins/view/inputfilter/HintTextField.java @@ -0,0 +1,64 @@ +package org.codinjutsu.tools.jenkins.view.inputfilter; + +import com.intellij.ui.JBColor; + +import javax.swing.*; +import java.awt.*; +import java.awt.event.FocusEvent; +import java.awt.event.FocusListener; + +class HintTextField extends JTextField implements FocusListener { + + private final String hint; + private final JBColor hintColor = JBColor.gray; + + private Color originalColor; + private boolean showingHint; + + public HintTextField(final String hint) { + super(hint); + this.hint = hint; + this.showingHint = true; + this.originalColor = this.getForeground(); + super.setForeground(hintColor); + super.addFocusListener(this); + } + + @Override + public void setForeground(Color fg) { + originalColor = fg; + super.setForeground(fg); + } + + @Override + public void setText(String t) { + super.setText(t); + if (!t.isEmpty()) { + super.setForeground(originalColor); + showingHint = false; + } + } + + @Override + public void focusGained(FocusEvent e) { + if (this.getText().isEmpty()) { + super.setForeground(originalColor); + super.setText(""); + showingHint = false; + } + } + + @Override + public void focusLost(FocusEvent e) { + if (this.getText().isEmpty()) { + super.setForeground(hintColor); + super.setText(hint); + showingHint = true; + } + } + + @Override + public String getText() { + return showingHint ? "" : super.getText(); + } +} \ No newline at end of file diff --git a/src/main/java/org/codinjutsu/tools/jenkins/view/inputfilter/InputFilterList.java b/src/main/java/org/codinjutsu/tools/jenkins/view/inputfilter/InputFilterList.java new file mode 100644 index 00000000..2561f5a7 --- /dev/null +++ b/src/main/java/org/codinjutsu/tools/jenkins/view/inputfilter/InputFilterList.java @@ -0,0 +1,138 @@ +package org.codinjutsu.tools.jenkins.view.inputfilter; + +import com.intellij.ui.DocumentAdapter; +import com.intellij.ui.JBColor; +import com.intellij.ui.components.JBList; +import com.intellij.ui.components.JBScrollPane; +import com.intellij.ui.components.panels.HorizontalLayout; +import com.intellij.util.ui.StatusText; +import org.jetbrains.annotations.NotNull; + +import javax.swing.*; +import javax.swing.event.DocumentEvent; +import java.awt.*; +import java.awt.event.KeyAdapter; +import java.awt.event.KeyEvent; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.function.BiPredicate; + +import static javax.swing.SwingConstants.TOP; + +public class InputFilterList extends JComponent { + + private final JTextField filterTextField = new HintTextField("Search"); + private final BiPredicate userFilter; + private final DefaultListModel model = new DefaultListModel<>(); + private final JBList list = new JBList<>(model); + private final JBScrollPane scrollPane = new JBScrollPane(this.list); + + private final List itemList; + private final String defaultValue; + + private String filteringText = ""; + + public InputFilterList(String defaultValue, List itemList, BiPredicate userFilter) { + this.itemList = itemList; + this.userFilter = userFilter; + this.defaultValue = defaultValue; + + setLayout(new HorizontalLayout(0, TOP)); + + model.addAll(itemList); + list.setSelectedValue(defaultValue, true); + + initDefaultEmptyTextIfNeed(); + initUIPreferredSize(); + initDocumentListener(); + initKeyListener(); + } + + public String getSelectedItem() { + String currentSelectedValue = list.getSelectedValue(); + return currentSelectedValue != null ? currentSelectedValue : defaultValue; + } + + + private void initDefaultEmptyTextIfNeed() { + if (defaultValue != null) { + StatusText emptyText = list.getEmptyText(); + emptyText.setShowAboveCenter(false); + emptyText.setText("No result"); + emptyText.appendLine(String.format("(Default value : %s)", defaultValue)); + } + } + + private void initUIPreferredSize() { + scrollPane.setPreferredSize(new Dimension(250, 100)); + add(scrollPane); + + filterTextField.setPreferredSize(new Dimension(80, filterTextField.getPreferredSize().height)); + add(filterTextField); + } + + private void initKeyListener() { + filterTextField.addKeyListener(new KeyAdapter() { + @Override + public void keyPressed(KeyEvent e) { + if (e.getKeyCode() == KeyEvent.VK_UP || e.getKeyCode() == KeyEvent.VK_KP_UP) { + moveSelectedIndex(true); + e.consume(); + } else if (e.getKeyCode() == KeyEvent.VK_DOWN || e.getKeyCode() == KeyEvent.VK_KP_DOWN) { + moveSelectedIndex(false); + e.consume(); + } + } + }); + } + + private void moveSelectedIndex(boolean isUp) { + int currentIndex = list.getSelectedIndex(); + if (currentIndex == -1) return; + if (isUp && currentIndex == 0) return; + if (!isUp && currentIndex == model.size() - 1) return; + list.setSelectedIndex(isUp ? currentIndex - 1 : currentIndex + 1); + list.ensureIndexIsVisible(isUp ? currentIndex - 1 : currentIndex + 1); + } + + private void initDocumentListener() { + filterTextField.getDocument().addDocumentListener(new DocumentAdapter() { + + @Override + protected void textChanged(@NotNull DocumentEvent e) { + SwingUtilities.invokeLater(() -> { + applyFilter(); + filteringText = filterTextField.getText(); + }); + } + }); + } + + private void applyFilter() { + if (Objects.equals(filterTextField.getText(), filteringText)) { + return; + } + model.removeAllElements(); + + final ArrayList filteredList = new ArrayList<>(); + boolean hasItem = false; + for (String item : itemList) { + if (userFilter.test(item, filterTextField.getText())) { + hasItem = true; + filteredList.add(item); + } + } + + model.addAll(filteredList); + if (hasItem) { + list.setSelectedIndex(0); + list.ensureIndexIsVisible(0); + } else { + list.clearSelection(); + } + filterTextField + .setForeground(!hasItem ? JBColor.RED : UIManager.getColor("Label.foreground")); + } + +} diff --git a/src/main/java/org/codinjutsu/tools/jenkins/view/parameter/BuiltInJobParameterRenderer.java b/src/main/java/org/codinjutsu/tools/jenkins/view/parameter/BuiltInJobParameterRenderer.java index c5f15646..ddcbb12d 100644 --- a/src/main/java/org/codinjutsu/tools/jenkins/view/parameter/BuiltInJobParameterRenderer.java +++ b/src/main/java/org/codinjutsu/tools/jenkins/view/parameter/BuiltInJobParameterRenderer.java @@ -16,12 +16,14 @@ import java.util.Optional; import java.util.function.BiFunction; +import static org.codinjutsu.tools.jenkins.view.extension.JobParameterRenderers.NUMBER_OF_REQUIRING_INPUT_FILTER_LIST; + public class BuiltInJobParameterRenderer implements JobParameterRenderer { private final Map>> converter = new HashMap<>(); public BuiltInJobParameterRenderer() { - converter.put(BuildInJobParameter.ChoiceParameterDefinition, JobParameterRenderers::createComboBox); + converter.put(BuildInJobParameter.ChoiceParameterDefinition, JobParameterRenderers::createComboBoxOrInputFilterListIfChoicesExists); converter.put(BuildInJobParameter.BooleanParameterDefinition, JobParameterRenderers::createCheckBox); converter.put(BuildInJobParameter.StringParameterDefinition, JobParameterRenderers::createTextField); converter.put(BuildInJobParameter.PasswordParameterDefinition, JobParameterRenderers::createPasswordField); @@ -47,7 +49,13 @@ public boolean isForJobParameter(@NotNull JobParameter jobParameter) { public Optional createLabel(@NotNull JobParameter jobParameter) { final JobParameterType jobParameterType = jobParameter.getJobParameterType(); final Optional label = JobParameterRenderer.super.createLabel(jobParameter); - if (BuildInJobParameter.TextParameterDefinition.equals(jobParameterType)) { + boolean isTypeForInputTextFilter = + BuildInJobParameter.TextParameterDefinition.equals(jobParameterType) + || BuildInJobParameter.ChoiceParameterDefinition.equals(jobParameterType); + + boolean isRequiringCountForInputTextFilter = + jobParameter.getChoices().size() > NUMBER_OF_REQUIRING_INPUT_FILTER_LIST; + if (isTypeForInputTextFilter && isRequiringCountForInputTextFilter) { label.ifPresent(textAreaLabel -> textAreaLabel.setVerticalAlignment(SwingConstants.TOP)); } return label; diff --git a/src/main/java/org/codinjutsu/tools/jenkins/view/parameter/GitParameterRenderer.java b/src/main/java/org/codinjutsu/tools/jenkins/view/parameter/GitParameterRenderer.java index adfbd6e0..f6659d7f 100644 --- a/src/main/java/org/codinjutsu/tools/jenkins/view/parameter/GitParameterRenderer.java +++ b/src/main/java/org/codinjutsu/tools/jenkins/view/parameter/GitParameterRenderer.java @@ -9,7 +9,10 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import javax.annotation.Nonnull; +import javax.swing.*; import java.util.HashSet; +import java.util.Optional; import java.util.Set; public class GitParameterRenderer implements JobParameterRenderer { @@ -51,12 +54,20 @@ public JobParameterComponent render(@NotNull JobParameter jobParameter, return JobParameterRenderers.createErrorLabel(jobParameter); } if (projectJob == null) { - return JobParameterRenderers.createComboBoxIfChoicesExists(jobParameter, jobParameter.getDefaultValue()); + return JobParameterRenderers.createInputFilterListIfChoicesExists(jobParameter, jobParameter.getDefaultValue()); } else { return JobParameterRenderers.createGitParameterChoices(projectJob).apply(jobParameter); } } + @Nonnull + @Override + public Optional createLabel(@NotNull JobParameter jobParameter) { + final Optional label = JobParameterRenderer.super.createLabel(jobParameter); + label.ifPresent(textAreaLabel -> textAreaLabel.setVerticalAlignment(SwingConstants.TOP)); + return label; + } + @Override public boolean isForJobParameter(@NotNull JobParameter jobParameter) { return validTypes.contains(jobParameter.getJobParameterType());