/*
* Lilith - a log event viewer.
* Copyright (C) 2007-2017 Joern Huxhorn
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package de.huxhorn.lilith.swing.preferences;
import de.huxhorn.lilith.data.eventsource.EventWrapper;
import de.huxhorn.lilith.data.eventsource.SourceIdentifier;
import de.huxhorn.lilith.data.logging.LoggingEvent;
import de.huxhorn.lilith.prefs.LilithPreferences;
import de.huxhorn.lilith.swing.ApplicationPreferences;
import de.huxhorn.lilith.swing.LilithKeyStrokes;
import de.huxhorn.lilith.swing.MainFrame;
import de.huxhorn.sulky.conditions.Condition;
import de.huxhorn.sulky.swing.KeyStrokes;
import groovy.ui.Console;
import java.awt.BorderLayout;
import java.awt.CardLayout;
import java.awt.Component;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.event.ActionEvent;
import java.awt.event.KeyEvent;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.DefaultListModel;
import javax.swing.JButton;
import javax.swing.JDialog;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JTextPane;
import javax.swing.KeyStroke;
import javax.swing.ListCellRenderer;
import javax.swing.SwingConstants;
import javax.swing.border.EmptyBorder;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import javax.swing.text.BadLocationException;
import javax.swing.text.Document;
import org.apache.commons.io.IOUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class PreferencesDialog
extends JDialog
{
private static final long serialVersionUID = 8313102215860746241L;
public enum Panes
{
General("General"),
StartupShutdown("Startup & Shutdown"),
Windows("Windows"),
Sounds("Sounds"),
Sources("Sources"),
SourceLists("Source Lists"),
SourceFiltering("Source Filtering"),
Conditions("Conditions"),
LoggingLevels("Logging levels"),
AccessStatus("Access status types"),
Troubleshooting("Troubleshooting");
private final String title;
Panes(String title)
{
this.title = title;
}
public String getTitle()
{
return title;
}
}
public enum Actions
{
reinitializeDetailsViewFiles
}
private static final Map<Panes, String> PANE_TOOLTIPS=new HashMap<>();
static
{
PANE_TOOLTIPS.put(Panes.General, null);
PANE_TOOLTIPS.put(Panes.StartupShutdown, "Configure behavior at startup and shutdown.");
PANE_TOOLTIPS.put(Panes.Windows, null);
PANE_TOOLTIPS.put(Panes.Sounds, "Configure sounds or mute them entirely.");
PANE_TOOLTIPS.put(Panes.Sources, "Configure human-readable names for source IP addresses.");
PANE_TOOLTIPS.put(Panes.SourceLists, "Manage lists of sources. Those are used for Source filtering.");
PANE_TOOLTIPS.put(Panes.SourceFiltering, "Configure which hosts are allowed to connect.");
PANE_TOOLTIPS.put(Panes.Conditions, "Manage saved conditions and configure their styling.");
PANE_TOOLTIPS.put(Panes.LoggingLevels, "Configure the styling of the different logging levels.");
PANE_TOOLTIPS.put(Panes.AccessStatus, "Configure the styling of the different access status types.");
PANE_TOOLTIPS.put(Panes.Troubleshooting, "Got a problem? Broke something? Take a look here.");
}
private final Logger logger = LoggerFactory.getLogger(PreferencesDialog.class);
private ApplicationPreferences applicationPreferences;
private MainFrame mainFrame;
private JList<Panes> paneSelectionList;
private CardLayout cardLayout;
private JPanel content;
private GeneralPanel generalPanel;
private StartupShutdownPanel startupShutdownPanel;
private WindowsPanel windowsPanel;
private SoundsPanel soundsPanel;
private SourcesPanel sourcesPanel;
private SourceListsPanel sourceListsPanel;
private ConditionsPanel conditionsPanel;
private LoggingLevelPanel loggingLevelPanel;
private AccessStatusTypePanel accessStatusTypePanel;
private Map<String, String> sourceNames;
private Map<String, Set<String>> sourceLists;
private SourceFilteringPanel sourceFilteringPanel;
private String blackListName;
private String whiteListName;
private LilithPreferences.SourceFiltering sourceFiltering;
public PreferencesDialog(MainFrame mainFrame)
{
super(mainFrame, "Preferences");
this.mainFrame = mainFrame;
this.applicationPreferences = mainFrame.getApplicationPreferences();
createUI();
}
public ApplicationPreferences getApplicationPreferences()
{
return applicationPreferences;
}
public MainFrame getMainFrame()
{
return mainFrame;
}
private void createUI()
{
generalPanel = new GeneralPanel(this);
startupShutdownPanel = new StartupShutdownPanel(this);
windowsPanel = new WindowsPanel(this);
soundsPanel = new SoundsPanel(this);
sourcesPanel = new SourcesPanel(this);
sourceListsPanel = new SourceListsPanel(this);
sourceFilteringPanel = new SourceFilteringPanel(this);
conditionsPanel = new ConditionsPanel(this);
loggingLevelPanel = new LoggingLevelPanel(this);
accessStatusTypePanel = new AccessStatusTypePanel(this);
TroubleshootingPanel troubleshootingPanel = new TroubleshootingPanel(this);
DefaultListModel<Panes> paneSelectionListModel = new DefaultListModel<>();
for(Panes current : Panes.values())
{
paneSelectionListModel.addElement(current);
}
paneSelectionList = new JList<>(paneSelectionListModel);
paneSelectionList.setCellRenderer(new PaneSelectionListCellRenderer());
paneSelectionList.setSelectedValue(Panes.General, true);
paneSelectionList.addListSelectionListener(new PaneSelectionListener());
paneSelectionList.setBorder(new EmptyBorder(4, 4, 0, 4));
cardLayout = new CardLayout();
content = new JPanel(cardLayout);
content.setPreferredSize(new Dimension(600, 500));
content.setBorder(new EmptyBorder(4, 4, 0, 4));
content.add(generalPanel, Panes.General.toString());
content.add(startupShutdownPanel, Panes.StartupShutdown.toString());
content.add(windowsPanel, Panes.Windows.toString());
content.add(soundsPanel, Panes.Sounds.toString());
content.add(sourcesPanel, Panes.Sources.toString());
content.add(sourceListsPanel, Panes.SourceLists.toString());
content.add(sourceFilteringPanel, Panes.SourceFiltering.toString());
content.add(conditionsPanel, Panes.Conditions.toString());
content.add(loggingLevelPanel, Panes.LoggingLevels.toString());
content.add(accessStatusTypePanel, Panes.AccessStatus.toString());
content.add(troubleshootingPanel, Panes.Troubleshooting.toString());
// Main buttons
JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.CENTER));
OkAction okAction = new OkAction();
buttonPanel.add(new JButton(okAction));
buttonPanel.add(new JButton(new ApplyAction()));
buttonPanel.add(new JButton(new ResetAction()));
CancelAction cancelAction = new CancelAction();
buttonPanel.add(new JButton(cancelAction));
Container contentPane = getContentPane();
contentPane.setLayout(new BorderLayout());
contentPane.add(paneSelectionList, BorderLayout.WEST);
contentPane.add(content, BorderLayout.CENTER);
contentPane.add(buttonPanel, BorderLayout.SOUTH);
KeyStrokes.registerCommand(content, okAction, "OK_ACTION");
KeyStrokes.registerCommand(buttonPanel, okAction, "OK_ACTION");
KeyStrokes.registerCommand(content, cancelAction, "CANCEL_ACTION");
KeyStrokes.registerCommand(buttonPanel, cancelAction, "CANCEL_ACTION");
}
private void initUI()
{
generalPanel.initUI();
startupShutdownPanel.initUI();
windowsPanel.initUI();
soundsPanel.initUI();
sourceNames = applicationPreferences.getSourceNames();
if(sourceNames == null)
{
sourceNames = new HashMap<>();
}
else
{
sourceNames = new HashMap<>(sourceNames);
}
sourceLists = applicationPreferences.getSourceLists();
conditionsPanel.initUI();
sourcesPanel.initUI();
sourceListsPanel.initUI();
sourceFilteringPanel.initUI();
loggingLevelPanel.initUI();
accessStatusTypePanel.initUI();
}
public Map<String, String> getSourceNames()
{
return sourceNames;
}
public void setSourceName(String oldIdentifier, String newIdentifier, String sourceName)
{
if(sourceNames.containsKey(oldIdentifier))
{
sourceNames.remove(oldIdentifier);
}
sourceNames.put(newIdentifier, sourceName);
sourcesPanel.initUI();
sourceListsPanel.initUI();
}
public void setSourceList(String oldName, String newName, List<Source> sourceList)
{
if(sourceLists.containsKey(oldName))
{
sourceLists.remove(oldName);
}
Set<String> newList = new HashSet<>();
for(Source s : sourceList)
{
newList.add(s.getIdentifier());
}
sourceLists.put(newName, newList);
sourceListsPanel.initUI();
sourceFilteringPanel.initUI();
}
/**
*
* @param name the name of the source list
* @return the source list of the given name or an empty List
*/
public List<Source> getSourceList(String name)
{
Set<String> srcList = sourceLists.get(name);
if(srcList != null)
{
List<Source> result = new ArrayList<>();
for(String current : srcList)
{
Source s = new Source();
s.setIdentifier(current);
s.setName(getSourceName(current));
result.add(s);
}
Collections.sort(result);
return result;
}
return new ArrayList<>();
}
private String getSourceName(String identifier)
{
String result = sourceNames.get(identifier);
if(result == null)
{
result = identifier;
}
return result;
}
private void saveSettings()
{
generalPanel.saveSettings();
startupShutdownPanel.saveSettings();
windowsPanel.saveSettings();
soundsPanel.saveSettings();
conditionsPanel.saveSettings();
loggingLevelPanel.saveSettings();
accessStatusTypePanel.saveSettings();
applicationPreferences.setSourceNames(sourceNames);
applicationPreferences.setSourceLists(sourceLists);
applicationPreferences.setBlackListName(blackListName);
applicationPreferences.setWhiteListName(whiteListName);
applicationPreferences.setSourceFiltering(sourceFiltering);
}
private void resetSettings()
{
// just reinit from preferences, nobody would expect anything else...
initUI();
}
public void setVisible(boolean visible)
{
if(visible != isVisible())
{
if(visible)
{
initUI();
}
super.setVisible(visible);
}
}
public List<String> getSourceListNames()
{
return new ArrayList<>(sourceLists.keySet());
}
public void removeSourceList(String sourceListName)
{
if(sourceLists.containsKey(sourceListName))
{
sourceLists.remove(sourceListName);
sourceListsPanel.initUI();
sourceFilteringPanel.initUI();
}
}
public String getBlackListName()
{
if(blackListName == null)
{
blackListName = applicationPreferences.getBlackListName();
}
return blackListName;
}
public String getWhiteListName()
{
if(whiteListName == null)
{
whiteListName = applicationPreferences.getWhiteListName();
}
return whiteListName;
}
public LilithPreferences.SourceFiltering getSourceFiltering()
{
if(sourceFiltering == null)
{
sourceFiltering = applicationPreferences.getSourceFiltering();
}
return sourceFiltering;
}
public void setSourceFiltering(LilithPreferences.SourceFiltering sourceFiltering)
{
this.sourceFiltering = sourceFiltering;
}
public void setBlackListName(String blackListName)
{
this.blackListName = blackListName;
}
public void setWhiteListName(String whiteListName)
{
this.whiteListName = whiteListName;
}
public void setShowingTipOfTheDay(boolean showingTipOfTheDay)
{
startupShutdownPanel.setShowingTipOfTheDay(showingTipOfTheDay);
}
public void setCheckingForUpdate(boolean checkingForUpdate)
{
startupShutdownPanel.setCheckingForUpdate(checkingForUpdate);
}
public void setCheckingForSnapshot(boolean checkingForSnapshot)
{
startupShutdownPanel.setCheckingForSnapshot(checkingForSnapshot);
}
private class OkAction
extends AbstractAction
{
private static final long serialVersionUID = 3395474960394431088L;
OkAction()
{
super("Ok");
KeyStroke accelerator = LilithKeyStrokes.getKeyStroke(LilithKeyStrokes.ENTER);
putValue(Action.ACCELERATOR_KEY, accelerator);
putValue(Action.MNEMONIC_KEY, KeyEvent.VK_O);
}
public void actionPerformed(ActionEvent e)
{
saveSettings();
setVisible(false);
}
}
private class ApplyAction
extends AbstractAction
{
private static final long serialVersionUID = -4047672339764590549L;
ApplyAction()
{
super("Apply");
putValue(Action.MNEMONIC_KEY, KeyEvent.VK_A);
}
public void actionPerformed(ActionEvent e)
{
saveSettings();
}
}
private class ResetAction
extends AbstractAction
{
private static final long serialVersionUID = -7109027518233905200L;
ResetAction()
{
super("Reset");
putValue(Action.MNEMONIC_KEY, KeyEvent.VK_R);
}
public void actionPerformed(ActionEvent e)
{
resetSettings();
}
}
private class CancelAction
extends AbstractAction
{
private static final long serialVersionUID = 6933499606501725571L;
CancelAction()
{
super("Cancel");
KeyStroke accelerator = LilithKeyStrokes.getKeyStroke(LilithKeyStrokes.ESCAPE);
putValue(Action.ACCELERATOR_KEY, accelerator);
putValue(Action.MNEMONIC_KEY, KeyEvent.VK_C);
}
public void actionPerformed(ActionEvent e)
{
setVisible(false);
}
}
public void editSourceName(String sourceIdentifier)
{
showPane(Panes.Sources);
sourcesPanel.editSourceName(sourceIdentifier);
}
public void executeAction(Actions action)
{
if(logger.isInfoEnabled()) logger.info("Execute action {}.", action);
if(action == null)
{
return;
}
switch(action)
{
case reinitializeDetailsViewFiles:
reinitializeDetailsViewFiles();
break;
}
}
public void showPane(Panes pane)
{
if(pane == null)
{
return;
}
cardLayout.show(content, pane.toString());
if(!pane.equals(paneSelectionList.getSelectedValue()))
{
paneSelectionList.setSelectedValue(pane, true);
}
if(!isVisible())
{
mainFrame.showPreferencesDialog();
}
}
public void reinitializeDetailsViewFiles()
{
String dialogTitle = "Reinitialize details view files?";
String message = "This resets all details view related files, all manual changes will be lost!\nReinitialize details view right now?";
int result = JOptionPane.showConfirmDialog(this, message, dialogTitle,
JOptionPane.OK_CANCEL_OPTION, JOptionPane.QUESTION_MESSAGE);
// TODO: add "Show in Finder/Explorer" button if running on Mac/Windows
if(JOptionPane.OK_OPTION != result)
{
return;
}
applicationPreferences.initDetailsViewRoot(true);
}
public void reinitializeGroovyConditions()
{
String dialogTitle = "Reinitialize example groovy conditions?";
String message = "This overwrites all example groovy conditions. Other conditions are not changed!\nReinitialize example groovy conditions right now?";
int result = JOptionPane.showConfirmDialog(this, message, dialogTitle,
JOptionPane.OK_CANCEL_OPTION, JOptionPane.QUESTION_MESSAGE);
// TODO: add "Show in Finder/Explorer" button if running on Mac/Windows
if(JOptionPane.OK_OPTION != result)
{
return;
}
applicationPreferences.installExampleConditions();
}
public void reinitializeGroovyClipboardFormatters()
{
String dialogTitle = "Reinitialize example groovy clipboard formatters?";
String message = "This overwrites all example groovy clipboard formatters. Other clipboard formatters are not changed!\nReinitialize example groovy clipboard formatters right now?";
int result = JOptionPane.showConfirmDialog(this, message, dialogTitle,
JOptionPane.OK_CANCEL_OPTION, JOptionPane.QUESTION_MESSAGE);
// TODO: add "Show in Finder/Explorer" button if running on Mac/Windows
if(JOptionPane.OK_OPTION != result)
{
return;
}
applicationPreferences.installExampleClipboardFormatters();
}
public void deleteAllLogs()
{
String dialogTitle = "Delete all log files?";
String message = "This deletes *all* log files, even the Lilith logs and the global logs!\nDelete all log files right now?";
int result = JOptionPane.showConfirmDialog(this, message, dialogTitle,
JOptionPane.OK_CANCEL_OPTION, JOptionPane.QUESTION_MESSAGE);
if(JOptionPane.OK_OPTION != result)
{
return;
}
mainFrame.deleteAllLogs();
}
public void editDetailsFormatter()
{
Console console = new Console();
File messageViewRoot = applicationPreferences.getDetailsViewRoot();
File messageViewGroovyFile = new File(messageViewRoot, ApplicationPreferences.DETAILS_VIEW_GROOVY_FILENAME);
EventWrapper<LoggingEvent> eventWrapper = new EventWrapper<>(new SourceIdentifier("identifier", "secondaryIdentifier"), 17, new LoggingEvent());
console.setVariable("eventWrapper", eventWrapper);
console.setCurrentFileChooserDir(messageViewRoot);
String text = "";
if(!messageViewGroovyFile.isFile())
{
applicationPreferences.initDetailsViewRoot(true);
}
if(messageViewGroovyFile.isFile())
{
try(InputStream is = Files.newInputStream(messageViewGroovyFile.toPath()))
{
List<String> lines = IOUtils.readLines(is, StandardCharsets.UTF_8);
boolean isFirst = true;
StringBuilder textBuffer = new StringBuilder();
for(String s : lines)
{
if(isFirst)
{
isFirst = false;
}
else
{
textBuffer.append("\n");
}
textBuffer.append(s);
}
text = textBuffer.toString();
}
catch(IOException e)
{
if(logger.isInfoEnabled()) logger.info("Exception while reading '{}'.", messageViewGroovyFile.getAbsolutePath(), e);
}
}
else
{
if(logger.isWarnEnabled()) logger.warn("Failed to initialize detailsView file '{}'!", messageViewGroovyFile.getAbsolutePath());
}
console.run(); // initializes everything
console.setScriptFile(messageViewGroovyFile);
JTextPane inputArea = console.getInputArea();
//inputArea.setText(text);
Document doc = inputArea.getDocument();
try
{
doc.remove(0, doc.getLength());
doc.insertString(0, text, null);
}
catch(BadLocationException e)
{
if(logger.isWarnEnabled()) logger.warn("Exception while setting source!", e);
}
console.setDirty(false);
inputArea.setCaretPosition(0);
inputArea.requestFocusInWindow();
}
public void editCondition(Condition condition)
{
showPane(Panes.Conditions);
conditionsPanel.editCondition(condition);
}
private static class PaneSelectionListCellRenderer
implements ListCellRenderer<Panes>
{
private JLabel label;
PaneSelectionListCellRenderer()
{
label = new JLabel();
label.setOpaque(true);
label.setHorizontalAlignment(SwingConstants.LEFT);
label.setVerticalAlignment(SwingConstants.CENTER);
label.setBorder(new EmptyBorder(2,2,2,2));
}
@Override
public Component getListCellRendererComponent(JList<? extends Panes> list, Panes value, int index, boolean isSelected, boolean cellHasFocus)
{
if(isSelected)
{
label.setBackground(list.getSelectionBackground());
label.setForeground(list.getSelectionForeground());
}
else
{
label.setBackground(list.getBackground());
label.setForeground(list.getForeground());
}
String title = null;
String toolTipText = null;
if(value != null)
{
title = value.getTitle();
toolTipText = PANE_TOOLTIPS.get(value);
if(toolTipText == null)
{
toolTipText = title;
}
}
label.setText(title);
label.setToolTipText(toolTipText);
return label;
}
}
private class PaneSelectionListener
implements ListSelectionListener
{
@Override
public void valueChanged(ListSelectionEvent e)
{
Panes pane = paneSelectionList.getSelectedValue();
if(pane != null)
{
showPane(pane);
}
}
}
}