diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ec95720 --- /dev/null +++ b/.gitignore @@ -0,0 +1,140 @@ + +# Created by https://www.toptal.com/developers/gitignore/api/intellij,java +# Edit at https://www.toptal.com/developers/gitignore?templates=intellij,java + +### Intellij ### +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 + +# User-specific stuff +.idea/**/workspace.xml +.idea/**/tasks.xml +.idea/**/usage.statistics.xml +.idea/**/dictionaries +.idea/**/shelf + +# AWS User-specific +.idea/**/aws.xml + +# Generated files +.idea/**/contentModel.xml + +# Sensitive or high-churn files +.idea/**/dataSources/ +.idea/**/dataSources.ids +.idea/**/dataSources.local.xml +.idea/**/sqlDataSources.xml +.idea/**/dynamic.xml +.idea/**/uiDesigner.xml +.idea/**/dbnavigator.xml + +# Gradle +.idea/**/gradle.xml +.idea/**/libraries + +# Gradle and Maven with auto-import +# When using Gradle or Maven with auto-import, you should exclude module files, +# since they will be recreated, and may cause churn. Uncomment if using +# auto-import. +# .idea/artifacts +# .idea/compiler.xml +# .idea/jarRepositories.xml +# .idea/modules.xml +# .idea/*.iml +# .idea/modules +# *.iml +# *.ipr + +# CMake +cmake-build-*/ + +# Mongo Explorer plugin +.idea/**/mongoSettings.xml + +# File-based project format +*.iws + +# IntelliJ +out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Cursive Clojure plugin +.idea/replstate.xml + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties + +# Editor-based Rest Client +.idea/httpRequests + +# Android studio 3.1+ serialized cache file +.idea/caches/build_file_checksums.ser + +### Intellij Patch ### +# Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721 + +# *.iml +# modules.xml +# .idea/misc.xml +# *.ipr + +# Sonarlint plugin +# https://plugins.jetbrains.com/plugin/7973-sonarlint +.idea/**/sonarlint/ + +# SonarQube Plugin +# https://plugins.jetbrains.com/plugin/7238-sonarqube-community-plugin +.idea/**/sonarIssues.xml + +# Markdown Navigator plugin +# https://plugins.jetbrains.com/plugin/7896-markdown-navigator-enhanced +.idea/**/markdown-navigator.xml +.idea/**/markdown-navigator-enh.xml +.idea/**/markdown-navigator/ + +# Cache file creation bug +# See https://youtrack.jetbrains.com/issue/JBR-2257 +.idea/$CACHE_FILE$ + +# CodeStream plugin +# https://plugins.jetbrains.com/plugin/12206-codestream +.idea/codestream.xml + +### Java ### +# Compiled class file +*.class + +# Log file +*.log + +# BlueJ files +*.ctxt + +# Mobile Tools for Java (J2ME) +.mtj.tmp/ + +# Package Files # +*.jar +*.war +*.nar +*.ear +*.zip +*.tar.gz +*.rar + +# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml +hs_err_pid* + +/target/ +.idea/ +*.iml +.DS_Store +# End of https://www.toptal.com/developers/gitignore/api/intellij,java \ No newline at end of file diff --git a/checkstyle.xml b/checkstyle.xml new file mode 100644 index 0000000..43aac6c --- /dev/null +++ b/checkstyle.xml @@ -0,0 +1,144 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..b3fcc68 --- /dev/null +++ b/pom.xml @@ -0,0 +1,286 @@ + + + 4.0.0 + + com.blazemeter + jmeter-bzm-commons + jar + 0.1 + BlazeMeter JMeter commons + This project holds common utilities used by BlazeMeter plugin projects. + https://github.com/Blazemeter/jmeter-bzm-commons + + + + The Apache License, Version 2.0 + http://www.apache.org/licenses/LICENSE-2.0.txt + + + + + + Blazemeter + ops@blazemeter.com + Blazemeter + https://blazemeter.com/ + + + + + UTF-8 + UTF-8 + + + + scm:git:git://github.com/Blazemeter/jmeter-bzm-commons.git + scm:git:ssh://github.com:Blazemeter/jmeter-bzm-commons.git + https://github.com/Blazemeter/jmeter-bzm-commons/tree/master + + + + + ossrh + https://oss.sonatype.org/content/repositories/snapshots + + + + + + org.apache.jmeter + ApacheJMeter_core + 4.0 + provided + + + org.apache.maven.plugins + maven-checkstyle-plugin + 3.1.0 + maven-plugin + + + + + + + org.jacoco + jacoco-maven-plugin + 0.8.4 + + true + + + + agent-for-ut + + prepare-agent + + + jacoco.agent.ut.arg + + + + agent-for-it + + prepare-agent-integration + + + jacoco.agent.it.arg + + + + + + org.apache.maven.plugins + maven-surefire-plugin + + + ${jacoco.agent.ut.arg} + + + + org.apache.maven.plugins + maven-failsafe-plugin + + + ${jacoco.agent.it.arg} + ${project.build.directory}/surefire-reports + + + + + integration-test + verify + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.8.1 + + 8 + 8 + + + + org.apache.maven.plugins + maven-checkstyle-plugin + 3.1.0 + + + validate + validate + + checkstyle.xml + true + false + true + + + check + + + + + + + + + + sonar + + + + + org.sonarsource.scanner.maven + sonar-maven-plugin + 3.6.0.1398 + + + + + + org.jacoco + jacoco-maven-plugin + 0.8.4 + + true + + + + agent-for-ut + + prepare-agent + + + jacoco.agent.ut.arg + + + + agent-for-it + + prepare-agent-integration + + + jacoco.agent.it.arg + + + + + + org.apache.maven.plugins + maven-surefire-plugin + + + ${jacoco.agent.ut.arg} + + + + org.apache.maven.plugins + maven-failsafe-plugin + + + ${jacoco.agent.it.arg} + ${project.build.directory}/surefire-reports + + + + + integration-test + verify + + + + + + + + + release + + + + org.apache.maven.plugins + maven-source-plugin + 3.0.1 + + + attach-sources + + jar + + + + + + org.apache.maven.plugins + maven-javadoc-plugin + 3.0.1 + + + attach-javadocs + + jar + + + + + + org.apache.maven.plugins + maven-gpg-plugin + 1.6 + + + sign-artifacts + + sign + + + + --pinentry-mode + loopback + + + + + + + org.sonatype.plugins + nexus-staging-maven-plugin + 1.6.8 + true + + ossrh + https://oss.sonatype.org/ + true + + + + + + + + \ No newline at end of file diff --git a/readme.md b/readme.md new file mode 100644 index 0000000..e8e8b2d --- /dev/null +++ b/readme.md @@ -0,0 +1,26 @@ +# BlazeMeter JMeter commons + +This repository holds common utilities used by BlazeMeter plugin projects, avoiding duplication between them. + +This is a list of the features and projects that use it: + + +| | CR | Cx | RTE | HLS | HTTP2 | +|-------------------------|----|----|-----|----- |-------| +| BlazeMeter Labs Logo | ☑ | ✅ | ☑ | ☑ | ☑ | +| Collapsible Panels | ☑ | ☑ | | || +| Fields with validation | ☑ | ✅ | | || +| Fields with placeholder | ☑ | ✅ | | || +| Themed icons | ☑ | ✅ | ☑ | ☑ | ☑ | +| ButtonBuilder | ☑ | ✅ | | || + +Legend: +* CR: Correlation Recorder +* Cx: Citrix Plugin +* RTE: RTE Plugin +* HLS: HLS Plugin +* HTTP2: HTTP2 Plugin + +☑ = Functionality used + +✅ = Functionality migrated (using this project) diff --git a/src/main/java/com/blazemeter/jmeter/commons/BlazemeterLabsLogo.java b/src/main/java/com/blazemeter/jmeter/commons/BlazemeterLabsLogo.java new file mode 100644 index 0000000..4910ac8 --- /dev/null +++ b/src/main/java/com/blazemeter/jmeter/commons/BlazemeterLabsLogo.java @@ -0,0 +1,48 @@ +package com.blazemeter.jmeter.commons; + +import java.awt.Cursor; +import java.awt.Desktop; +import java.awt.Graphics; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; +import java.io.IOException; +import java.net.URI; +import java.net.URISyntaxException; +import javax.swing.ImageIcon; +import javax.swing.JLabel; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class BlazemeterLabsLogo extends JLabel { + + private static final Logger LOG = LoggerFactory.getLogger(BlazemeterLabsLogo.class); + private static final ImageIcon BLAZEMETER_LOGO = ThemedIcon + .fromResourceName("blazemeter-labs-logo.png"); + + public BlazemeterLabsLogo(String pageURL) { + super(BLAZEMETER_LOGO); + setBrowseOnClick(pageURL); + } + + @Override + public void paint(Graphics g) { + setIcon(BLAZEMETER_LOGO); + super.paint(g); + } + + private void setBrowseOnClick(String url) { + setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)); + addMouseListener(new MouseAdapter() { + @Override + public void mouseClicked(MouseEvent mouseEvent) { + if (Desktop.isDesktopSupported()) { + try { + Desktop.getDesktop().browse(new URI(url)); + } catch (IOException | URISyntaxException exception) { + LOG.error("Problem when accessing repository", exception); + } + } + } + }); + } +} diff --git a/src/main/java/com/blazemeter/jmeter/commons/CollapsiblePanel.java b/src/main/java/com/blazemeter/jmeter/commons/CollapsiblePanel.java new file mode 100644 index 0000000..dd16352 --- /dev/null +++ b/src/main/java/com/blazemeter/jmeter/commons/CollapsiblePanel.java @@ -0,0 +1,308 @@ +package com.blazemeter.jmeter.commons; + +import java.awt.Color; +import java.awt.Cursor; +import java.awt.Dimension; +import java.awt.Insets; +import java.awt.event.FocusAdapter; +import java.awt.event.FocusEvent; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; +import java.util.Collections; +import java.util.List; +import java.util.function.Consumer; +import javax.swing.BorderFactory; +import javax.swing.Box; +import javax.swing.BoxLayout; +import javax.swing.ImageIcon; +import javax.swing.JButton; +import javax.swing.JCheckBox; +import javax.swing.JComponent; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JTextField; + +public class CollapsiblePanel extends JPanel { + + public static final int PREFERED_HEIGHT = 40; + private final Header header; + private final JComponent contentComponent; + private boolean collapsed; + + private CollapsiblePanel(Builder builder) { + setBackground(Color.darkGray); + this.header = new Header(builder); + this.contentComponent = builder.content; + + setName(builder.namePrefix + "-collapsiblePanel"); + setLayout(new BoxLayout(this, BoxLayout.Y_AXIS)); + + add(this.header); + add(this.contentComponent); + if (builder.isCollapsed) { + toggleCollapsed(); + } + } + + public void toggleCollapsed() { + collapsed = !collapsed; + header.toggleCollapsed(); + contentComponent.setVisible(!collapsed); + } + + public boolean isCollapsed() { + return collapsed; + } + + public JPanel getHeaderPanel() { + return this.header; + } + + public void setEnabled(boolean enabled) { + header.setEnabled(enabled); + } + + public String getId() { + return header.getPanelTitle(); + } + + private class Header extends JPanel { + + private final JTextField name; + private final JPanel buttonsPanel; + private final ImageIcon collapsedIcon = ThemedIcon.fromResourceName("collapsed.png"); + private final ImageIcon expandedIcon = ThemedIcon.fromResourceName("expanded.png"); + private final JButton collapseButton; + private boolean enable; + + private Header(Builder builder) { + //Used to avoid issues while testing + setName(builder.namePrefix + "-collapsiblePanel-header"); + setBorder(BorderFactory.createTitledBorder("")); + setLayout(new BoxLayout(this, BoxLayout.LINE_AXIS)); + this.enable = builder.isEnabled; + if (builder.enablingListener != null) { + /* + we pass each field of builder instead of builder as parameter to avoid tying this instance + listeners to builder, which might change after creating this object. + */ + add(buildEnableCheckBox(builder.enablingListener)); + addGap(); + } + name = buildNameField(builder.title, builder.editableTitle); + addFocusListener(buildDisableNameOnFocusLostListener(name)); + add(name); + if (builder.editableTitle) { + addGap(); + add(buildEditTitleIcon(name)); + addGap(); + } + add(Box.createHorizontalGlue()); + buttonsPanel = buildButtonsPanel(builder.buttons); + add(buttonsPanel); + add(Box.createHorizontalGlue()); + collapseButton = buildCollapseButton(builder.collapsingListener); + add(collapseButton); + setMaximumSize(new Dimension(Integer.MAX_VALUE, calcHeight(builder.buttons))); + } + + private JCheckBox buildEnableCheckBox(Consumer enablingListener) { + JCheckBox ret = SwingUtils + .createComponent(getName() + "-disableCheck", new JCheckBox()); + ret.setSelected(this.enable); + ret.addItemListener(e -> { + this.enable = !this.enable; + ret.setSelected(this.enable); + enablingListener.accept(this.enable); + }); + return ret; + } + + private void addGap() { + add(Box.createRigidArea(new Dimension(5, 0))); + } + + private JTextField buildNameField(String title, boolean editable) { + JTextField ret = new JTextField(title, 10); + ret.setName(getName() + "-title"); + ret.setBorder(BorderFactory.createEmptyBorder(0, 5, 0, 10)); + ret.setEditable(false); + if (editable) { + ret.addFocusListener(buildDisableNameOnFocusLostListener(ret)); + ret.addActionListener(e -> ret.setEditable(false)); + } + return ret; + } + + private FocusAdapter buildDisableNameOnFocusLostListener(JTextField name) { + return new FocusAdapter() { + @Override + public void focusLost(FocusEvent e) { + name.setEditable(false); + } + }; + } + + private JLabel buildEditTitleIcon(JTextField nameField) { + JLabel ret = new JLabel(); + ret.setIcon(ThemedIcon.fromResourceName("pencil-edit.png")); + ret.setName(getName() + "-editTitleIcon"); + ret.addMouseListener(new MouseAdapter() { + @Override + public void mouseClicked(MouseEvent e) { + nameField.setEditable(true); + nameField.requestFocus(); + } + + public void mouseEntered(MouseEvent e) { + setCursor(Cursor.HAND_CURSOR); + } + + public void mouseExited(MouseEvent e) { + setCursor(Cursor.DEFAULT_CURSOR); + } + }); + return ret; + } + + private JPanel buildButtonsPanel(List buttons) { + JPanel ret = new JPanel(); + ret.setLayout(new BoxLayout(ret, BoxLayout.LINE_AXIS)); + ret.setName(getName() + "-buttonsPanel"); + for (JButton button : buttons) { + ret.add(button); + ret.add(Box.createRigidArea(new Dimension(10, 0))); + } + return ret; + } + + private JButton buildCollapseButton(Runnable collapsingListeners) { + JButton ret = new JButton(expandedIcon); + ret.setName(getName() + "-collapseButton"); + //Making the button looks like a label + ret.setFocusPainted(false); + ret.setMargin(new Insets(0, 0, 0, 0)); + ret.setContentAreaFilled(false); + ret.setBorderPainted(false); + ret.setOpaque(false); + ret.addMouseListener(new MouseAdapter() { + public void mouseClicked(MouseEvent e) { + CollapsiblePanel.this.toggleCollapsed(); + if (collapsingListeners != null) { + collapsingListeners.run(); + } + } + + public void mouseEntered(MouseEvent e) { + setCursor(Cursor.HAND_CURSOR); + } + + public void mouseExited(MouseEvent e) { + setCursor(Cursor.DEFAULT_CURSOR); + } + }); + return ret; + } + + private int calcHeight(List buttons) { + return buttons.stream() + .mapToInt(button -> (int) button.getMinimumSize().getHeight()) + .max() + .orElse(PREFERED_HEIGHT); + } + + public void setEnabled(boolean enabled) { + name.setEditable(true); + name.setForeground( + enabled ? new JTextField().getForeground() : new JTextField().getDisabledTextColor()); + name.setEditable(false); + } + + private String getPanelTitle() { + return name.getText(); + } + + private void setCursor(int cursor) { + this.setCursor(Cursor.getPredefinedCursor(cursor)); + } + + private void toggleCollapsed() { + boolean isCollapsed = collapseButton.getIcon().equals(expandedIcon); + buttonsPanel.setVisible(!isCollapsed); + collapseButton.setIcon(isCollapsed ? collapsedIcon : expandedIcon); + } + + @Override + public int getHeight() { + return PREFERED_HEIGHT; + } + + } + + public static final class Builder { + + private String namePrefix = ""; + private String title = ""; + private boolean editableTitle = false; + private List buttons = Collections.emptyList(); + private Runnable collapsingListener; + private boolean isEnabled = true; + private boolean isCollapsed = false; + private Consumer enablingListener; + private JComponent content; + + public Builder() { + } + + public Builder withNamePrefix(String prefix) { + this.namePrefix = prefix; + return this; + } + + public Builder withTitle(String title) { + this.title = title; + return this; + } + + public Builder withEditableTitle() { + this.editableTitle = true; + return this; + } + + public Builder withButtons(List buttons) { + this.buttons = buttons; + return this; + } + + public Builder withCollapsingListener(Runnable collapsingListener) { + this.collapsingListener = collapsingListener; + return this; + } + + public Builder withEnablingListener(Consumer enablingListener) { + this.enablingListener = enablingListener; + return this; + } + + public Builder withEnabled(boolean isEnabled) { + this.isEnabled = isEnabled; + return this; + } + + public Builder withCollapsed(boolean isCollapsed) { + this.isCollapsed = isCollapsed; + return this; + } + + public Builder withContent(JComponent content) { + this.content = content; + return this; + } + + public CollapsiblePanel build() { + return new CollapsiblePanel(this); + } + + } +} + diff --git a/src/main/java/com/blazemeter/jmeter/commons/FieldValidations.java b/src/main/java/com/blazemeter/jmeter/commons/FieldValidations.java new file mode 100644 index 0000000..f381271 --- /dev/null +++ b/src/main/java/com/blazemeter/jmeter/commons/FieldValidations.java @@ -0,0 +1,60 @@ +package com.blazemeter.jmeter.commons; + +import java.awt.Color; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; +import javax.swing.BorderFactory; +import javax.swing.JLabel; +import javax.swing.JTextField; +import javax.swing.border.Border; + +public class FieldValidations { + private final Border defaultBorder = new JTextField().getBorder(); + private final Border invalidBorder = BorderFactory.createLineBorder(Color.red); + + private JTextField field; + private final JLabel error; + private List validations = new ArrayList<>(); + + public FieldValidations(JTextField field, JLabel error) { + this.field = field; + this.error = error; + } + + public void setField(JTextField field) { + this.field = field; + } + + public JTextField getField() { + return field; + } + + public void addValidations(Validation... validation) { + validations = Arrays.asList(validation); + } + + public List getValidations() { + return validations; + } + + public void applyFormat() { + updateValidationStates(); + boolean valid = isValid(); + field.setBorder(valid ? defaultBorder : invalidBorder); + error.setVisible(!valid); + error.setText(valid ? "" : validations.stream() + .filter(validation -> !validation.isValid()) + .map(Validation::getErrorMessage) + .collect(Collectors.joining(". "))); + } + + public void updateValidationStates() { + validations.forEach(validation -> validation.updateState(field.getText())); + } + + public boolean isValid() { + return validations.stream().allMatch(Validation::isValid); + } +} diff --git a/src/main/java/com/blazemeter/jmeter/commons/FormValidation.java b/src/main/java/com/blazemeter/jmeter/commons/FormValidation.java new file mode 100644 index 0000000..ed18e49 --- /dev/null +++ b/src/main/java/com/blazemeter/jmeter/commons/FormValidation.java @@ -0,0 +1,76 @@ +package com.blazemeter.jmeter.commons; + +import java.util.List; +import javax.swing.JTextField; + +public class FormValidation { + private final List fieldValidations; + private Runnable onValid; + private Runnable onInvalid; + private boolean active = true; + + public FormValidation(List fieldValidations) { + this.fieldValidations = fieldValidations; + } + + public void onSuccess(Runnable runnable) { + this.onValid = runnable; + } + + public void onFailure(Runnable runnable) { + this.onInvalid = runnable; + } + + public boolean isValid() { + fieldValidations.forEach(FieldValidations::updateValidationStates); + return fieldValidations.stream().allMatch(FieldValidations::isValid); + } + + public void updateFormats(JTextField source) { + boolean allFieldsValid = true; + for (FieldValidations fieldValidations : fieldValidations) { + fieldValidations.updateValidationStates(); + + //If at least 1 field is invalid, the form is invalid + if (!fieldValidations.isValid()) { + allFieldsValid = false; + } + + //Apply the format to the field if the triggering field is + // the same as the field being validated + if (fieldValidations.getField().equals(source)) { + fieldValidations.applyFormat(); + } + } + + if (allFieldsValid) { + onValid.run(); + } else { + onInvalid.run(); + } + } + + public void applyFormats() { + fieldValidations.forEach(FieldValidations::applyFormat); + } + + public void validate(JTextField field) { + updateFormats(field); + } + + public void validate() { + if (!active) { + return; + } + + if (isValid()) { + onValid.run(); + } else { + onInvalid.run(); + } + } + + public void setActive(boolean active) { + this.active = active; + } +} diff --git a/src/main/java/com/blazemeter/jmeter/commons/PlaceHolderPassword.java b/src/main/java/com/blazemeter/jmeter/commons/PlaceHolderPassword.java new file mode 100644 index 0000000..06c5d6e --- /dev/null +++ b/src/main/java/com/blazemeter/jmeter/commons/PlaceHolderPassword.java @@ -0,0 +1,41 @@ +package com.blazemeter.jmeter.commons; + +import java.awt.Graphics; +import java.awt.Graphics2D; +import java.awt.RenderingHints; +import javax.swing.JPasswordField; + +public class PlaceHolderPassword extends JPasswordField { + private String placeHolder = ""; + + public PlaceHolderPassword() { + this(null); + } + + public PlaceHolderPassword(String text) { + super(text); + } + + @Override + protected void paintComponent(Graphics pG) { + super.paintComponent(pG); + + if (placeHolder == null || placeHolder.length() == 0 || getPassword().length > 0) { + return; + } + + Graphics2D g = (Graphics2D) pG; + g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); + g.setColor(getDisabledTextColor()); + g.drawString(placeHolder, getInsets().left, pG.getFontMetrics() + .getMaxAscent() + getInsets().top); + } + + public void setPlaceHolder(String placeHolder) { + this.placeHolder = placeHolder; + } + + public String getPlaceHolder() { + return placeHolder; + } +} diff --git a/src/main/java/com/blazemeter/jmeter/commons/PlaceHolderTextField.java b/src/main/java/com/blazemeter/jmeter/commons/PlaceHolderTextField.java new file mode 100644 index 0000000..21ae583 --- /dev/null +++ b/src/main/java/com/blazemeter/jmeter/commons/PlaceHolderTextField.java @@ -0,0 +1,42 @@ +package com.blazemeter.jmeter.commons; + +import java.awt.Graphics; +import java.awt.Graphics2D; +import java.awt.RenderingHints; +import javax.swing.JTextField; + +public class PlaceHolderTextField extends JTextField { + + private String placeHolder = ""; + + public PlaceHolderTextField() { + this(null); + } + + public PlaceHolderTextField(String text) { + super(text); + } + + @Override + protected void paintComponent(Graphics pG) { + super.paintComponent(pG); + + if (placeHolder == null || placeHolder.length() == 0 || getText().length() > 0) { + return; + } + + Graphics2D g = (Graphics2D) pG; + g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); + g.setColor(getDisabledTextColor()); + g.drawString(placeHolder, getInsets().left, pG.getFontMetrics() + .getMaxAscent() + getInsets().top); + } + + public void setPlaceHolder(String placeHolder) { + this.placeHolder = placeHolder; + } + + public String getPlaceHolder() { + return placeHolder; + } +} diff --git a/src/main/java/com/blazemeter/jmeter/commons/StringUtils.java b/src/main/java/com/blazemeter/jmeter/commons/StringUtils.java new file mode 100644 index 0000000..31af7fa --- /dev/null +++ b/src/main/java/com/blazemeter/jmeter/commons/StringUtils.java @@ -0,0 +1,10 @@ +package com.blazemeter.jmeter.commons; + +public class StringUtils { + + public static String capitalize(String text) { + return text != null && !text.isEmpty() ? text.substring(0, 1).toUpperCase() + text.substring(1) + : text; + } + +} diff --git a/src/main/java/com/blazemeter/jmeter/commons/SwingUtils.java b/src/main/java/com/blazemeter/jmeter/commons/SwingUtils.java new file mode 100644 index 0000000..a386f46 --- /dev/null +++ b/src/main/java/com/blazemeter/jmeter/commons/SwingUtils.java @@ -0,0 +1,79 @@ +package com.blazemeter.jmeter.commons; + +import java.awt.event.ActionListener; +import javax.swing.ImageIcon; +import javax.swing.JButton; +import javax.swing.JComponent; +import org.apache.jmeter.util.JMeterUtils; + +public class SwingUtils { + public static T createComponent(String name, T component) { + component.setName(name); + return component; + } + + public static final class ButtonBuilder { + + private ActionListener actionListener; + private String action; + private String name; + private String iconName = ""; + private ImageIcon icon; + private boolean enabled = true; + private boolean hasText = true; + + public ButtonBuilder() { + + } + + public ButtonBuilder withActionListener(ActionListener actionListener) { + this.actionListener = actionListener; + return this; + } + + public ButtonBuilder withAction(String action) { + this.action = action; + return this; + } + + public ButtonBuilder withName(String name) { + this.name = name; + return this; + } + + public ButtonBuilder withIcon(String iconName) { + this.iconName = iconName; + return this; + } + + public ButtonBuilder withIcon(ImageIcon icon) { + this.icon = icon; + return this; + } + + public ButtonBuilder isEnabled(boolean enabled) { + this.enabled = enabled; + return this; + } + + public ButtonBuilder hasText(boolean hasText) { + this.hasText = hasText; + return this; + } + + public JButton build() { + String parsedName = JMeterUtils.getResString(name); + + JButton button = createComponent(name + "Button", new JButton()); + + button.setActionCommand(action); + button.addActionListener(actionListener); + button.setEnabled(enabled); + button.setIcon(iconName.isEmpty() ? icon : ThemedIcon.fromResourceName(iconName)); + button.setText(!hasText ? "" + : parsedName.contains("res_key") ? StringUtils.capitalize(name) : parsedName); + return button; + } + } + +} diff --git a/src/main/java/com/blazemeter/jmeter/commons/ThemeUtils.java b/src/main/java/com/blazemeter/jmeter/commons/ThemeUtils.java new file mode 100644 index 0000000..cc96740 --- /dev/null +++ b/src/main/java/com/blazemeter/jmeter/commons/ThemeUtils.java @@ -0,0 +1,14 @@ +package com.blazemeter.jmeter.commons; + +import java.util.regex.Pattern; +import org.apache.jmeter.gui.action.LookAndFeelCommand; + +public class ThemeUtils { + + private static final Pattern DARK_THEME_PATTERN = Pattern + .compile("Intellij|HighContrastLight|HighContrastDark|Darcula|Motif|OneDark|SolarizedDark"); + + public static boolean isDark() { + return DARK_THEME_PATTERN.matcher(LookAndFeelCommand.getJMeterLaf()).find(); + } +} diff --git a/src/main/java/com/blazemeter/jmeter/commons/ThemedIcon.java b/src/main/java/com/blazemeter/jmeter/commons/ThemedIcon.java new file mode 100644 index 0000000..75970b7 --- /dev/null +++ b/src/main/java/com/blazemeter/jmeter/commons/ThemedIcon.java @@ -0,0 +1,22 @@ +package com.blazemeter.jmeter.commons; + +import java.util.Map; +import java.util.WeakHashMap; +import javax.swing.ImageIcon; + +public class ThemedIcon { + + private static final Map CACHED_ICONS = new WeakHashMap<>(); + private static final String RESOURCE_SEPARATOR = "/"; + + public static ImageIcon fromResourceName(String resourceName) { + String resourcePath = getThemePath() + RESOURCE_SEPARATOR + resourceName; + return CACHED_ICONS + .computeIfAbsent(resourcePath, p -> new ImageIcon(ThemedIcon.class.getResource(p))); + } + + private static String getThemePath() { + return ThemeUtils.isDark() ? "/dark-theme" : "/light-theme"; + } + +} diff --git a/src/main/java/com/blazemeter/jmeter/commons/ThemedIconLabel.java b/src/main/java/com/blazemeter/jmeter/commons/ThemedIconLabel.java new file mode 100644 index 0000000..49f40bd --- /dev/null +++ b/src/main/java/com/blazemeter/jmeter/commons/ThemedIconLabel.java @@ -0,0 +1,20 @@ +package com.blazemeter.jmeter.commons; + +import java.awt.Graphics; +import javax.swing.JLabel; + +public class ThemedIconLabel extends JLabel { + + private final String iconResourceName; + + public ThemedIconLabel(String iconResourceName) { + super(ThemedIcon.fromResourceName(iconResourceName)); + this.iconResourceName = iconResourceName; + } + + @Override + public void paint(Graphics g) { + setIcon(ThemedIcon.fromResourceName(iconResourceName)); + super.paint(g); + } +} diff --git a/src/main/java/com/blazemeter/jmeter/commons/Validation.java b/src/main/java/com/blazemeter/jmeter/commons/Validation.java new file mode 100644 index 0000000..317e054 --- /dev/null +++ b/src/main/java/com/blazemeter/jmeter/commons/Validation.java @@ -0,0 +1,27 @@ +package com.blazemeter.jmeter.commons; + +import java.util.function.Predicate; + +public class Validation { + + private final Predicate condition; + private final String errorMessage; + private boolean valid; + + public Validation(Predicate condition, String errorMessage) { + this.condition = condition; + this.errorMessage = errorMessage; + } + + public void updateState(String value) { + this.valid = condition.test(value); + } + + public boolean isValid() { + return valid; + } + + public String getErrorMessage() { + return errorMessage; + } +} diff --git a/src/main/resources/dark-theme/blazemeter-labs-logo.png b/src/main/resources/dark-theme/blazemeter-labs-logo.png new file mode 100644 index 0000000..917ecb1 Binary files /dev/null and b/src/main/resources/dark-theme/blazemeter-labs-logo.png differ diff --git a/src/main/resources/dark-theme/collapsed.png b/src/main/resources/dark-theme/collapsed.png new file mode 100644 index 0000000..c8b0ddb Binary files /dev/null and b/src/main/resources/dark-theme/collapsed.png differ diff --git a/src/main/resources/dark-theme/expanded.png b/src/main/resources/dark-theme/expanded.png new file mode 100644 index 0000000..e029ca4 Binary files /dev/null and b/src/main/resources/dark-theme/expanded.png differ diff --git a/src/main/resources/dark-theme/not-visible-credentials.png b/src/main/resources/dark-theme/not-visible-credentials.png new file mode 100644 index 0000000..0cbfde3 Binary files /dev/null and b/src/main/resources/dark-theme/not-visible-credentials.png differ diff --git a/src/main/resources/dark-theme/visible-credentials.png b/src/main/resources/dark-theme/visible-credentials.png new file mode 100644 index 0000000..4b78520 Binary files /dev/null and b/src/main/resources/dark-theme/visible-credentials.png differ diff --git a/src/main/resources/light-theme/blazemeter-labs-logo.png b/src/main/resources/light-theme/blazemeter-labs-logo.png new file mode 100644 index 0000000..beb7b9b Binary files /dev/null and b/src/main/resources/light-theme/blazemeter-labs-logo.png differ diff --git a/src/main/resources/light-theme/collapsed.png b/src/main/resources/light-theme/collapsed.png new file mode 100644 index 0000000..87d0d6c Binary files /dev/null and b/src/main/resources/light-theme/collapsed.png differ diff --git a/src/main/resources/light-theme/expanded.png b/src/main/resources/light-theme/expanded.png new file mode 100644 index 0000000..9e79a39 Binary files /dev/null and b/src/main/resources/light-theme/expanded.png differ diff --git a/src/main/resources/light-theme/not-visible-credentials.png b/src/main/resources/light-theme/not-visible-credentials.png new file mode 100644 index 0000000..948bfaa Binary files /dev/null and b/src/main/resources/light-theme/not-visible-credentials.png differ diff --git a/src/main/resources/light-theme/visible-credentials.png b/src/main/resources/light-theme/visible-credentials.png new file mode 100644 index 0000000..de8b55b Binary files /dev/null and b/src/main/resources/light-theme/visible-credentials.png differ