Skip to content

Commit fc803f5

Browse files
committed
commonlib: Add indicator for new output to TabbedOutputPanel
Signed-off-by: ricekot <[email protected]>
1 parent bfadca8 commit fc803f5

File tree

2 files changed

+78
-6
lines changed

2 files changed

+78
-6
lines changed

addOns/commonlib/src/main/java/org/zaproxy/addon/commonlib/ui/TabbedOutputPanel.java

+78-6
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import java.awt.BorderLayout;
2323
import java.awt.Component;
2424
import java.awt.event.KeyEvent;
25+
import java.awt.event.MouseEvent;
2526
import java.util.ArrayList;
2627
import java.util.Comparator;
2728
import java.util.HashMap;
@@ -34,6 +35,9 @@
3435
import javax.swing.JPanel;
3536
import javax.swing.JScrollPane;
3637
import javax.swing.JToolBar;
38+
import javax.swing.event.ChangeListener;
39+
import javax.swing.event.DocumentEvent;
40+
import javax.swing.event.DocumentListener;
3741
import javax.swing.text.DefaultCaret;
3842
import org.apache.commons.lang3.exception.ExceptionUtils;
3943
import org.parosproxy.paros.Constant;
@@ -47,6 +51,7 @@
4751
import org.zaproxy.zap.utils.TimeStampUtils;
4852
import org.zaproxy.zap.utils.ZapTextArea;
4953
import org.zaproxy.zap.view.OutputSource;
54+
import org.zaproxy.zap.view.OverlayIcon;
5055
import org.zaproxy.zap.view.TabbedPanel2;
5156
import org.zaproxy.zap.view.ZapToggleButton;
5257

