// BlogBridge -- RSS feed reader, manager, and web based service
// Copyright (C) 2002-2006 by R. Pito Salas
//
// 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 2 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, write to the Free Software Foundation, Inc., 59 Temple Place,
// Suite 330, Boston, MA 02111-1307 USA
//
// Contact: R. Pito Salas
// mailto:pitosalas@users.sourceforge.net
// More information: about BlogBridge
// http://www.blogbridge.com
// http://sourceforge.net/projects/blogbridge
//
// $Id: Editor.java,v 1.17 2008/04/09 04:34:38 spyromus Exp $
//
package com.salas.bb.remixfeeds.templates;
import com.jgoodies.forms.builder.ButtonBarBuilder;
import com.jgoodies.forms.layout.CellConstraints;
import com.jgoodies.uif.AbstractDialog;
import com.jgoodies.uif.util.Resizer;
import com.salas.bb.utils.Resources;
import com.salas.bb.utils.StringUtils;
import com.salas.bb.utils.i18n.Strings;
import com.salas.bb.utils.uif.BBFormBuilder;
import com.salas.bb.utils.uif.ComponentsFactory;
import com.salas.bb.utils.uif.HeaderPanelExt;
import com.salas.bb.views.mainframe.MainFrame;
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.util.Collection;
/**
* Templates editor lets the user to see available templates
* and edit them (not system ones).
*/
public class Editor extends AbstractDialog
{
private final String STR_DIALOG_TITLE = Strings.message("te.dialog.title");
private final String STR_DIALOG_HEADER = Strings.message("te.dialog.header");
private final String STR_TEMPLATE = Strings.message("te.template");
private final String STR_HELP = Strings.message("te.help");
private final String STR_HELP_TEXT = Strings.message("te.help.text");
private final String STR_NEW_PROMT = Strings.message("te.new.prompt");
private final String STR_NEW_TITLE = Strings.message("te.new.title");
private final String STR_COPY_TITLE = Strings.message("te.copy.title");
private final String STR_NEW_PROMPT_EMPTY = Strings.message("te.new.prompt.empty");
private final String STR_NEW_PROMPT_UNIQUE = Strings.message("te.new.prompt.unique");
private final String STR_DELETE_TEXT = Strings.message("te.delete.text");
private final String STR_DELETE_TITLE = Strings.message("te.delete.title");
private final String STR_SAVE_SAVE = Strings.message("te.save");
private final String STR_SAVE_DONT_SAVE = Strings.message("te.dont.save");
private final String STR_SAVE_CANCEL = Strings.message("te.cancel");
private final String STR_SAVE_MODIFIED = Strings.message("te.save.modified");
private final String STR_SAVE_INVALID = Strings.message("te.save.invalid");
private final String STR_NEW = Strings.message("te.new");
private final String STR_DELETE = Strings.message("te.delete");
private final String STR_COPY = Strings.message("te.copy");
private final String STR_SAVE = Strings.message("te.save");
private final String STR_REVERT = Strings.message("te.revert");
/** Set to TRUE when the editor template text differs from the current. */
private volatile boolean templateModified;
/** Set to TRUE when the template in the editor has a valid syntax. */
private volatile boolean templateValid;
/** Currently selected template. */
private volatile Template selectedTemplate;
/** Templates list. */
private JComboBox cbTemplates;
private JTextArea taEditor;
private JButton btnSave;
private JButton btnRevert;
private JButton btnDelete;
private JButton btnCopy;
private JButton btnNew;
/**
* Creates the dialog.
*
* @param parent parent.
*/
public Editor(Dialog parent)
{
super(parent);
initComponents();
}
/**
* Creates the dialog.
*
* @param parent parent.
*/
public Editor(MainFrame parent)
{
super(parent);
initComponents();
}
/**
* Build the standard header.
*
* @return header
*/
protected JComponent buildHeader()
{
return new HeaderPanelExt(STR_DIALOG_TITLE, STR_DIALOG_HEADER);
}
/**
* Makes the dialog resizable.
*/
protected void setResizable()
{
setResizable(true);
}
/**
* Initializes components.
*/
private void initComponents()
{
setTitle(STR_DIALOG_TITLE);
btnNew = new JButton(new NewTemplateAction());
btnCopy = new JButton(new CopyTemplateAction());
btnDelete = new JButton(new DeleteTemplateAction());
btnSave = new JButton(new SaveTemplateAction());
btnRevert = new JButton(new RevertTemplateAction());
cbTemplates = new JComboBox();
cbTemplates.addActionListener(new TemplateDropDownListener());
taEditor = new JTextArea("");
taEditor.setFont(new Font("Monospaced", Font.PLAIN, btnNew.getFont().getSize()));
taEditor.setTabSize(2);
taEditor.addKeyListener(new EditorListener());
}
/**
* Returns the contents.
*
* @return pane.
*/
protected JComponent buildContent()
{
JPanel panel = new JPanel(new BorderLayout());
panel.add(buildMainPanel(), BorderLayout.CENTER);
panel.add(buildButtonBar(), BorderLayout.SOUTH);
return panel;
}
/**
* Main panel.
*
* @return panel.
*/
private Component buildMainPanel()
{
// Buttons
ButtonBarBuilder buttons = new ButtonBarBuilder();
buttons.addFixed(btnNew);
buttons.addRelatedGap();
buttons.addFixed(btnCopy);
buttons.addRelatedGap();
buttons.addFixed(btnDelete);
// --- Panel construction
BBFormBuilder builder = new BBFormBuilder("p, 2dlu, max(50dlu;p):grow, 7dlu, p, 0dlu:grow");
// First row
builder.append(STR_TEMPLATE, cbTemplates);
builder.append(buttons.getPanel());
// Editor
builder.appendRelatedComponentsGapRow();
builder.appendRow("200px:grow");
builder.nextLine(2);
builder.append(new JScrollPane(taEditor), 6,
CellConstraints.FILL, CellConstraints.FILL);
// Help section
builder.appendUnrelatedComponentsGapRow(2);
builder.append(STR_HELP, 6);
builder.appendRelatedComponentsGapRow();
builder.appendRow("150px");
builder.nextLine(2);
builder.append(ComponentsFactory.createInstructionsBox(STR_HELP_TEXT), 6,
CellConstraints.FILL, CellConstraints.FILL);
builder.appendUnrelatedComponentsGapRow(2);
return builder.getPanel();
}
/**
* Button bar.
*
* @return bar.
*/
private Component buildButtonBar()
{
ButtonBarBuilder builder = new ButtonBarBuilder();
builder.addGriddedButtons(new JButton[] {btnSave, btnRevert});
builder.addGlue();
builder.addGridded(createCloseButton(true));
return builder.getPanel();
}
/**
* Opens the editor.
*
* @param template template to select or NULL to select the first.
*/
public void open(Template template)
{
updateDropDown(template);
super.open();
}
// ------------------------------------------------------------------------
// Action events
// ------------------------------------------------------------------------
/**
* Invoked when a template text changes.
*/
private void onTemplateChange()
{
// Check the syntax
// See if the text differs from what's in the rep and
// update the state of cancel / save buttons
String text = taEditor.getText();
templateModified = !text.equals(selectedTemplate.getText());
templateValid = SyntaxChecker.validate(text).isEmpty();
btnSave.setEnabled(templateModified && templateValid);
btnRevert.setEnabled(templateModified);
}
/**
* Invoked when the template is selected.
*
* @param template selected template.
*/
private void onTemplateSelect(Template template)
{
if (template == null || template == selectedTemplate) return;
if (!saveIfNecessary())
{
updateDropDown(selectedTemplate);
return;
}
// Reset the state
selectedTemplate = template;
templateModified = false;
templateValid = true;
// Set text and cursor location
taEditor.setText(template.getText());
taEditor.setCaretPosition(0);
// Disable the editor and some action buttons for the system templates
boolean userDefined = !template.isSystem();
btnDelete.setEnabled(userDefined);
btnSave.setEnabled(userDefined);
btnRevert.setEnabled(userDefined);
taEditor.setEditable(userDefined);
taEditor.setEnabled(userDefined);
onTemplateChange();
}
/**
* Invoked when a selected template is duplicated.
*/
private void onTemplateCopy()
{
if (selectedTemplate == null) return;
String title = selectedTemplate.getName();
onTemplateNew(createUniqueTitleFrom(title), selectedTemplate.getText());
}
/**
* Invoked when a new template is added.
*
* @param newTitle new title or NULL.
* @param text new template text or NULL.
*/
private void onTemplateNew(String newTitle, String text)
{
String message = STR_NEW_PROMT;
String dialogTitle = newTitle == null ? STR_NEW_TITLE : STR_COPY_TITLE;
String name = null;
while (message != null)
{
// Ask for the name
name = (String)JOptionPane.showInputDialog(Editor.this, message, dialogTitle,
JOptionPane.QUESTION_MESSAGE, getIcon(), null, newTitle);
// Analyze the result
if (name == null)
{
// Cancelled
return;
} else
{
// Entered something or simply confirmed
if (StringUtils.isEmpty(name))
{
message = STR_NEW_PROMPT_EMPTY;
} else if (Templates.isExisting(name))
{
message = STR_NEW_PROMPT_UNIQUE;
} else
{
// Finally, no error
message = null;
}
}
}
// Create a template and register
if (text == null) text = "";
Template newTemplate = new Template(name, false, text);
Templates.addTemplate(newTemplate);
updateDropDown(newTemplate);
}
/**
* Invoked when a selected template is removed.
*/
private void onTemplateDelete()
{
if (selectedTemplate == null) return;
int answer = JOptionPane.showConfirmDialog(Editor.this,
STR_DELETE_TEXT, STR_DELETE_TITLE,
JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE,
getIcon());
if (answer == JOptionPane.YES_OPTION)
{
// Delete the template
Templates.deleteTemplate(selectedTemplate);
selectedTemplate = null;
updateDropDown(null);
}
}
/**
* Returns the icon to use in dialogs.
*
* @return icon.
*/
private Icon getIcon()
{
return Resources.getLargeApplicationIcon();
}
/**
* Invoked when the changes are saved.
*/
private void onTemplateSave()
{
saveTemplate(selectedTemplate);
}
/**
* Invoked when a user cancels his changes to the template.
*/
private void onTemplateRevert()
{
taEditor.setText(selectedTemplate.getText());
onTemplateChange();
}
/**
* Invoked when a user presses the close button.
*/
public void doClose()
{
if (saveIfNecessary()) super.doClose();
}
/**
* Invoked when a user closes the window.
*/
protected void doCloseWindow()
{
doClose();
}
// ------------------------------------------------------------------------
// Functions
// ------------------------------------------------------------------------
/**
* Saves the text from the editor to the target template.
*
* @param target target.
*/
private void saveTemplate(Template target)
{
target.setText(taEditor.getText());
onTemplateChange();
}
/**
* Updates the templates drop-down.
*
* @param select template to select or NULL to select what was previously selected.
*/
private void updateDropDown(Template select)
{
// Find what was previously selected if there's nothing to select given
if (select == null)
{
select = (Template)cbTemplates.getSelectedItem();
}
// Refill the dialog
cbTemplates.removeAllItems();
Collection<Template> templates = Templates.getUserTemplates().values();
for (Template template : templates)
{
cbTemplates.addItem(template);
}
// Set the selection
if (select != null) cbTemplates.setSelectedItem(select);
}
/**
* Creates a unique title from the given name by adding "(N)".
*
* @param title base title.
*
* @return unique title.
*/
private static String createUniqueTitleFrom(String title)
{
String newTitle;
int cnt = 2;
do
{
newTitle = title + " (" + cnt + ")";
cnt++;
} while (Templates.isExisting(newTitle));
return newTitle;
}
/**
* Dialog resizer.
*
* @param component component.
*/
protected void resizeHook(JComponent component)
{
Resizer.ONE2ONE.resize(component);
}
/**
* Shows the dialog and saves the template if necessary.
*
* @return TRUE if saved and successfully and the operation can continue; FALSE if canceled.
*/
private boolean saveIfNecessary()
{
if (templateModified)
{
// Template is modified, the user may want to save it,
// but with bad syntax saving isn't possible, and the user
// needs to fix it first, or drop the changes
String answerSave = STR_SAVE_SAVE;
String answerDontSave = STR_SAVE_DONT_SAVE;
String answerCancel = STR_SAVE_CANCEL;
Object answer;
if (templateValid)
{
Object[] options = { answerSave, answerDontSave, answerCancel };
answer = JOptionPane.showOptionDialog(Editor.this,
STR_SAVE_MODIFIED,
STR_DIALOG_TITLE, -1, JOptionPane.QUESTION_MESSAGE, getIcon(),
options, answerSave);
answer = options[(Integer)answer];
} else
{
Object[] options = {answerDontSave, answerCancel};
answer = JOptionPane.showOptionDialog(Editor.this,
STR_SAVE_INVALID,
STR_DIALOG_TITLE, -1, JOptionPane.QUESTION_MESSAGE, getIcon(),
options, answerCancel);
answer = options[(Integer)answer];
}
if (answer == answerSave)
{
// Save template
saveTemplate(selectedTemplate);
} else if (answer == answerCancel)
{
// Cancel
return false;
}
}
return true;
}
// ------------------------------------------------------------------------
// Actions and Listeners
// ------------------------------------------------------------------------
/**
* Listens to the template drop-down.
*/
private class TemplateDropDownListener implements ActionListener
{
public void actionPerformed(ActionEvent e)
{
Object newSelection = cbTemplates.getSelectedItem();
if (newSelection != selectedTemplate)
{
onTemplateSelect((Template)newSelection);
}
}
}
/**
* Listens to the editor changes and validates the text.
*/
private class EditorListener extends KeyAdapter
{
public void keyReleased(KeyEvent e)
{
if (!e.isActionKey()) onTemplateChange();
}
}
/**
* Intercepts the call to the new-template command.
*/
private class NewTemplateAction extends AbstractAction
{
private NewTemplateAction()
{
super(STR_NEW);
}
public void actionPerformed(ActionEvent e)
{
onTemplateNew(null, null);
}
}
/**
* Intercepts the call to the delete-template command.
*/
private class DeleteTemplateAction extends AbstractAction
{
private DeleteTemplateAction()
{
super(STR_DELETE);
}
public void actionPerformed(ActionEvent e)
{
onTemplateDelete();
}
}
/**
* Intercepts the call to the copy-template command.
*/
private class CopyTemplateAction extends AbstractAction
{
private CopyTemplateAction()
{
super(STR_COPY);
}
public void actionPerformed(ActionEvent e)
{
onTemplateCopy();
}
}
/**
* Intercepts the call to the save-template command.
*/
private class SaveTemplateAction extends AbstractAction
{
private SaveTemplateAction()
{
super(STR_SAVE);
}
public void actionPerformed(ActionEvent e)
{
onTemplateSave();
}
}
/**
* Intercepts the call to the revert-template command.
*/
private class RevertTemplateAction extends AbstractAction
{
private RevertTemplateAction()
{
super(STR_REVERT);
}
public void actionPerformed(ActionEvent e)
{
onTemplateRevert();
}
}
}