Skip to content

Commit 0cd166a

Browse files
committed
encoder: Allow resizing the input panel
Signed-off-by: kingthorin <kingthorin@users.noreply.github.com>
1 parent ab0f8b3 commit 0cd166a

5 files changed

Lines changed: 215 additions & 40 deletions

File tree

addOns/encoder/CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
66

77
## Unreleased
88

9+
### Changed
10+
- Main dialog input area is now resizable via a draggable divider between input and output panels, the position is saved and restored when the dialog is opened.
911

1012
## [1.8.0] - 2025-12-15
1113
### Changed

addOns/encoder/src/main/java/org/zaproxy/addon/encoder/EncodeDecodeDialog.java

Lines changed: 48 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
import javax.swing.JOptionPane;
4141
import javax.swing.JPanel;
4242
import javax.swing.JScrollPane;
43+
import javax.swing.JSplitPane;
4344
import javax.swing.JTabbedPane;
4445
import javax.swing.JToolBar;
4546
import javax.swing.ScrollPaneConstants;
@@ -86,9 +87,12 @@ public class EncodeDecodeDialog extends AbstractFrame implements OptionsChangedL
8687
private JButton deleteSelectedTabButton;
8788
private int globalOutputPanelIndex;
8889
private JButton resetButton;
90+
private JSplitPane inputOutputSplitPane = null;
91+
private final double initialDividerLocation;
8992

