Skip to content

Commit 396f11c

Browse files
committed
LoggingPanel: add gui to filter log messages by source and level
1 parent 75a1ab1 commit 396f11c

File tree

2 files changed

+374
-12
lines changed

2 files changed

+374
-12
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,316 @@
1+
/*
2+
* #%L
3+
* SciJava UI components for Java Swing.
4+
* %%
5+
* Copyright (C) 2010 - 2017 Board of Regents of the University of
6+
* Wisconsin-Madison.
7+
* %%
8+
* Redistribution and use in source and binary forms, with or without
9+
* modification, are permitted provided that the following conditions are met:
10+
*
11+
* 1. Redistributions of source code must retain the above copyright notice,
12+
* this list of conditions and the following disclaimer.
13+
* 2. Redistributions in binary form must reproduce the above copyright notice,
14+
* this list of conditions and the following disclaimer in the documentation
15+
* and/or other materials provided with the distribution.
16+
*
17+
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
18+
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19+
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20+
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE
21+
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
22+
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
23+
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
24+
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
25+
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
26+
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27+
* POSSIBILITY OF SUCH DAMAGE.
28+
* #L%
29+
*/
30+
31+
package org.scijava.ui.swing.console;
32+
33+
import java.awt.*;
34+
import java.util.Arrays;
35+
import java.util.Collections;
36+
import java.util.EnumSet;
37+
import java.util.HashMap;
38+
import java.util.HashSet;
39+
import java.util.Map;
40+
import java.util.Set;
41+
import java.util.StringJoiner;
42+
import java.util.function.Consumer;
43+
import java.util.function.Function;
44+
import java.util.function.Predicate;
45+
import java.util.stream.Stream;
46+
47+
import javax.swing.*;
48+
import javax.swing.tree.DefaultMutableTreeNode;
49+
import javax.swing.tree.DefaultTreeCellRenderer;
50+
import javax.swing.tree.DefaultTreeModel;
51+
import javax.swing.tree.TreePath;
52+
53+
import net.miginfocom.swing.MigLayout;
54+
55+
import org.scijava.log.LogLevel;
56+
import org.scijava.log.LogMessage;
57+
import org.scijava.log.LogSource;
58+
59+
/**
60+
* {@link LogSourcesPanel} is a {@link JPanel}, that contains a tree view, where
61+
* {@link LogSource}s are listed. For each log source the user may select a set
62+
* of visible log levels.
63+
*
64+
* @author Matthias Arzt
65+
*/
66+
class LogSourcesPanel extends JPanel {
67+
68+
private static final EnumSet<Level> VALID_LEVELS = EnumSet.range(Level.ERROR,
69+
Level.TRACE);
70+
71+
private final JPopupMenu menu = new JPopupMenu();
72+
73+
private final Map<LogSource, Item> sourceItems = new HashMap<>();
74+
private JTree tree;
75+
private DefaultTreeModel treeModel;
76+
private DefaultMutableTreeNode rootNode = new DefaultMutableTreeNode("Log Sources:");
77+
78+
private Predicate<LogMessage> filter = message -> true;
79+
private Runnable changeListener = null;
80+
81+
// -- constructor --
82+
83+
LogSourcesPanel(JButton reloadButton) {
84+
initMenu();
85+
initTreeView();
86+
JButton visibilityButton = initVisibilityButton();
87+
setLayout(new MigLayout("inset 0", "[grow]", "[][grow][]"));
88+
add(new JLabel("Log Sources:"), "grow, wrap");
89+
add(new JScrollPane(tree), "grow, wrap");
90+
add(new JLabel(""), "split 3, grow");
91+
add(reloadButton);
92+
add(visibilityButton);
93+
}
94+
95+
// -- LogLevelPanel methods --
96+
97+
public void setChangeListener(Runnable changeListener) {
98+
this.changeListener = changeListener;
99+
}
100+
101+
public Predicate<LogMessage> getFilter() {
102+
if (filter == null) updateFilter();
103+
return filter;
104+
}
105+
106+
public void updateSources(Set<LogSource> logSources) {
107+
for (LogSource sources : logSources)
108+
getItem(sources);
109+
treeModel.reload();
110+
}
111+
112+
// -- Helper methods --
113+
114+
private void initTreeView() {
115+
treeModel = new DefaultTreeModel(rootNode);
116+
tree = new JTree(treeModel);
117+
tree.setRootVisible(false);
118+
DefaultTreeCellRenderer cellRenderer = new DefaultTreeCellRenderer();
119+
cellRenderer.setIcon(null);
120+
cellRenderer.setLeafIcon(null);
121+
cellRenderer.setOpenIcon(null);
122+
tree.setCellRenderer(cellRenderer);
123+
tree.setComponentPopupMenu(menu);
124+
tree.setEditable(false);
125+
tree.setShowsRootHandles(true);
126+
}
127+
128+
private JButton initVisibilityButton() {
129+
JButton button = new JButton("visibility");
130+
button.addActionListener(a -> menu.show(button, 0, button.getHeight()));
131+
return button;
132+
}
133+
134+
private void initMenu() {
135+
EnumSet<Level> levels = EnumSet.range(Level.ERROR, Level.TRACE);
136+
addMenuItemPerLevel(EnumSet.of(Level.TRACE), level -> "show all",
137+
this::onShowErrorUpToClicked);
138+
addMenuItemPerLevel(EnumSet.of(Level.NONE), level -> "hide all",
139+
this::onShowNoneClicked);
140+
menu.addSeparator();
141+
addMenuItemPerLevel(levels, level -> "show " + level.toString(),
142+
this::onShowLogLevelClicked);
143+
menu.addSeparator();
144+
addMenuItemPerLevel(levels, level -> "hide " + level.toString(),
145+
this::onHideLogLevelClicked);
146+
menu.addSeparator();
147+
addMenuItemPerLevel(levels, LogSourcesPanel::listLevelsErrorTo,
148+
this::onShowErrorUpToClicked);
149+
}
150+
151+
private void addMenuItemPerLevel(EnumSet<Level> levels,
152+
Function<Level, String> title, Consumer<Level> consumer)
153+
{
154+
for (Level level : levels) {
155+
JMenuItem menuItem = new JMenuItem(title.apply(level));
156+
menuItem.addActionListener(a -> consumer.accept(level));
157+
menu.add(menuItem);
158+
}
159+
}
160+
161+
private void onShowLogLevelClicked(Level level) {
162+
modifyFilterOnSelection(filter -> {
163+
filter.add(level);
164+
return filter;
165+
});
166+
}
167+
168+
private void onHideLogLevelClicked(Level level) {
169+
modifyFilterOnSelection(filter -> {
170+
filter.remove(level);
171+
return filter;
172+
});
173+
}
174+
175+
private void onShowErrorUpToClicked(Level level) {
176+
EnumSet<Level> enumSet = EnumSet.range(Level.ERROR, level);
177+
modifyFilterOnSelection(ignore -> enumSet);
178+
}
179+
180+
private void onShowNoneClicked(Level level) {
181+
EnumSet<Level> enumSet = EnumSet.noneOf(Level.class);
182+
modifyFilterOnSelection(ignore -> enumSet);
183+
}
184+
185+
private void modifyFilterOnSelection(
186+
Function<EnumSet<Level>, EnumSet<Level>> itemConsumer)
187+
{
188+
getSelectedItems().forEach(item -> {
189+
item.setLevelSet(itemConsumer.apply(item.levels));
190+
treeModel.nodeChanged(item.node);
191+
});
192+
settingsChanged();
193+
}
194+
195+
private Stream<Item> getSelectedItems() {
196+
TreePath[] selectionPaths = tree.getSelectionPaths();
197+
if (selectionPaths == null) return Collections.<Item> emptyList().stream();
198+
return Stream.of(selectionPaths).map(path -> getItem(
199+
(DefaultMutableTreeNode) path.getLastPathComponent()));
200+
}
201+
202+
private void settingsChanged() {
203+
filter = null;
204+
if (changeListener != null) changeListener.run();
205+
}
206+
207+
private Item getItem(DefaultMutableTreeNode node) {
208+
return (Item) node.getUserObject();
209+
}
210+
211+
private Item getItem(LogSource source) {
212+
Item existing = sourceItems.get(source);
213+
return existing == null ? initItem(source) : existing;
214+
}
215+
216+
private Item initItem(LogSource source) {
217+
Item item = new Item(source);
218+
sourceItems.put(item.source, item);
219+
DefaultMutableTreeNode parent = source.isRoot() ?
220+
rootNode : getItem(source.parent()).node;
221+
parent.add(item.node);
222+
return item;
223+
}
224+
225+
private void updateFilter() {
226+
Map<LogSource, EnumSet<Level>> filterData = new HashMap<>();
227+
EnumSet<Level> none = EnumSet.noneOf(Level.class);
228+
sourceItems.forEach((name, item) -> filterData.put(name, item.visible
229+
? item.levels : none));
230+
filter = message -> {
231+
EnumSet<Level> logLevels = filterData.get(message.source());
232+
if (logLevels == null) return true;
233+
return logLevels.contains(Level.of(message.level()));
234+
};
235+
}
236+
237+
private static String listLevelsErrorTo(Level max) {
238+
return enumSetToString(EnumSet.range(Level.ERROR, max));
239+
}
240+
241+
private static String enumSetToString(EnumSet<Level> levels) {
242+
StringJoiner s = new StringJoiner(", ");
243+
for (Level level : levels)
244+
s.add(level.toString());
245+
return s.toString();
246+
}
247+
248+
// -- Helper classes --
249+
250+
private static class Item {
251+
252+
DefaultMutableTreeNode node;
253+
LogSource source;
254+
EnumSet<Level> levels;
255+
boolean visible;
256+
257+
public Item(LogSource source) {
258+
this.levels = EnumSet.range(Level.ERROR, Level.TRACE);
259+
this.source = source;
260+
this.visible = true;
261+
this.node = new DefaultMutableTreeNode(this);
262+
}
263+
264+
public String toString() {
265+
String name = source.isRoot() ? "ROOT" : source.name();
266+
return name + " " + getLevelString();
267+
}
268+
269+
private String getLevelString() {
270+
if (levels.equals(VALID_LEVELS)) return "";
271+
if (levels.isEmpty()) return "[hidden]";
272+
return levels.toString();
273+
}
274+
275+
public void setLevelSet(EnumSet<Level> value) {
276+
levels = value.clone();
277+
}
278+
}
279+
280+
private enum Level {
281+
NONE, ERROR, WARN, INFO, DEBUG, TRACE;
282+
283+
static Level of(int x) {
284+
switch (x) {
285+
case LogLevel.NONE:
286+
return NONE;
287+
case LogLevel.ERROR:
288+
return ERROR;
289+
case LogLevel.WARN:
290+
return WARN;
291+
case LogLevel.INFO:
292+
return INFO;
293+
case LogLevel.DEBUG:
294+
return DEBUG;
295+
default:
296+
if (x >= LogLevel.TRACE) return TRACE;
297+
else throw new IllegalArgumentException();
298+
}
299+
}
300+
}
301+
302+
public static void main(String... args) {
303+
JFrame frame = new JFrame();
304+
LogSourcesPanel logLevelPanel = new LogSourcesPanel(new JButton("dummy"));
305+
LogSource root = LogSource.newRoot();
306+
Set<LogSource> loggers = new HashSet<>(Arrays.asList(
307+
root.subSource("Hello:World"),
308+
root.subSource("Hello:Universe"),
309+
root.subSource("Hello:foo:bar")
310+
));
311+
logLevelPanel.updateSources(loggers);
312+
frame.getContentPane().add(logLevelPanel, BorderLayout.CENTER);
313+
frame.pack();
314+
frame.setVisible(true);
315+
}
316+
}

0 commit comments

Comments
 (0)