package chatty.gui.components.settings;
import chatty.gui.GuiUtil;
import chatty.gui.components.LinkLabel;
import chatty.gui.components.LinkLabelListener;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.Window;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import javax.swing.JButton;
import javax.swing.JDialog;
import javax.swing.JLabel;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.JToggleButton;
import javax.swing.SwingUtilities;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
/**
* Simple text editor dialog that can be opened with a description of the edit,
* a preset value and an optional help text. Auto adjusts the size of the input
* box.
*
* It should probably be possible to use one instance of this for several
* purposes, because the most important stuff is set everytime the dialog is
* opened, however some stuff (like whether the help is currently shown) remains
* the same.
*
* @author tduva
*/
public class Editor {
private final JDialog dialog;
private final JLabel label;
private final JTextArea input;
private final JButton okButton = new JButton("Save");
private final JButton cancelButton = new JButton("Cancel");
private final JButton testButton = new JButton("Test");
private final JToggleButton toggleInfoButton = new JToggleButton("Help");
private final Window parent;
private final LinkLabel info;
private DataFormatter<String> formatter;
private Tester tester;
private boolean allowEmpty;
private String result;
public Editor(Window parent) {
dialog = new JDialog(parent);
this.parent = parent;
dialog.setTitle("Input");
dialog.setModal(true);
GuiUtil.installEscapeCloseOperation(dialog);
dialog.setLayout(new GridBagLayout());
GridBagConstraints gbc;
// Should contain something to set correct minimum size in constructor
label = new JLabel("abc");
gbc = GuiUtil.makeGbc(0, 0, 2, 1, GridBagConstraints.WEST);
gbc.insets = new Insets(5, 5, 5, 5);
dialog.add(label, gbc);
gbc = GuiUtil.makeGbc(2, 0, 1, 1, GridBagConstraints.EAST);
testButton.setMargin(GuiUtil.SMALL_BUTTON_INSETS);
dialog.add(testButton, gbc);
testButton.setVisible(false);
gbc = GuiUtil.makeGbc(0, 1, 3, 1);
gbc.fill = GridBagConstraints.BOTH;
gbc.weightx = 1;
gbc.weighty = 1;
gbc.insets = new Insets(0, 7, 5, 7);
input = new JTextArea();
input.getDocument().addDocumentListener(new ChangeListener());
input.setMargin(new Insets(2, 2, 2, 2));
setAllowLinebreaks(false);
input.setLineWrap(true);
input.setWrapStyleWord(true);
input.setText("test");
// Use monospaced font for easier editing of some kinds of text
input.setFont(Font.decode(Font.MONOSPACED));
dialog.add(new JScrollPane(input), gbc);
gbc = GuiUtil.makeGbc(0, 4, 3, 1);
gbc.insets = new Insets(5, 8, 8, 8);
gbc.anchor = GridBagConstraints.CENTER;
info = new LinkLabel("", null);
dialog.add(info, gbc);
/**
* Set help invisible by default (do it in constructor to get correct
* preferred size when setting minimum size)
*/
info.setVisible(false);
gbc = GuiUtil.makeGbc(0, 3, 1, 1);
dialog.add(toggleInfoButton, gbc);
gbc = GuiUtil.makeGbc(1, 3, 1, 1);
gbc.anchor = GridBagConstraints.EAST;
gbc.weightx = 1;
dialog.add(okButton, gbc);
gbc = GuiUtil.makeGbc(2, 3, 1, 1);
dialog.add(cancelButton, gbc);
ActionListener buttonAction = new ButtonAction();
okButton.addActionListener(buttonAction);
cancelButton.addActionListener(buttonAction);
toggleInfoButton.addActionListener(buttonAction);
testButton.addActionListener(buttonAction);
okButton.setMnemonic(KeyEvent.VK_S);
cancelButton.setMnemonic(KeyEvent.VK_C);
//okButton.setToolTipText("Press Enter in inputbox to save");
cancelButton.setToolTipText("Press ESC to cancel");
/**
* Set a minimum width, so it has some width even for values that may
* be shorter. This should be based on one line height.
*/
dialog.pack();
Dimension preferred = dialog.getPreferredSize();
dialog.setMinimumSize(new Dimension(400, preferred.height));
}
/**
* Shows the editor dialog, with {@code title} as description of the action,
* {@code preset} as preset value and {@code info} as help, which can be
* toggled by clicking on the Help-button. If not help text is specified,
* then there will be no Help-button.
*
* @param title The description of what this edit action does
* @param preset The preset value to fill the inputbox with
* @param info The help text (use {@code null} to don't use help)
* @return The edited value or {@code null} if the dialog was closed without
* saving
*/
public String showDialog(String title, String preset, String info) {
input.setText(preset);
label.setText(title);
this.info.setText(info);
if (info == null) {
this.info.setVisible(false);
}
toggleInfoButton.setVisible(info != null);
toggleInfoButton.setSelected(this.info.isVisible());
result = null;
input.requestFocusInWindow();
// Set initial size, now also based on the preset value
dialog.pack();
dialog.setSize(dialog.getPreferredSize());
dialog.setLocationRelativeTo(parent);
dialog.setVisible(true);
// This will block until closed, during that time stuff can be changed
return result;
}
/**
* Sets an optional formatter, which can format the input before determining
* if there is something to save (which enables/disables the Save-button if
* {@link setAllowEmpty(boolean)} is set to false).
*
* @param formatter
*/
public void setFormatter(DataFormatter<String> formatter) {
this.formatter = formatter;
}
public void setTester(Tester tester) {
this.tester = tester;
testButton.setVisible(tester != null);
}
public void setLinkLabelListener(LinkLabelListener listener) {
info.setListener(listener);
}
/**
* Set whether to allow an empty value to be saved. Default is false.
*
* @param allow true if empty values should be able to be saved
*/
public void setAllowEmpty(boolean allow) {
allowEmpty = allow;
}
/**
* Set whether to allow linebrekas in the value. Otherwise linebreaks are
* filtered out when editing an replaced by a space. Default is false.
*
* @param allow true to allow linebreaks
*/
public final void setAllowLinebreaks(boolean allow) {
input.getDocument().putProperty("filterNewlines", !allow);
}
private String format(String input) {
if (formatter != null && input != null) {
return formatter.format(input);
}
return input;
}
/**
* Checks if the current input isn't empty (after formatting) and enables or
* diables the "add" button accordingly.
*/
private void updateOkButton() {
okButton.setEnabled(isSomethingToSave());
}
/**
* Check if there is currently something to save, based on the allowEmpty
* property and the current input (possibly after formatting).
*
* @return true if there is something to save (as defined by the set rules),
* false otherwise
*/
private boolean isSomethingToSave() {
if (allowEmpty) {
return true;
}
String currentInput = format(input.getText());
return currentInput != null && !currentInput.isEmpty();
}
/**
* React on button presses.
*/
private class ButtonAction implements ActionListener {
@Override
public void actionPerformed(ActionEvent e) {
if (e.getSource() == okButton) {
result = input.getText();
dialog.setVisible(false);
} else if (e.getSource() == cancelButton) {
dialog.setVisible(false);
} else if (e.getSource() == toggleInfoButton) {
/**
* Show and hide the help. Showing the help means the minimum
* size set in the constructor won't really fit anymore, but
* that's not too bad.
*/
info.setVisible(toggleInfoButton.isSelected());
dialog.pack();
} else if (e.getSource() == testButton) {
if (tester != null) {
tester.test(testButton, 0, testButton.getHeight(), input.getText());
}
}
}
}
/**
* Checks if the first Dimension is bigger in width and/or height than the
* second one.
*
* @param d1 The first Dimension
* @param d2 The second Dimension
* @return true if the first Dimension is bigger, false otherwise
*/
private static boolean bigger(Dimension d1, Dimension d2) {
if (d1.height > d2.height || d1.width > d2.width) {
return true;
}
return false;
}
/**
* Adjusts the size of the dialog to the preferred size, but only if that
* is bigger than the current size.
*/
private void adjustSize() {
if (bigger(dialog.getPreferredSize(), dialog.getSize())) {
dialog.pack();
}
}
/**
* Adjust size of the dialog when editing the input (so the input box
* automatically resizes) and update the Save-button state.
*/
private class ChangeListener implements DocumentListener {
@Override
public void insertUpdate(DocumentEvent e) {
updateOkButton();
// Wrap in invokeLater() so the size is adjusted correctly after
// entering text (otherwise it would always be one edit behind)
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
adjustSize();
}
});
}
@Override
public void removeUpdate(DocumentEvent e) {
updateOkButton();
}
@Override
public void changedUpdate(DocumentEvent e) {
updateOkButton();
}
}
public interface Tester {
public void test(Component component, int x, int y, String value);
}
}