90-
public EncodeDecodeDialog(List<TabModel> tabModels) {
93+
public EncodeDecodeDialog(List<TabModel> tabModels, double initialDividerLocation) {
9194
super();
95+
this.initialDividerLocation = initialDividerLocation;
9296
encodeDecodeProcessors = new EncodeDecodeProcessors();
9397
init();
9498
setTabs(tabModels);
@@ -146,15 +150,15 @@ private void resetTabs() {
146150

147151
List<TabModel> defaultTabModels = new ArrayList<>();
148152
try {
149-
defaultTabModels.addAll(EncoderConfig.loadDefaultConfig());
153+
defaultTabModels.addAll(EncoderConfig.loadDefaultConfig().getTabs());
150154
} catch (ConfigurationException | IOException e) {
151155
LOGGER.warn("There was a problem loading the default encoder config.", e);
152156
}
153157

154158
setTabs(defaultTabModels);
155159

156160
try {
157-
EncoderConfig.saveConfig(tabs);
161+
EncoderConfig.saveConfig(tabs, getDividerProportion());
158162
} catch (ConfigurationException | IOException e) {
159163
LOGGER.warn("There was a problem saving the encoder config.", e);
160164
}
@@ -489,24 +493,6 @@ private JPanel getMainPanel() {
489493
mainPanel.setPreferredSize(new Dimension(800, 600));
490494
mainPanel.setLayout(new GridBagLayout());
491495

492-
final GridBagConstraints gbcScrollPanel = new GridBagConstraints();
493-
gbcScrollPanel.gridx = 0;
494-
gbcScrollPanel.gridy = 1;
495-
gbcScrollPanel.insets = new Insets(1, 1, 1, 1);
496-
gbcScrollPanel.anchor = GridBagConstraints.NORTHWEST;
497-
gbcScrollPanel.fill = GridBagConstraints.BOTH;
498-
gbcScrollPanel.weightx = 1.0D;
499-
gbcScrollPanel.weighty = 0.25D;
500-
501-
final GridBagConstraints gbcTabPanel = new GridBagConstraints();
502-
gbcTabPanel.gridx = 0;
503-
gbcTabPanel.gridy = 3;
504-
gbcTabPanel.insets = new Insets(1, 1, 1, 1);
505-
gbcTabPanel.anchor = GridBagConstraints.NORTHWEST;
506-
gbcTabPanel.fill = GridBagConstraints.BOTH;
507-
gbcTabPanel.weightx = 1.0D;
508-
gbcTabPanel.weighty = 1.0D;
509-
510496
final JScrollPane scrollPanelWithInputField = new JScrollPane();
511497
scrollPanelWithInputField.setViewportView(getInputField());
512498
scrollPanelWithInputField.setHorizontalScrollBarPolicy(
@@ -519,16 +505,40 @@ private JPanel getMainPanel() {
519505
TitledBorder.DEFAULT_POSITION,
520506
FontUtils.getFont(FontUtils.Size.standard)));
521507

508+
JPanel bottomPanel = new JPanel(new GridBagLayout());
522509
final GridBagConstraints gbcToolbar = new GridBagConstraints();
523510
gbcToolbar.gridx = 0;
524-
gbcToolbar.gridy = 2;
511+
gbcToolbar.gridy = 0;
525512
gbcToolbar.insets = new Insets(1, 1, 1, 1);
526513
gbcToolbar.anchor = GridBagConstraints.NORTHWEST;
527514
gbcToolbar.fill = GridBagConstraints.BOTH;
528-
529-
mainPanel.add(scrollPanelWithInputField, gbcScrollPanel);
530-
mainPanel.add(getPanelToolbar(), gbcToolbar);
531-
mainPanel.add(getMainTabbedPane(), gbcTabPanel);
515+
final GridBagConstraints gbcTabPanel = new GridBagConstraints();
516+
gbcTabPanel.gridx = 0;
517+
gbcTabPanel.gridy = 1;
518+
gbcTabPanel.insets = new Insets(1, 1, 1, 1);
519+
gbcTabPanel.anchor = GridBagConstraints.NORTHWEST;
520+
gbcTabPanel.fill = GridBagConstraints.BOTH;
521+
gbcTabPanel.weightx = 1.0D;
522+
gbcTabPanel.weighty = 1.0D;
523+
bottomPanel.add(getPanelToolbar(), gbcToolbar);
524+
bottomPanel.add(getMainTabbedPane(), gbcTabPanel);
525+
526+
inputOutputSplitPane =
527+
new JSplitPane(
528+
JSplitPane.VERTICAL_SPLIT, scrollPanelWithInputField, bottomPanel);
529+
inputOutputSplitPane.setResizeWeight(initialDividerLocation);
530+
inputOutputSplitPane.setDividerLocation(initialDividerLocation);
531+
inputOutputSplitPane.setOneTouchExpandable(true);
532+
533+
final GridBagConstraints gbcSplit = new GridBagConstraints();
534+
gbcSplit.gridx = 0;
535+
gbcSplit.gridy = 0;
536+
gbcSplit.insets = new Insets(1, 1, 1, 1);
537+
gbcSplit.anchor = GridBagConstraints.NORTHWEST;
538+
gbcSplit.fill = GridBagConstraints.BOTH;
539+
gbcSplit.weightx = 1.0D;
540+
gbcSplit.weighty = 1.0D;
541+
mainPanel.add(inputOutputSplitPane, gbcSplit);
532542
}
533543
return mainPanel;
534544
}
@@ -665,12 +675,23 @@ private boolean updateEncodeDecodeField(ZapTextArea zapTextArea, OutputPanelMode
665675

666676
private void saveSetting() {
667677
try {
668-
EncoderConfig.saveConfig(tabs);
678+
EncoderConfig.saveConfig(tabs, getDividerProportion());
669679
} catch (Exception e) {
670680
LOGGER.error("Can not store Encoder Config", e);
671681
}
672682
}
673683

684+
private double getDividerProportion() {
685+
if (inputOutputSplitPane == null) {
686+
return initialDividerLocation;
687+
}
688+
int height = inputOutputSplitPane.getHeight() - inputOutputSplitPane.getDividerSize();
689+
if (height <= 0) {
690+
return initialDividerLocation;
691+
}
692+
return (double) inputOutputSplitPane.getDividerLocation() / height;
693+
}
694+
674695
@Override
675696
public void optionsChanged(OptionsParam optionsParam) {
676697
updateEncodeDecodeFields();

addOns/encoder/src/main/java/org/zaproxy/addon/encoder/EncoderConfig.java

Lines changed: 38 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -53,19 +53,24 @@ public class EncoderConfig {
5353
private static final String DEFAULT_BUNDLED_CONFIG_FILE =
5454
"resources/" + DEFAULT_CONFIG_FILE_NAME;
5555

56+
private static final String DIVIDER_LOCATION_KEY = "dividerLocation";
57+
58+
/** Default proportion (0.0–1.0) for the input area in the main dialog. */
59+
public static final double DEFAULT_DIVIDER_LOCATION = 0.2;
60+
5661
private EncoderConfig() {
5762
// Utility Class
5863
}
5964

60-
public static List<TabModel> loadConfig() throws ConfigurationException, IOException {
65+
public static EncoderConfigData loadConfig() throws ConfigurationException, IOException {
6166
Path config = getConfigPath(CONFIG_FILE);
6267
if (Files.notExists(config)) {
6368
return loadDefaultConfig();
6469
}
6570
return loadConfig(config);
6671
}
6772

68-
public static List<TabModel> loadDefaultConfig() throws ConfigurationException, IOException {
73+
public static EncoderConfigData loadDefaultConfig() throws ConfigurationException, IOException {
6974
Path defaultConfig = getConfigPath(DEFAULT_CONFIG_FILE);
7075
if (Files.notExists(defaultConfig)) {
7176
Files.createDirectories(defaultConfig.getParent());
@@ -81,7 +86,7 @@ public static List<TabModel> loadDefaultConfig() throws ConfigurationException,
8186
} catch (IOException e1) {
8287
LOGGER.error("Failed to load the default bundled configuration file.", e1);
8388
}
84-
return new ArrayList<>();
89+
return new EncoderConfigData(new ArrayList<>(), DEFAULT_DIVIDER_LOCATION);
8590
}
8691
}
8792
return loadConfig(defaultConfig);
@@ -91,11 +96,11 @@ private static Path getConfigPath(String configName) {
9196
return Paths.get(Constant.getZapHome(), configName);
9297
}
9398

94-
private static List<TabModel> loadConfig(Path file) throws ConfigurationException {
99+
private static EncoderConfigData loadConfig(Path file) throws ConfigurationException {
95100
return loadConfig(new ZapXmlConfiguration(file.toFile()));
96101
}
97102

98-
private static List<TabModel> loadConfig(ZapXmlConfiguration config) {
103+
private static EncoderConfigData loadConfig(ZapXmlConfiguration config) {
99104
List<TabModel> tabs = new ArrayList<>();
100105
List<HierarchicalConfiguration> tabConfigs = config.configurationsAt(TAB_PATH);
101106
for (HierarchicalConfiguration tabConfig : tabConfigs) {
@@ -118,15 +123,19 @@ private static List<TabModel> loadConfig(ZapXmlConfiguration config) {
118123
tab.setOutputPanels(panels);
119124
tabs.add(tab);
120125
}
121-
return tabs;
126+
double raw = config.getDouble(DIVIDER_LOCATION_KEY, DEFAULT_DIVIDER_LOCATION);
127+
double dividerLocation = raw >= 0.0 && raw <= 1.0 ? raw : DEFAULT_DIVIDER_LOCATION;
128+
return new EncoderConfigData(tabs, dividerLocation);
122129
}
123130

124-
public static void saveConfig(List<TabModel> tabs) throws ConfigurationException, IOException {
125-
saveConfig(getConfigPath(CONFIG_FILE), tabs);
131+
public static void saveConfig(List<TabModel> tabs, double dividerLocation)
132+
throws ConfigurationException, IOException {
133+
saveConfig(getConfigPath(CONFIG_FILE), tabs, dividerLocation);
126134
}
127135

128-
private static void saveConfig(Path file, List<TabModel> tabs)
136+
private static void saveConfig(Path file, List<TabModel> tabs, double dividerLocation)
129137
throws ConfigurationException, IOException {
138+
double clamped = Math.max(0.0, Math.min(1.0, dividerLocation));
130139
ZapXmlConfiguration config = new ZapXmlConfiguration();
131140
int t = 0;
132141
for (TabModel tab : tabs) {
@@ -140,8 +149,28 @@ private static void saveConfig(Path file, List<TabModel> tabs)
140149
elementPanelKey + OUTPUT_PANEL_SCRIPT_KEY, panel.getProcessorId());
141150
}
142151
}
152+
config.setProperty(DIVIDER_LOCATION_KEY, clamped);
143153

144154
Files.createDirectories(file.getParent());
145155
config.save(file.toFile());
146156
}
157+
158+
/** Holder for encoder config: tab layout and divider position. */
159+
public static final class EncoderConfigData {
160+
private final List<TabModel> tabs;
161+
private final double dividerLocation;
162+
163+
public EncoderConfigData(List<TabModel> tabs, double dividerLocation) {
164+
this.tabs = new ArrayList<>(tabs);
165+
this.dividerLocation = Math.max(0.0, Math.min(1.0, dividerLocation));
166+
}
167+
168+
public List<TabModel> getTabs() {
169+
return new ArrayList<>(tabs);
170+
}
171+
172+
public double getDividerLocation() {
173+
return dividerLocation;
174+
}
175+
}
147176
}

addOns/encoder/src/main/java/org/zaproxy/addon/encoder/ExtensionEncoder.java

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -169,14 +169,17 @@ private PopupReplaceInputMenu getPopupMenuReplaceInput() {
169169

170170
private EncodeDecodeDialog getEncodeDecodeDialog() {
171171
if (encodeDecodeDialog == null) {
172-
List<TabModel> tabModels;
172+
EncoderConfig.EncoderConfigData configData;
173173
try {
174-
tabModels = EncoderConfig.loadConfig();
174+
configData = EncoderConfig.loadConfig();
175175
} catch (ConfigurationException | IOException e) {
176176
LOGGER.error("Can not load Encoder Config");
177-
tabModels = new ArrayList<>(0);
177+
configData =
178+
new EncoderConfig.EncoderConfigData(
179+
new ArrayList<>(0), EncoderConfig.DEFAULT_DIVIDER_LOCATION);
178180
}
179-
encodeDecodeDialog = new EncodeDecodeDialog(tabModels);
181+
encodeDecodeDialog =
182+
new EncodeDecodeDialog(configData.getTabs(), configData.getDividerLocation());
180183
}
181184
return encodeDecodeDialog;
182185
}
Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
/*
2+
* Zed Attack Proxy (ZAP) and its related class files.
3+
*
4+
* ZAP is an HTTP/HTTPS proxy for assessing web application security.
5+
*
6+
* Copyright 2026 The ZAP Development Team
7+
*
8+
* Licensed under the Apache License, Version 2.0 (the "License");
9+
* you may not use this file except in compliance with the License.
10+
* You may obtain a copy of the License at
11+
*
12+
* http://www.apache.org/licenses/LICENSE-2.0
13+
*
14+
* Unless required by applicable law or agreed to in writing, software
15+
* distributed under the License is distributed on an "AS IS" BASIS,
16+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17+
* See the License for the specific language governing permissions and
18+
* limitations under the License.
19+
*/
20+
package org.zaproxy.addon.encoder;
21+
22+
import static org.hamcrest.MatcherAssert.assertThat;
23+
import static org.hamcrest.Matchers.equalTo;
24+
import static org.hamcrest.Matchers.is;
25+
26+
import java.nio.file.Files;
27+
import java.nio.file.Path;
28+
import java.nio.file.Paths;
29+
import java.util.ArrayList;
30+
import java.util.List;
31+
import org.junit.jupiter.api.BeforeEach;
32+
import org.junit.jupiter.api.Test;
33+
import org.parosproxy.paros.Constant;
34+
import org.zaproxy.zap.testutils.TestUtils;
35+
import org.zaproxy.zap.utils.ZapXmlConfiguration;
36+
37+
/** Unit test for {@link EncoderConfig} (tabs and divider location). */
38+
class EncoderConfigUnitTest extends TestUtils {
39+
40+
private static final String CONFIG_PATH = "addOnData/encoder/config/encoder-config.xml";
41+
42+
@BeforeEach
43+
void setUp() throws Exception {
44+
setUpZap();
45+
}
46+
47+
@Test
48+
void shouldHaveDefaultDividerLocationWhenNoConfigFile() throws Exception {
49+
// Given no config file (loadConfig will use loadDefaultConfig)
50+
// When
51+
EncoderConfig.EncoderConfigData data = EncoderConfig.loadConfig();
52+
// Then
53+
assertThat(data.getDividerLocation(), is(equalTo(EncoderConfig.DEFAULT_DIVIDER_LOCATION)));
54+
}
55+
56+
@Test
57+
void shouldSaveAndLoadDividerLocation() throws Exception {
58+
// Given
59+
List<TabModel> tabs = new ArrayList<>();
60+
double value = 0.35;
61+
// When
62+
EncoderConfig.saveConfig(tabs, value);
63+
EncoderConfig.EncoderConfigData data = EncoderConfig.loadConfig();
64+
// Then
65+
assertThat(data.getDividerLocation(), is(equalTo(value)));
66+
}
67+
68+
@Test
69+
void shouldClampDividerLocationWhenSaving() throws Exception {
70+
// Given
71+
List<TabModel> tabs = new ArrayList<>();
72+
// When
73+
EncoderConfig.saveConfig(tabs, -0.1);
74+
EncoderConfig.EncoderConfigData dataLow = EncoderConfig.loadConfig();
75+
EncoderConfig.saveConfig(tabs, 1.5);
76+
EncoderConfig.EncoderConfigData dataHigh = EncoderConfig.loadConfig();
77+
// Then
78+
assertThat(dataLow.getDividerLocation(), is(equalTo(0.0)));
79+
assertThat(dataHigh.getDividerLocation(), is(equalTo(1.0)));
80+
}
81+
82+
@Test
83+
void shouldUseDefaultWhenDividerLocationInFileOutOfRange() throws Exception {
84+
// Given
85+
Path configFile = Paths.get(Constant.getZapHome(), CONFIG_PATH);
86+
Files.createDirectories(configFile.getParent());
87+
ZapXmlConfiguration config = new ZapXmlConfiguration();
88+
config.setProperty("dividerLocation", 1.5);
89+
config.save(configFile.toFile());
90+
// When
91+
EncoderConfig.EncoderConfigData data = EncoderConfig.loadConfig();
92+
// Then
93+
assertThat(data.getDividerLocation(), is(equalTo(EncoderConfig.DEFAULT_DIVIDER_LOCATION)));
94+
}
95+
96+
@Test
97+
void shouldAcceptDividerLocationAtBoundaries() throws Exception {
98+
// Given
99+
List<TabModel> tabs = new ArrayList<>();
100+
// When
101+
EncoderConfig.saveConfig(tabs, 0.0);
102+
EncoderConfig.EncoderConfigData data0 = EncoderConfig.loadConfig();
103+
EncoderConfig.saveConfig(tabs, 1.0);
104+
EncoderConfig.EncoderConfigData data1 = EncoderConfig.loadConfig();
105+
// Then
106+
assertThat(data0.getDividerLocation(), is(equalTo(0.0)));
107+
assertThat(data1.getDividerLocation(), is(equalTo(1.0)));
108+
}
109+
110+
@Test
111+
void encoderConfigDataShouldClampDividerLocation() {
112+
// Given / When
113+
List<TabModel> tabs = List.of();
114+
EncoderConfig.EncoderConfigData dataLow = new EncoderConfig.EncoderConfigData(tabs, -0.1);
115+
EncoderConfig.EncoderConfigData dataHigh = new EncoderConfig.EncoderConfigData(tabs, 1.5);
116+
// Then
117+
assertThat(dataLow.getDividerLocation(), is(equalTo(0.0)));
118+
assertThat(dataHigh.getDividerLocation(), is(equalTo(1.0)));
119+
}
120+
}

0 commit comments

Comments
 (0)