// 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: BlogDetailsPreferencesPanel.java,v 1.28 2008/06/26 13:41:58 spyromus Exp $
//
package com.salas.bb.remixfeeds.prefs;
import com.jgoodies.binding.adapter.ComboBoxAdapter;
import com.jgoodies.binding.adapter.DocumentAdapter;
import com.jgoodies.binding.adapter.RadioButtonAdapter;
import com.jgoodies.binding.beans.PropertyAdapter;
import com.jgoodies.binding.list.ArrayListModel;
import com.jgoodies.binding.value.AbstractConverter;
import com.jgoodies.binding.value.BufferedValueModel;
import com.jgoodies.binding.value.Trigger;
import com.jgoodies.binding.value.ValueModel;
import com.jgoodies.forms.builder.ButtonBarBuilder;
import com.jgoodies.forms.layout.CellConstraints;
import com.jgoodies.uif.AbstractDialog;
import com.salas.bb.remixfeeds.api.IWeblogAPI;
import com.salas.bb.remixfeeds.api.WeblogAPIs;
import com.salas.bb.remixfeeds.templates.Editor;
import com.salas.bb.remixfeeds.templates.Templates;
import com.salas.bb.utils.Constants;
import com.salas.bb.utils.StringUtils;
import com.salas.bb.utils.i18n.Strings;
import com.salas.bb.utils.uif.*;
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.Arrays;
import java.util.Collection;
import java.util.Set;
/**
* Single blog details panel.
*/
class BlogDetailsPreferencesPanel extends JPanel implements PropertyChangeListener
{
private JDialog parent;
private final TargetBlogProxy proxy = new TargetBlogProxy(null);
public static final String PROP_BLOG_PREFERENCES = "blogPreferences";
private Trigger trigger = new Trigger();
private JTextField tfTitle = new JTextField();
private JTextField tfURLSummary = new JTextField();
private JTextField tfURL = new JTextField();
private JTextField tfUser = new JTextField();
private JPasswordField tfPassword = new JPasswordField();
private JTextArea taDescription = new JTextArea();
private JComboBox cbDefaultCategory = new JComboBox();
private JComboBox cbBlog = new JComboBox();
private JComboBox cbWeblogAPIType;
private FetchBlogsAction actFetchBlogs;
private FetchCategoriesAction actFetchCategories;
private JButton btnSetup;
private JButton btnFetchCategories;
private JButton btnFetchBlogs;
private JRadioButton rbPublic = new JRadioButton(Strings.message("ptb.prefs.public"));
private JRadioButton rbDraft = new JRadioButton(Strings.message("ptb.prefs.draft"));
private final JLabel lbTitle = new JLabel(Strings.message("ptb.prefs.details.name"));
private final JLabel lbURL = new JLabel(Strings.message("ptb.prefs.details.url"));
private final JLabel lbDefaultCategory = new JLabel(Strings.message("ptb.category"));
private final JLabel lbBlog = new JLabel(Strings.message("ptb.prefs.details.blog"));
private final JLabel lbPostAs = new JLabel(Strings.message("ptb.prefs.details.post.as"));
private final boolean hasTemplatesFeature;
private JComboBox cbTemplate;
private final JLabel lbTemplate = new JLabel(Strings.message("te.template"));
private JButton btnTemplateEditor;
private final ArrayListModel lmdlCategories = new ArrayListModel();
private final ArrayListModel lmdlBlogs = new ArrayListModel();
private ArrayListModel lmTemplateNames;
private BufferedValueModel vmURL;
private BufferedValueModel vmUser;
private BufferedValueModel vmPass;
private BufferedValueModel vmAPI;
/**
* Creates a panel with layout.
*
* @param parent parent dialog.
*/
public BlogDetailsPreferencesPanel(JDialog parent)
{
this.parent = parent;
hasTemplatesFeature = Templates.hasCustomTemplatesFeature();
initComponents(parent);
btnTemplateEditor.setEnabled(hasTemplatesFeature);
// Synthetic panels
BBFormBuilder builder = new BBFormBuilder("p, 2dlu, p");
builder.append(rbPublic, rbDraft);
JPanel pnlPostAs = builder.getPanel();
// Layout components
builder = new BBFormBuilder("max(p;60dlu), 4dlu, 125dlu, 2dlu, p, 2dlu, 0:grow", this);
builder.append(lbTitle, tfTitle);
builder.nextLine();
builder.append(lbURL, tfURLSummary);
builder.append(btnSetup);
builder.appendUnrelatedComponentsGapRow(2);
builder.append(lbBlog, cbBlog);
builder.append(btnFetchBlogs);
builder.nextLine();
builder.append(lbDefaultCategory, cbDefaultCategory);
builder.append(btnFetchCategories);
builder.nextLine();
builder.append(lbPostAs, pnlPostAs);
builder.nextLine();
builder.append(lbTemplate, cbTemplate);
builder.append(btnTemplateEditor, UifUtilities.makePublisherPlanIcon(!hasTemplatesFeature));
}
/**
* Binds components to the properties of the preferences proxy.
*
* @param parent parent dialog.
*/
private void initComponents(JDialog parent)
{
vmURL = new BufferedValueModel(new PropertyAdapter(proxy, TargetBlog.PROP_API_URL, true), trigger);
vmUser = new BufferedValueModel(new PropertyAdapter(proxy, TargetBlog.PROP_USER, true), trigger);
vmPass = new BufferedValueModel(new PropertyAdapter(proxy, TargetBlog.PROP_PASSWORD, true), trigger);
tfTitle.setDocument(new DocumentAdapter(new PropertyAdapter(proxy, TargetBlog.PROP_TITLE, true)));
tfURL.setDocument(new DocumentAdapter(vmURL));
tfURLSummary.setDocument(new DocumentAdapter(new URLSummaryConverter(
new PropertyAdapter(proxy, TargetBlog.PROP_API_URL, true))));
tfURLSummary.setEditable(false);
tfUser.setDocument(new DocumentAdapter(vmUser));
tfPassword.setDocument(new DocumentAdapter(vmPass));
taDescription = ComponentsFactory.createInstructionsArea("");
loadCategoriesLookup();
ValueModel mdlDefaultCategory = new PropertyAdapter(proxy, TargetBlog.PROP_DEFAULT_CATEGORY, true);
ComboBoxAdapter adapter = new ComboBoxAdapter((ListModel)lmdlCategories, mdlDefaultCategory);
cbDefaultCategory = new JComboBox(adapter);
ValueModel mdlBlog = new PropertyAdapter(proxy, TargetBlog.PROP_BLOG, true);
adapter = new ComboBoxAdapter((ListModel)lmdlBlogs, mdlBlog);
cbBlog = new JComboBox(adapter);
ValueModel mdlDraft = new PropertyAdapter(proxy, TargetBlog.PROP_DRAFT, true);
rbPublic.setModel(new RadioButtonAdapter(mdlDraft, Boolean.FALSE));
rbDraft.setModel(new RadioButtonAdapter(mdlDraft, Boolean.TRUE));
Collection apis = WeblogAPIs.getWeblogAPIs();
ValueModel mdlAPIs = new PropertyAdapter(proxy, TargetBlog.PROP_API_TYPE, true);
vmAPI = new BufferedValueModel(mdlAPIs, trigger);
adapter = new ComboBoxAdapter(apis.toArray(), vmAPI);
cbWeblogAPIType = new JComboBox(adapter);
vmAPI.addPropertyChangeListener(new WeblogTypeChangeListener());
btnSetup = new JButton(new SetupAction(parent));
btnFetchCategories = new JButton();
actFetchCategories = new FetchCategoriesAction(); // It uses btnFetchCategories (hence the order)
btnFetchCategories.setAction(actFetchCategories);
btnFetchBlogs = new JButton();
actFetchBlogs = new FetchBlogsAction(); // It uses btnFetchBlogs (hence the order)
btnFetchBlogs.setAction(actFetchBlogs);
// Templates
Set<String> templateNames = Templates.getUserTemplates().keySet();
lmTemplateNames = new ArrayListModel(templateNames);
ValueModel mdlTemplates = new PropertyAdapter(proxy, TargetBlog.PROP_TEMPLATE_NAME, true);
cbTemplate = new JComboBox(new ComboBoxAdapter((ListModel)lmTemplateNames, mdlTemplates));
btnTemplateEditor = new JButton(new TemplateEditorAction());
proxy.addPropertyChangeListener(TargetBlogProxy.PROP_BLOG_PREFERENCES, this);
updateViewState();
}
private void loadCategoriesLookup()
{
loadLookup(lmdlCategories, proxy.getCategories());
}
private void loadBlogsLookup()
{
loadLookup(lmdlBlogs, proxy.getBlogs());
}
private static void loadLookup(ArrayListModel lm, Object[] items)
{
Arrays.sort(items);
lm.clear();
lm.addAll(Arrays.asList(items));
}
/**
* Returns the blog preferences object.
*
* @return preferences.
*/
public TargetBlog getBlogPreferences()
{
return proxy.getBlogPreferences();
}
/**
* Sets the preferences object to edit. If object is <code>NULL</code>,
* the view is disabled.
*
* @param prefs preferences object.
*/
public void setBlogPreferences(TargetBlog prefs)
{
proxy.setBlogPreferences(prefs);
loadBlogsLookup();
loadCategoriesLookup();
}
/**
* Invoked when the proxy preferences object gets loaded / unloaded.
*
* @param evt event.
*/
public void propertyChange(PropertyChangeEvent evt)
{
updateViewState();
}
/**
* Updates the state of view components.
*/
private void updateViewState()
{
boolean en = proxy.isLoaded();
lbTitle.setEnabled(en);
tfTitle.setEnabled(en);
lbURL.setEnabled(en);
tfURLSummary.setEnabled(en);
btnSetup.setEnabled(en);
lbDefaultCategory.setEnabled(en);
cbDefaultCategory.setEnabled(en);
btnFetchCategories.setEnabled(en);
lbBlog.setEnabled(en);
cbBlog.setEnabled(en);
btnFetchBlogs.setEnabled(en);
lbPostAs.setEnabled(en);
rbPublic.setEnabled(en);
rbDraft.setEnabled(en);
lbTemplate.setEnabled(en);
cbTemplate.setEnabled(en);
}
/**
* Opens setup dialog.
*/
private class SetupAction extends AbstractAction
{
private final JDialog parentDialog;
private BlogSetupDialog dialog;
/**
* Creates action.
*
* @param parent parent dialog.
*/
public SetupAction(JDialog parent)
{
super(Strings.message("ptb.prefs.details.setup"));
parentDialog = parent;
}
/**
* Invoked when an action occurs.
*
* @param e event.
*/
public void actionPerformed(ActionEvent e)
{
if (dialog == null) dialog = new BlogSetupDialog(parentDialog);
String oldURL = tfURL.getText();
dialog.open();
String newURL = tfURL.getText();
if (!oldURL.equalsIgnoreCase(newURL))
{
proxy.setCategories(null);
proxy.setDefaultCategory(null);
proxy.setBlogs(null);
proxy.setBlog(null);
actFetchBlogs.actionPerformed(null);
actFetchCategories.actionPerformed(null);
}
}
}
/**
* The dialog for setup and probing blogs.
*/
private class BlogSetupDialog extends AbstractDialog
{
private final JLabel lbStatus;
private JButton btnOK;
private JButton btnCancel;
private ProgressSpinner spnProgress;
/**
* Creates setup dialog.
*
* @param parent parent dialog.
*/
public BlogSetupDialog(JDialog parent)
{
super(parent, Strings.message("ptb.prefs.details.setup.title"), true);
lbStatus = new JLabel(Strings.message("ptb.prefs.details.setup.status.idle"));
spnProgress = new ProgressSpinner();
spnProgress.setText(Strings.message("ptb.prefs.details.setup.status.connecting"));
}
/**
* Creates main page component.
*
* @return component.
*/
protected JComponent buildContent()
{
JPanel content = new JPanel(new BorderLayout());
content.add(buildMainPane(), BorderLayout.CENTER);
content.add(buildButtonsBar(), BorderLayout.SOUTH);
return content;
}
/**
* Creates main pane.
*
* @return main pane.
*/
private Component buildMainPane()
{
BBFormBuilder builder = new BBFormBuilder("p, 2dlu, max(p;50dlu), 75dlu");
builder.setDefaultDialogBorder();
builder.append(Strings.message("ptb.prefs.details.setup.type"), cbWeblogAPIType);
builder.append(Strings.message("ptb.prefs.details.setup.url"), tfURL, 2);
builder.nextLine();
builder.append(Strings.message("ptb.prefs.details.setup.user"), tfUser, 2);
builder.nextLine();
builder.append(Strings.message("ptb.prefs.details.setup.pass"), tfPassword, 2);
builder.nextLine();
builder.appendUnrelatedComponentsGapRow();
builder.nextLine();
builder.appendRow("75dlu");
builder.append(new JScrollPane(taDescription), 4, CellConstraints.FILL, CellConstraints.FILL);
builder.nextLine();
builder.appendUnrelatedComponentsGapRow();
builder.nextLine();
builder.append(Strings.message("ptb.prefs.details.setup.status"), lbStatus, 2);
builder.appendRow("0:grow");
return builder.getPanel();
}
/**
* Creates buttons bar.
*
* @return bar.
*/
private Component buildButtonsBar()
{
btnOK = createOKButton(true);
btnOK.setAction(new TestBlogSetupAction());
btnCancel = createCancelButton();
ButtonBarBuilder builder = new ButtonBarBuilder();
builder.getPanel().setBorder(Constants.DIALOG_BUTTON_BAR_BORDER);
builder.addFixed(spnProgress);
builder.addUnrelatedGap();
builder.addGlue();
builder.addGriddedButtons(new JButton[] { btnOK, btnCancel });
return builder.getPanel();
}
/**
* Opens the dialog.
*/
public void open()
{
status(Strings.message("ptb.prefs.details.setup.status.idle"), false);
super.open();
}
/**
* Commit changes when accepting the dialog.
*/
public void doAccept()
{
trigger.triggerCommit();
super.doAccept();
}
/**
* Flush all the fields in the setup dialog when cancelled.
*/
public void doCancel()
{
trigger.triggerFlush();
super.doCancel();
}
/**
* Sets status and font.
*
* @param text status text.
* @param bold <code>TRUE</code> for bold font.
*/
private void status(String text, boolean bold)
{
lbStatus.setText(text);
lbStatus.setFont(lbStatus.getFont().deriveFont(bold ? Font.BOLD :Font.PLAIN));
}
/**
* Tests connection settings.
*/
private class TestBlogSetupAction extends AbstractAction
{
/**
* Creates action.
*/
public TestBlogSetupAction()
{
super(Strings.message("ptb.prefs.details.setup.test"));
}
/**
* Invoked when an action occurs.
*
* @param e event.
*/
public void actionPerformed(ActionEvent e)
{
spnProgress.start();
enableButtons(false);
new Thread("Testing Connection")
{
{ setDaemon(true); }
/**
* Invoked when running a thread.
*/
public void run()
{
TargetBlog tb = new TargetBlog();
tb.setApiType((IWeblogAPI)vmAPI.getValue());
tb.setApiURL((String)vmURL.getValue());
tb.setUser((String)vmUser.getValue());
tb.setPassword((String)vmPass.getValue());
final String msg = tb.testConnection();
if (msg == null) vmURL.setValue(tb.getApiURL());
SwingUtilities.invokeLater(new Runnable()
{
public void run()
{
spnProgress.stop();
onTestComplete(msg);
}
});
}
}.start();
}
/**
* Invoked when the connection testing is complete.
*
* @param msg <code>NULL</code> if fine, or error message
*/
private void onTestComplete(String msg)
{
enableButtons(true);
if (msg == null)
{
status(Strings.message("ptb.prefs.details.setup.status.detected"), true);
doAccept();
} else
{
status(msg, false);
}
}
/**
* Enables and disables buttons.
*
* @param en <code>TRUE</code> to enable.
*/
private void enableButtons(boolean en)
{
btnOK.setEnabled(en);
btnCancel.setEnabled(en);
}
}
}
/**
* Fetches the list of categories.
*/
private class FetchCategoriesAction extends AbstractFetchLookupAction
{
/**
* Creates action.
*/
public FetchCategoriesAction()
{
super(cbDefaultCategory, btnFetchCategories, Strings.message("ptb.prefs.details.category.update"));
}
/**
* This method is called asynchronously when the action takes place. You need
* to call loading procedure from it and the callback should be executed from
* within EDT upon completion.
*
* @param edtCallback callback to execute.
*/
protected void startAsyncLoading(Runnable edtCallback)
{
proxy.loadCategories(edtCallback);
}
/** This method is called when the lookup combo-box can be reloaded with new data. */
protected void loadLookup()
{
loadCategoriesLookup();
}
/**
* After the async loading the state is checked to learn whether the lookup
* update is neccessary.
*
* @return <code>TRUE</code> if loading was successful and lookup should be updated.
*/
protected boolean isSuccessfulLoading()
{
return proxy.getCategories().length > 0;
}
}
/**
* Fetches the list of blogs.
*/
private class FetchBlogsAction extends AbstractFetchLookupAction
{
/**
* Creates action.
*/
public FetchBlogsAction()
{
super(cbBlog, btnFetchBlogs, Strings.message("ptb.prefs.details.blog.update"));
}
/**
* This method is called asynchronously when the action takes place. You need
* to call loading procedure from it and the callback should be executed from
* within EDT upon completion.
*
* @param edtCallback callback to execute.
*/
protected void startAsyncLoading(Runnable edtCallback)
{
proxy.loadBlogs(edtCallback);
}
/** This method is called when the lookup combo-box can be reloaded with new data. */
protected void loadLookup()
{
loadBlogsLookup();
}
/**
* After the async loading the state is checked to learn whether the lookup
* update is neccessary.
*
* @return <code>TRUE</code> if loading was successful and lookup should be updated.
*/
protected boolean isSuccessfulLoading()
{
return proxy.getBlogs().length > 0;
}
}
/** This converter returns <i>Not Set</i> when URL isn't set. */
private static class URLSummaryConverter extends AbstractConverter
{
/**
* Creates converter.
*
* @param valueModel model to convert.
*/
public URLSummaryConverter(ValueModel valueModel)
{
super(valueModel);
}
/**
* Converts the data from the subject.
*
* @param object object.
*
* @return value.
*/
public Object convertFromSubject(Object object)
{
String url = (String)object;
return StringUtils.isEmpty(url) ? Strings.message("ptb.prefs.details.not.set") : url;
}
/**
* We don't set anything.
*
* @param object value.
*/
public void setValue(Object object)
{
}
}
/**
* Invoked when a template editor is called.
*/
private class TemplateEditorAction extends AbstractAction
{
private TemplateEditorAction()
{
super(Strings.message("te.edit"));
}
public void actionPerformed(ActionEvent e)
{
String templateName = (String)cbTemplate.getSelectedItem();
Editor editor = new Editor(parent);
editor.open(Templates.getByName(templateName));
// Repopulate the templates list
Collection<String> templateNames = Templates.getUserTemplateNames();
lmTemplateNames.clear();
lmTemplateNames.addAll(templateNames);
if (templateName != null)
{
if (templateNames.contains(templateName))
{
cbTemplate.setSelectedItem(templateName);
} else
{
cbTemplate.setSelectedIndex(0);
}
}
}
}
private class WeblogTypeChangeListener implements PropertyChangeListener
{
private String lastEnteredURL;
/**
* This method gets called when a bound property is changed.
*
* @param evt A PropertyChangeEvent object describing the event source and the property that has changed.
*/
public void propertyChange(PropertyChangeEvent evt)
{
IWeblogAPI type = (IWeblogAPI)vmAPI.getValue();
if (type != null)
{
boolean newEnabled = type.isApiUrlApplicable();
boolean oldEnabled = tfURL.isEnabled();
if (newEnabled != oldEnabled)
{
tfURL.setEnabled(newEnabled);
if (newEnabled)
{
tfURL.setText(lastEnteredURL);
} else
{
lastEnteredURL = tfURL.getText();
tfURL.setText(Strings.message("ptb.prefs.details.setup.not.applicable"));
}
}
taDescription.setText(type.getDescription());
}
}
}
}