@@ -77,15 +82,19 @@ public class TabbedOutputPanel extends OutputPanel {
7782
getImageIcon("/org/zaproxy/addon/commonlib/resources/ui-scroll-pane.png");
7883
private static final ImageIcon SCROLL_LOCK_ENABLED_ICON =
7984
getImageIcon("/org/zaproxy/addon/commonlib/resources/ui-scroll-lock-pane.png");
85+
private static final ImageIcon GREEN_BADGE_CORNER_ICON =
86+
getImageIcon("/org/zaproxy/addon/commonlib/resources/green-badge-corner.png");
8087

8188
private final TabbedPanel2 tabbedPanel;
8289

8390
private final Map<String, ZapTextArea> txtOutputs;
8491
private final Map<String, OutputSource> registeredOutputSources;
92+
private final Map<String, ChangeListener> outputSourceChangeListeners;
8593

8694
public TabbedOutputPanel() {
8795
txtOutputs = new HashMap<>();
8896
registeredOutputSources = new HashMap<>();
97+
outputSourceChangeListeners = new HashMap<>();
8998

9099
setLayout(new BorderLayout());
91100
setName(Constant.messages.getString("commonlib.output.panel.title"));
@@ -124,6 +133,10 @@ public void unregisterOutputSource(OutputSource source) {
124133
txtOutputs.remove(source.getName());
125134
}
126135
registeredOutputSources.remove(source.getName());
136+
ChangeListener listener = outputSourceChangeListeners.remove(source.getName());
137+
if (listener != null) {
138+
tabbedPanel.removeChangeListener(listener);
139+
}
127140
}
128141

129142
private void addNewOutputSource(String name) {
@@ -138,12 +151,24 @@ private void addNewOutputSource(String name) {
138151
var outputPanel = new AbstractPanel();
139152
outputPanel.setName(name);
140153
outputPanel.setLayout(new BorderLayout());
141-
if (attributes.containsKey(ATTRIBUTE_ICON)
142-
&& attributes.get(ATTRIBUTE_ICON) instanceof Icon) {
143-
outputPanel.setIcon((Icon) attributes.get(ATTRIBUTE_ICON));
144-
}
154+
Icon icon =
155+
attributes.containsKey(ATTRIBUTE_ICON)
156+
&& attributes.get(ATTRIBUTE_ICON) instanceof Icon
157+
? (Icon) attributes.get(ATTRIBUTE_ICON)
158+
: DOC_ICON;
159+
outputPanel.setIcon(icon);
160+
161+
var changeListener =
162+
new ChangeListener() {
163+
@Override
164+
public void stateChanged(javax.swing.event.ChangeEvent e) {
165+
markTabRead(outputPanel, icon);
166+
}
167+
};
168+
tabbedPanel.addChangeListener(changeListener);
169+
outputSourceChangeListeners.put(name, changeListener);
145170

146-
ZapTextArea txtOutput = buildOutputTextArea();
171+
ZapTextArea txtOutput = buildOutputTextArea(outputPanel, icon);
147172
JToolBar toolBar = buildToolbar(txtOutput, attributes);
148173
outputPanel.add(toolBar, BorderLayout.PAGE_START);
149174
var jScrollPane = new JScrollPane();
@@ -157,7 +182,7 @@ private void addNewOutputSource(String name) {
157182
txtOutputs.put(name, txtOutput);
158183
}
159184

160-
private static ZapTextArea buildOutputTextArea() {
185+
private ZapTextArea buildOutputTextArea(AbstractPanel outputPanel, Icon icon) {
161186
var txtOutput = new ZapTextArea();
162187
txtOutput.setEditable(false);
163188
txtOutput.setLineWrap(true);
@@ -174,6 +199,11 @@ public void mouseReleased(java.awt.event.MouseEvent e) {
174199
showPopupMenuIfTriggered(e);
175200
}
176201

202+
@Override
203+
public void mouseEntered(MouseEvent e) {
204+
markTabRead(outputPanel, icon);
205+
}
206+
177207
private void showPopupMenuIfTriggered(java.awt.event.MouseEvent e) {
178208
if (e.isPopupTrigger()) {
179209
View.getSingleton()
@@ -182,9 +212,49 @@ private void showPopupMenuIfTriggered(java.awt.event.MouseEvent e) {
182212
}
183213
}
184214
});
215+
216+
// Mark tab unread with a green dot when there's new text
217+
// Note that OverlayIcon only supports ImageIcons so this will not work for regular Icons
218+
// However, most (all?) icons in ZAP are ImageIcons so we should probably be fine
219+
if (icon instanceof ImageIcon imageIcon) {
220+
var overlayIcon = new OverlayIcon(imageIcon);
221+
overlayIcon.add(GREEN_BADGE_CORNER_ICON);
222+
txtOutput
223+
.getDocument()
224+
.addDocumentListener(
225+
new DocumentListener() {
226+
@Override
227+
public void insertUpdate(DocumentEvent e) {
228+
if (outputPanel.getIcon() != overlayIcon) {
229+
setTabIcon(outputPanel, overlayIcon);
230+
}
231+
}
232+
233+
@Override
234+
public void removeUpdate(DocumentEvent e) {}
235+
236+
@Override
237+
public void changedUpdate(DocumentEvent e) {}
238+
});
239+
}
240+
185241
return txtOutput;
186242
}
187243

244+
private void markTabRead(AbstractPanel outputPanel, Icon originalIcon) {
245+
if (outputPanel.isShowing() && outputPanel.equals(tabbedPanel.getSelectedComponent())) {
246+
setTabIcon(outputPanel, originalIcon);
247+
}
248+
}
249+
250+
private void setTabIcon(AbstractPanel outputPanel, Icon icon) {
251+
outputPanel.setIcon(icon);
252+
int index = tabbedPanel.indexOfComponent(outputPanel);
253+
if (index != -1) {
254+
tabbedPanel.setIconAt(index, icon);
255+
}
256+
}
257+
188258
private static JToolBar buildToolbar(ZapTextArea txtOutput, Map<String, Object> attributes) {
189259
List<AbstractButton> buttons = new ArrayList<>();
190260

@@ -269,6 +339,8 @@ public void appendAsync(String message, String sourceName) {
269339

270340
@Override
271341
public void clear() {
342+
outputSourceChangeListeners.values().forEach(tabbedPanel::removeChangeListener);
343+
outputSourceChangeListeners.clear();
272344
tabbedPanel.removeAll();
273345
txtOutputs.clear();
274346
addNewOutputSource(DEFAULT_OUTPUT_SOURCE_NAME);

0 commit comments

Comments
 (0)