/* Copyright (c) 2008-2010, developers of the Ascension Log Visualizer * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or * sell copies of the Software, and to permit persons to whom * the Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ package com.googlecode.logVisualizer.gui.notetaker; import java.awt.BorderLayout; import java.awt.Component; import java.awt.Dimension; import java.awt.GridBagConstraints; import java.awt.GridBagLayout; import java.awt.GridLayout; import java.awt.Insets; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.io.File; import java.io.IOException; import java.util.Iterator; import javax.swing.ButtonGroup; import javax.swing.DefaultListModel; import javax.swing.JButton; import javax.swing.JComboBox; import javax.swing.JComponent; import javax.swing.JDialog; import javax.swing.JFileChooser; import javax.swing.JFrame; import javax.swing.JList; import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.JRadioButton; import javax.swing.JScrollPane; import javax.swing.JSplitPane; import javax.swing.JTextArea; import javax.swing.JTextField; import javax.swing.ListCellRenderer; import javax.swing.ListSelectionModel; import javax.swing.WindowConstants; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; import javax.swing.event.ListSelectionEvent; import javax.swing.event.ListSelectionListener; import org.jfree.ui.RefineryUtilities; import com.googlecode.logVisualizer.Settings; import com.googlecode.logVisualizer.logData.LogDataHolder; import com.googlecode.logVisualizer.logData.turn.TurnInterval; import com.googlecode.logVisualizer.logData.turn.turnAction.DayChange; import com.googlecode.logVisualizer.parser.LogsCreator; import com.googlecode.logVisualizer.util.LookAheadIterator; import com.googlecode.logVisualizer.util.textualLogs.TextLogCreator; import com.googlecode.logVisualizer.util.textualLogs.TextLogCreator.TextualLogVersion; /** * This class is ascension log notes editor, that gives the user a basic * interface to manage log notes. */ public final class Notetaker extends JFrame { /** * */ private static final long serialVersionUID = -8185868433565028732L; private final LogDataHolder log; private final JButton saveButton; private final TurnIntervalMenuList turnIntervalMenu; private final JTextArea notesArea; private TurnIntervalContainer activeTurnInterval; /** * Will show a dialog to let the user choose which turncounts should be * included inside the Notetaker and then show the actual Notetaker * interface based on that decision. */ public static void showNotetaker(final LogDataHolder log) { final JComboBox<LogInterval> selectionBox = new JComboBox<>(); selectionBox.setPreferredSize(new Dimension(400, selectionBox .getPreferredSize().height)); // Populate the selection combo box with all possible intervals. selectionBox .addItem(new LogInterval("Full log", -1, Integer.MAX_VALUE)); final LookAheadIterator<DayChange> index = new LookAheadIterator<>(log .getDayChanges().iterator()); while (index.hasNext()) { final DayChange dc = index.next(); final int nextDayChange = index.peek() != null ? index.peek() .getTurnNumber() : Integer.MAX_VALUE; selectionBox.addItem(new LogInterval("Day " + dc.getDayNumber(), dc .getTurnNumber(), nextDayChange)); } selectionBox.setSelectedIndex(0); // Show the interval selection dialog. JOptionPane .showMessageDialog( null, selectionBox, "Select what interval that should be displayed in the Notetaker", JOptionPane.QUESTION_MESSAGE); // Create and show the actual Notetaker interface with the chosen // interval. final LogInterval selection = (LogInterval) selectionBox .getSelectedItem(); if (selection.getName().equals("Full log")) { new Notetaker(log); } else { new Notetaker(log.getSubIntervalLogData(selection.getStartTurn(), selection.getEndTurn())); } } /** * Creates the notes editor frame. * * @param log * The log data whose notes should be managed. */ Notetaker(final LogDataHolder log) { super("Notetaker for " + log.getLogName()); this.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE); this.setLayout(new BorderLayout(5, 25)); this.log = log; this.saveButton = new JButton("Save log to file"); this.turnIntervalMenu = new TurnIntervalMenuList(log); this.notesArea = new JTextArea(); this.notesArea.setLineWrap(true); this.notesArea.setWrapStyleWord(true); this.getContentPane().add(this.createNotePanel(), BorderLayout.CENTER); this.getContentPane().add(this.createButtonPanel(), BorderLayout.SOUTH); this.addListeners(); this.turnIntervalMenu.setSelectedIndex(0); this.setSize(800, 600); RefineryUtilities.centerFrameOnScreen(this); this.setVisible(true); } private JComponent createNotePanel() { final JSplitPane splitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT); splitPane.setTopComponent(new JScrollPane(this.turnIntervalMenu)); splitPane.setBottomComponent(new JScrollPane(this.notesArea)); splitPane.setDividerLocation(400); return splitPane; } private JPanel createButtonPanel() { final JPanel buttonPanel = new JPanel(new GridLayout(1, 0, 200, 0)); final JButton closeButton = new JButton("Close window"); this.saveButton.setPreferredSize(new Dimension(0, 40)); closeButton.setPreferredSize(new Dimension(0, 40)); closeButton.addActionListener(new ActionListener() { @Override public void actionPerformed(final ActionEvent e) { if (Notetaker.this.activeTurnInterval != null) { Notetaker.this.activeTurnInterval .setNotes(Notetaker.this.notesArea.getText()); } Notetaker.this.dispose(); } }); buttonPanel.add(this.saveButton); buttonPanel.add(closeButton); return buttonPanel; } private void addListeners() { this.turnIntervalMenu .addListSelectionListener(new ListSelectionListener() { @Override public void valueChanged(final ListSelectionEvent lse) { if (!lse.getValueIsAdjusting()) { if (Notetaker.this.activeTurnInterval != null) { Notetaker.this.activeTurnInterval .setNotes(Notetaker.this.notesArea .getText()); } Notetaker.this.activeTurnInterval = Notetaker.this.turnIntervalMenu .getCurrentlySelectedTurnInterval(); Notetaker.this.notesArea .setText(Notetaker.this.activeTurnInterval .getNotes()); } } }); this.saveButton.addActionListener(new ActionListener() { @Override public void actionPerformed(final ActionEvent e) { if (Notetaker.this.activeTurnInterval != null) { Notetaker.this.activeTurnInterval .setNotes(Notetaker.this.notesArea.getText()); } new SaveDialog(); } }); } /** * ListCellRenderer for turn intervals, because the need to be able to show * multi-line strings as texts with multiple lines. */ private static final class TurnIntervalCellRenderer extends JTextArea implements ListCellRenderer<TurnInterval> { /** * */ private static final long serialVersionUID = -5038545398904224614L; TurnIntervalCellRenderer() { this.setLineWrap(true); this.setWrapStyleWord(true); this.setEditable(false); this.setOpaque(true); } @Override public Component getListCellRendererComponent( final JList<? extends TurnInterval> list, final TurnInterval value, final int index, final boolean isSelected, final boolean cellHasFocus) { this.setText(value.toString()); if (isSelected) { this.setBackground(list.getSelectionBackground()); this.setForeground(list.getSelectionForeground()); } else { this.setBackground(list.getBackground()); this.setForeground(list.getForeground()); } return this; } } /** * Just a little helper class make instantiation of the turn interval menu a * little nicer. */ private static final class TurnIntervalMenuList extends JList<TurnInterval> { /** * */ private static final long serialVersionUID = 2607220827265758012L; /** * Creates the turn interval menu. * * @param log * The log data whose notes should be managed. */ @SuppressWarnings({ "unchecked", "rawtypes" }) TurnIntervalMenuList(final LogDataHolder log) { super(new DefaultListModel<TurnInterval>()); this.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); this.setCellRenderer(new TurnIntervalCellRenderer()); final Iterator<String> turnRundownListIndex = TextLogCreator .getTurnRundownList(log).iterator(); for (final TurnInterval ti : log.getTurnsSpent()) { ((DefaultListModel) this.getModel()) .addElement(new TurnIntervalContainer(ti, turnRundownListIndex.next())); } } @SuppressWarnings("rawtypes") TurnIntervalContainer getCurrentlySelectedTurnInterval() { if (this.isSelectionEmpty()) { throw new IllegalStateException( "No turn interval is currently selected."); } return (TurnIntervalContainer) ((DefaultListModel) this.getModel()) .get(this.getSelectedIndex()); } } /** * The dialog that is seen when one wants to save the log to a file. */ private final class SaveDialog extends JDialog { /** * */ private static final long serialVersionUID = -2257321026657963976L; private final JTextField directoryLocationField = new JTextField( Settings.getSettingString("Parsed logs saving location")); private TextualLogVersion logVersion = TextualLogVersion.TEXT_LOG; /** * Creates and shows the save dialog in the centre of the screen. Please * note that this dialog is also modal on the notes editor frame. */ SaveDialog() { super(Notetaker.this, true); this.setTitle("Choose saving directory for the ascension log"); this.setLayout(new BorderLayout(0, 10)); this.getContentPane().add(this.createLogVersionChooserPanel(), BorderLayout.NORTH); this.getContentPane().add(this.createDirectoryFinderPanel(), BorderLayout.CENTER); this.getContentPane().add(this.createButtonPanel(), BorderLayout.SOUTH); this.pack(); this.setSize(500, this.getSize().height); RefineryUtilities.centerFrameOnScreen(this); this.setVisible(true); } private JPanel createLogVersionChooserPanel() { final JPanel panel = new JPanel(new GridLayout(1, 3, 10, 10)); final JRadioButton textButton = new JRadioButton("Text", true); final JRadioButton htmlButton = new JRadioButton("HTML", false); final JRadioButton bbcodeButton = new JRadioButton("BBCode", false); final ButtonGroup group = new ButtonGroup(); group.add(textButton); group.add(htmlButton); group.add(bbcodeButton); final ChangeListener listener = new ChangeListener() { @Override public void stateChanged(final ChangeEvent e) { if (htmlButton.isSelected()) { SaveDialog.this.logVersion = TextualLogVersion.HTML_LOG; } else if (bbcodeButton.isSelected()) { SaveDialog.this.logVersion = TextualLogVersion.BBCODE_LOG; } else { SaveDialog.this.logVersion = TextualLogVersion.TEXT_LOG; } } }; textButton.addChangeListener(listener); htmlButton.addChangeListener(listener); bbcodeButton.addChangeListener(listener); panel.add(textButton); panel.add(htmlButton); panel.add(bbcodeButton); return panel; } private JPanel createDirectoryFinderPanel() { final JPanel panel = new JPanel(new GridBagLayout()); final JButton directoryChooserButton = new JButton("Find Directory"); GridBagConstraints gbc; gbc = new GridBagConstraints(); gbc.gridx = 1; gbc.gridy = 0; gbc.anchor = GridBagConstraints.WEST; gbc.fill = GridBagConstraints.HORIZONTAL; gbc.weightx = 1.0; gbc.weighty = 1.0; gbc.insets = new Insets(10, 10, 5, 0); panel.add(this.directoryLocationField, gbc); gbc = new GridBagConstraints(); gbc.gridx = 2; gbc.gridy = 0; gbc.anchor = GridBagConstraints.EAST; gbc.insets = new Insets(10, 25, 5, 10); panel.add(directoryChooserButton, gbc); File logsDirectory = new File(this.directoryLocationField.getText()); if (!logsDirectory.exists()) { logsDirectory = null; } final JFileChooser directoryChooser = new JFileChooser( logsDirectory); directoryChooser .setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY); directoryChooserButton.addActionListener(new ActionListener() { @Override public void actionPerformed(final ActionEvent e) { final int state = directoryChooser.showOpenDialog(null); if (state == JFileChooser.APPROVE_OPTION) { SaveDialog.this.directoryLocationField .setText(directoryChooser.getSelectedFile() .getAbsolutePath()); } } }); return panel; } private JPanel createButtonPanel() { final JPanel buttonPanel = new JPanel(new GridLayout(1, 0, 100, 0)); final JButton closeButton = new JButton("Cancel"); final JButton saveButton = new JButton("OK"); saveButton.setPreferredSize(new Dimension(0, 30)); saveButton.addActionListener(new ActionListener() { @Override public void actionPerformed(final ActionEvent e) { try { String filePath = SaveDialog.this.directoryLocationField .getText(); if (!filePath.endsWith(File.separator)) { filePath += File.separator; } final File logsDest; if (SaveDialog.this.logVersion == TextualLogVersion.HTML_LOG) { logsDest = new File( filePath + LogsCreator .getParsedLogNameFromCondensedMafiaLog(Notetaker.this.log .getLogName()) + ".html"); } else { logsDest = new File( filePath + LogsCreator .getParsedLogNameFromCondensedMafiaLog(Notetaker.this.log .getLogName()) + ".txt"); } if (logsDest.exists()) { logsDest.delete(); } logsDest.createNewFile(); TextLogCreator.saveTextualLogToFile(Notetaker.this.log, logsDest, SaveDialog.this.logVersion); Settings.setSettingString( "Parsed logs saving location", SaveDialog.this.directoryLocationField .getText()); } catch (final IOException e1) { JOptionPane.showMessageDialog(null, "A problem occurred while creating the log.", "Error occurred", JOptionPane.ERROR_MESSAGE); e1.printStackTrace(); } SaveDialog.this.dispose(); } }); closeButton.setPreferredSize(new Dimension(0, 30)); closeButton.addActionListener(new ActionListener() { @Override public void actionPerformed(final ActionEvent e) { SaveDialog.this.dispose(); } }); buttonPanel.add(saveButton); buttonPanel.add(closeButton); return buttonPanel; } } private static final class LogInterval { private final String name; private final int startTurn; private final int endTurn; LogInterval(final String name, final int startTurn, final int endTurn) { if (name == null) { throw new IllegalArgumentException("The name must not be null."); } this.name = name; this.startTurn = startTurn; this.endTurn = endTurn; } String getName() { return this.name; } int getStartTurn() { return this.startTurn; } int getEndTurn() { return this.endTurn; } @Override public String toString() { return this.name; } @Override public int hashCode() { final int prime = 31; int result = 8421; result = (prime * result) + this.startTurn; result = (prime * result) + this.endTurn; result = (prime * result) + this.name.hashCode(); return result; } @Override public boolean equals(final Object obj) { if (this == obj) { return true; } if (obj == null) { return false; } if (!(obj instanceof LogInterval)) { return false; } final LogInterval other = (LogInterval) obj; if (this.startTurn != other.startTurn) { return false; } if (this.endTurn != other.endTurn) { return false; } if (this.name == null) { if (other.name != null) { return false; } } else if (!this.name.equals(other.name)) { return false; } return true; } } }