--- /dev/null
+package org.simantics.logging.ui;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.function.Consumer;
+import java.util.function.Function;
+import java.util.function.Predicate;
+
+import org.eclipse.jface.dialogs.IDialogConstants;
+import org.eclipse.jface.dialogs.IDialogSettings;
+import org.eclipse.jface.dialogs.TrayDialog;
+import org.eclipse.jface.layout.GridDataFactory;
+import org.eclipse.jface.layout.GridLayoutFactory;
+import org.eclipse.jface.layout.TableColumnLayout;
+import org.eclipse.jface.resource.FontDescriptor;
+import org.eclipse.jface.resource.JFaceResources;
+import org.eclipse.jface.viewers.ArrayContentProvider;
+import org.eclipse.jface.viewers.CellEditor;
+import org.eclipse.jface.viewers.ColumnLabelProvider;
+import org.eclipse.jface.viewers.ColumnWeightData;
+import org.eclipse.jface.viewers.ComboBoxCellEditor;
+import org.eclipse.jface.viewers.EditingSupport;
+import org.eclipse.jface.viewers.TableViewer;
+import org.eclipse.jface.viewers.TableViewerColumn;
+import org.eclipse.jface.viewers.TextCellEditor;
+import org.eclipse.jface.viewers.Viewer;
+import org.eclipse.jface.viewers.ViewerCell;
+import org.eclipse.jface.viewers.ViewerFilter;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.custom.CCombo;
+import org.eclipse.swt.graphics.Font;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.swt.widgets.Display;
+import org.eclipse.swt.widgets.Shell;
+import org.eclipse.swt.widgets.Text;
+import org.simantics.logging.LogConfigurator;
+import org.simantics.logging.LoggerLevel;
+
+public class LoggerManagementDialog extends TrayDialog {
+
+ private static final String DIALOG = LoggerManagementDialog.class.getSimpleName(); //$NON-NLS-1$
+
+ private static final String[] LEVELS = {
+ "ERROR",
+ "WARN",
+ "INFO",
+ "DEBUG",
+ "TRACE",
+ };
+
+ private static final Map<String, Integer> LEVEL_TO_INDEX = new HashMap<>();
+
+ static {
+ for (int i = 0; i < LEVELS.length; i++) {
+ LEVEL_TO_INDEX.put(LEVELS[i], i);
+ }
+ }
+
+ /**
+ * ID for Apply buttons.
+ */
+ private int APPLY_ID = IDialogConstants.CLIENT_ID + 1;
+
+ /**
+ * The label for Apply buttons.
+ */
+ private String APPLY_LABEL = JFaceResources.getString("apply"); //$NON-NLS-1$
+
+ private TableViewer tableViewer;
+ private List<LoggerLevel> configuration;
+ private IDialogSettings dialogBoundsSettings;
+
+ private Consumer<List<LoggerLevel>> applyFunction;
+
+ public LoggerManagementDialog(Shell shell, Consumer<List<LoggerLevel>> applyFunction) {
+ super(shell);
+ reloadConfiguration();
+ this.applyFunction = applyFunction;
+
+ IDialogSettings settings = Activator.getDefault().getDialogSettings();
+ dialogBoundsSettings = settings.getSection(DIALOG);
+ if (dialogBoundsSettings == null)
+ dialogBoundsSettings = settings.addNewSection(DIALOG);
+ }
+
+ private void reloadConfiguration() {
+ this.configuration = LogConfigurator.listConfiguredLoggers();
+ addEmptyRow();
+ }
+
+ private void addEmptyRow() {
+ this.configuration.add(new LoggerLevel("", LEVELS[2]));
+ }
+
+ @Override
+ protected IDialogSettings getDialogBoundsSettings() {
+ return dialogBoundsSettings;
+ }
+
+ @Override
+ protected void configureShell(Shell newShell) {
+ super.configureShell(newShell);
+ newShell.setText("Manage Loggers");
+ newShell.setMinimumSize(800, 600);
+ }
+
+ @Override
+ protected boolean isResizable() {
+ return true;
+ }
+
+ @Override
+ protected void createButtonsForButtonBar(Composite parent) {
+ createButton(parent, IDialogConstants.OK_ID, IDialogConstants.OK_LABEL, true);
+ createButton(parent, APPLY_ID, APPLY_LABEL, false);
+ createButton(parent, IDialogConstants.CANCEL_ID, IDialogConstants.CANCEL_LABEL, false);
+ }
+
+ @Override
+ protected void buttonPressed(int buttonId) {
+ if (buttonId == IDialogConstants.CLOSE_ID) {
+ super.buttonPressed(IDialogConstants.CANCEL_ID);
+ } else if (buttonId == APPLY_ID) {
+ if (applyFunction != null) {
+ applyFunction.accept(configuration);
+ reloadConfiguration();
+ setInput();
+ }
+ } else {
+ super.buttonPressed(buttonId);
+ }
+ }
+
+ @Override
+ protected Control createDialogArea(Composite parent) {
+ final Composite composite = (Composite) super.createDialogArea(parent);
+ GridLayoutFactory.fillDefaults().margins(10, 10).numColumns(1).applyTo(composite);
+ GridDataFactory.fillDefaults().grab(true, true).applyTo(composite);
+
+ Composite tableComposite = new Composite(composite, SWT.NONE);
+ TableColumnLayout tcl = new TableColumnLayout();
+ tableComposite.setLayout(tcl);
+ GridDataFactory.fillDefaults().grab(true, true).applyTo(tableComposite);
+
+ tableViewer = new TableViewer(tableComposite, SWT.BORDER | SWT.FULL_SELECTION | SWT.MULTI);
+
+ Display display = getShell().getDisplay();
+
+ // Fonts
+ final Font systemFont = display.getSystemFont();
+ final Font italic = FontDescriptor.createFrom(systemFont).setStyle(SWT.ITALIC).createFont(display);
+ final Font bold = FontDescriptor.createFrom(systemFont).setStyle(SWT.BOLD).createFont(display);
+ tableViewer.getTable().addDisposeListener(e -> {
+ italic.dispose();
+ bold.dispose();
+ });
+
+ Function<LoggerLevel, Font> fontFunction = l -> {
+ if (l.isLoggerDefined()) {
+ if (l.levelChanged()) {
+ return bold;
+ }
+ return systemFont;
+ }
+ return italic;
+ };
+
+ // Column 1: logger name
+ TableViewerColumn column1 = new TableViewerColumn(tableViewer, SWT.NONE);
+ column1.getColumn().setWidth(300);
+ column1.getColumn().setText("Logger");
+ column1.getColumn().setToolTipText("Package Name of Logger");
+ column1.setLabelProvider(new ColumnLabelProvider() {
+ @Override
+ public void update(ViewerCell cell) {
+ LoggerLevel l = (LoggerLevel) cell.getElement();
+ cell.setText(l.getName());
+ cell.setFont(fontFunction.apply(l));
+ }
+ });
+ column1.setEditingSupport(new EditingSupport(tableViewer) {
+ CellEditor editor = new TextCellEditor(tableViewer.getTable());
+ @Override
+ protected CellEditor getCellEditor(Object element) {
+ return editor;
+ }
+ @Override
+ protected boolean canEdit(Object element) {
+ LoggerLevel l = (LoggerLevel) element;
+ return !l.isLoggerDefined();
+ }
+ @Override
+ protected Object getValue(Object element) {
+ return ((LoggerLevel)element).getName();
+ }
+ @Override
+ protected void setValue(Object element, Object value) {
+ LoggerLevel l = (LoggerLevel) element;
+ String s = ((String) value).trim();
+
+ boolean sameNameExists = !s.isEmpty() && configuration.stream().anyMatch(ll -> ll != l && ll.getName().equals(s));
+
+ // Prevent providing the same package twice
+ if (sameNameExists)
+ return;
+
+ String previousName = l.getName();
+ l.setName(s);
+ if (s.isEmpty()) {
+ int index = configuration.indexOf(l);
+ if (index >= 0 && index < (configuration.size()-1)) {
+ configuration.remove(index);
+ }
+ } else if (previousName.isEmpty()) {
+ addEmptyRow();
+ }
+ setInput();
+ }
+ });
+
+ // Column 2: logging level of logger
+ TableViewerColumn column2 = new TableViewerColumn(tableViewer, SWT.NONE);
+ column2.getColumn().setWidth(100);
+ column2.getColumn().setText("Log Level");
+ column2.getColumn().setToolTipText("Logging Level for Package and Subpackages");
+ column2.setLabelProvider(new ColumnLabelProvider() {
+ @Override
+ public void update(ViewerCell cell) {
+ LoggerLevel l = (LoggerLevel) cell.getElement();
+ cell.setText(l.getLevel());
+ cell.setFont(fontFunction.apply(l));
+ }
+ });
+ column2.setEditingSupport(new EditingSupport(tableViewer) {
+ @Override
+ protected boolean canEdit(Object element) {
+ return true;
+ }
+ @Override
+ protected CellEditor getCellEditor(Object element) {
+ return new ComboBoxCellEditor(tableViewer.getTable(), LEVELS, SWT.READ_ONLY) {
+ @Override
+ protected Control createControl(Composite parent) {
+ CCombo combo = (CCombo) super.createControl(parent);
+ // The only way found to actually open the combo box list
+ // right away when starting to edit.
+ combo.getDisplay().asyncExec(() -> {
+ if (!combo.isDisposed())
+ combo.setListVisible(true);
+ });
+ return combo;
+ }
+ };
+ }
+ @Override
+ protected Object getValue(Object element) {
+ LoggerLevel l = (LoggerLevel) element;
+ return LEVEL_TO_INDEX.get(l.getLevel());
+ }
+ @Override
+ protected void setValue(Object element, Object value) {
+ LoggerLevel l = (LoggerLevel)element;
+ l.setLevel(LEVELS[(Integer) value]);
+ getViewer().update(element, null);
+ }
+ });
+
+ tcl.setColumnData(column1.getColumn(), new ColumnWeightData(5, 300));
+ tcl.setColumnData(column2.getColumn(), new ColumnWeightData(1, 150));
+
+ // Decorations
+ tableViewer.getTable().setHeaderVisible(true);
+ tableViewer.getTable().setLinesVisible(true);
+
+ // Table content
+ tableViewer.setContentProvider(ArrayContentProvider.getInstance());
+
+ tableViewer.getTable().addListener(SWT.KeyDown, e -> {
+ if (e.keyCode == SWT.DEL) {
+ @SuppressWarnings("unchecked")
+ List<Object> s = tableViewer.getStructuredSelection().toList();
+ Predicate<Object> removable = l -> {
+ LoggerLevel ll = (LoggerLevel) l;
+ return ll.isLoggerDefined() && !ll.getName().isEmpty();
+ };
+ if (s.stream().allMatch(removable)) {
+ s.forEach(configuration::remove);
+ setInput();
+ }
+ }
+ });
+
+ Text filterText = new Text(composite, SWT.FLAT | SWT.BORDER);
+ GridDataFactory.fillDefaults().grab(true, false).applyTo(filterText);
+ filterText.setToolTipText("Package Name Filter");
+ filterText.moveAbove(tableComposite);
+ filterText.addModifyListener(e -> {
+ String filter = filterText.getText().trim().toLowerCase();
+ ViewerFilter[] filters = {};
+ if (!filter.isEmpty()) {
+ filters = new ViewerFilter[] {
+ new ViewerFilter() {
+ @Override
+ public boolean select(Viewer viewer, Object parentElement, Object element) {
+ LoggerLevel l = (LoggerLevel) element;
+ return !l.isLoggerDefined() || l.levelChanged()
+ ? true
+ : l.getName().toLowerCase().contains(filter);
+ }
+ }
+ };
+ };
+ scheduleSetFilters(filters);
+ });
+
+ setInput();
+
+ return composite;
+ }
+
+ private void setInput() {
+ tableViewer.setInput(configuration.toArray(new LoggerLevel[configuration.size()]));
+ }
+
+ public List<LoggerLevel> getConfiguration() {
+ return configuration;
+ }
+
+ private final AtomicReference<ViewerFilter[]> filtersToSet = new AtomicReference<>();
+ private final Runnable setFilters = () -> {
+ if (!tableViewer.getTable().isDisposed()) {
+ ViewerFilter[] fs = filtersToSet.getAndSet(null);
+ if (fs != null) {
+ tableViewer.getTable().setRedraw(false);
+ tableViewer.setFilters(fs);
+ tableViewer.getTable().setRedraw(true);
+ }
+ }
+ };
+
+ protected void scheduleSetFilters(ViewerFilter[] array) {
+ filtersToSet.set(array);
+ getShell().getDisplay().timerExec(250, setFilters);
+ }
+
+}
--- /dev/null
+package org.simantics.logging;
+
+import java.util.Objects;
+
+import org.slf4j.Logger;
+
+/**
+ * @author Tuukka Lehtonen
+ * @since 1.47.0
+ */
+public class LoggerLevel implements Comparable<LoggerLevel> {
+
+ private final Logger logger;
+ private final String originalLevel;
+ private String loggerName;
+ private String level;
+
+ public LoggerLevel(Logger logger, String level) {
+ Objects.requireNonNull(logger, "logger");
+ Objects.requireNonNull(level, "level");
+ this.logger = logger;
+ this.originalLevel = level;
+ this.loggerName = logger.getName();
+ this.level = level;
+ }
+
+ public LoggerLevel(String loggerName, String level) {
+ Objects.requireNonNull(loggerName, "loggerName");
+ Objects.requireNonNull(level, "level");
+ this.logger = null;
+ this.originalLevel = null;
+ this.loggerName = loggerName;
+ this.level = level;
+ }
+
+ Logger getLogger() {
+ return logger;
+ }
+
+ public String getName() {
+ return loggerName;
+ }
+
+ public LoggerLevel setName(String name) {
+ if (logger != null)
+ throw new UnsupportedOperationException("Cannot rename already created logger " + loggerName + " to " + name);
+ Objects.requireNonNull(name, "name");
+ this.loggerName = name;
+ return this;
+ }
+
+ public String getLevel() {
+ return level;
+ }
+
+ public LoggerLevel setLevel(String level) {
+ Objects.requireNonNull(level, "level");
+ this.level = level;
+ return this;
+ }
+
+ public boolean levelChanged() {
+ return !Objects.equals(originalLevel, level);
+ }
+
+ public boolean isLoggerDefined() {
+ return logger != null;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(level, logger, loggerName);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (obj == null)
+ return false;
+ if (getClass() != obj.getClass())
+ return false;
+ LoggerLevel other = (LoggerLevel) obj;
+ return Objects.equals(level, other.level)
+ && Objects.equals(logger, other.logger)
+ && Objects.equals(loggerName, other.loggerName);
+ }
+
+ @Override
+ public int compareTo(LoggerLevel o) {
+ return loggerName.compareToIgnoreCase(o.loggerName);
+ }
+
+}