// 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: Dialog.java,v 1.26 2007/09/11 18:59:49 spyromus Exp $
//
package com.salas.bb.whatshot;
import com.jgoodies.binding.adapter.*;
import com.jgoodies.binding.beans.PropertyAdapter;
import com.jgoodies.binding.value.AbstractConverter;
import com.jgoodies.binding.value.BufferedValueModel;
import com.jgoodies.binding.value.ValueHolder;
import com.jgoodies.binding.value.ValueModel;
import com.jgoodies.forms.layout.CellConstraints;
import com.jgoodies.forms.layout.FormLayout;
import com.jgoodies.uif.AbstractDialog;
import com.jgoodies.uif.application.Application;
import com.jgoodies.uif.util.Resizer;
import com.jgoodies.uif.util.SystemUtils;
import com.salas.bb.core.GlobalModel;
import com.salas.bb.domain.GuidesSet;
import com.salas.bb.domain.IGuide;
import com.salas.bb.domain.prefs.UserPreferences;
import com.salas.bb.search.ResultGroup;
import com.salas.bb.search.ResultGroupPanel;
import com.salas.bb.search.ResultItem;
import com.salas.bb.search.ResultsList;
import com.salas.bb.utils.DateUtils;
import com.salas.bb.utils.StringUtils;
import com.salas.bb.utils.i18n.Strings;
import com.salas.bb.utils.swingworker.SwingWorker;
import com.salas.bb.utils.uif.BBFormBuilder;
import com.salas.bb.utils.uif.ComponentsFactory;
import com.salas.bb.utils.uif.ProgressPanel;
import com.salas.bb.utils.uif.UifUtilities;
import com.salas.bb.views.mainframe.StarsSelectionComponent;
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Vector;
import java.util.prefs.Preferences;
/**
* What's Hot dialog box.
*/
public class Dialog extends AbstractDialog
{
private static final String PROP_WH_STARZ = "wh.starz";
private static final String PROP_WH_UNREAD_ONLY = "wh.unreadOnly";
private static final String PROP_WH_TIME_OPTION = "wh.timeOption";
/** Minimum dialog size. */
private static final Dimension MIN_SIZE = new Dimension(500, 350);
/** Group separator background gradient colors. */
private static final Color GROUP_COLOR_1 = Color.decode("#eebb6b");
private static final Color GROUP_COLOR_2 = Color.decode("#f3b149");
private ResultsList itemsList;
private ListModel model;
private StarsSelectionComponent starz;
private JCheckBox chOnlyUnread;
private JComboBox cbTimeOptions;
/** Progress panel. Used to show the progress of the search. */
private ProgressPanel pnlProgress;
/** Content panel to update with new views. */
private JScrollPane scrollPanel;
/** Setup button. */
private JButton btnSetup;
/** Guides set. */
private final GuidesSet guidesSet;
/**
* Creates search dialog.
*
* @param owner dialog's parent frame.
* @param engine engine to use.
* @param set guides set to pick the guides from.
* @param listener selection listener.
*/
public Dialog(Frame owner, Engine engine, final GuidesSet set, ActionListener listener)
{
super(owner, Strings.message("whatshot.dialog.title"));
guidesSet = set;
pnlProgress = new ProgressPanel(Strings.message("whatshot.inprogress"));
scrollPanel = buildResultsPanel();
setModal(false);
// Only unread selector
Preferences prefs = Application.getUserPreferences();
ValueModel mdlOnlyUnread = new PreferencesAdapter(prefs, PROP_WH_UNREAD_ONLY, false);
chOnlyUnread = ComponentsFactory.createCheckBox(Strings.message("whatshot.unreadonly"),
new ToggleButtonAdapter(mdlOnlyUnread));
// Time options
ValueModel mdlTimeOption = new AbstractConverter(
new PreferencesAdapter(prefs, PROP_WH_TIME_OPTION, TimeOption.THIS_WEEK.getCode()))
{
public Object convertFromSubject(Object o)
{
return TimeOption.fromCode((Integer)o);
}
public void setValue(Object o)
{
TimeOption to = (TimeOption)o;
subject.setValue(to.getCode());
}
};
cbTimeOptions = new JComboBox(new ComboBoxAdapter(TimeOption.OPTIONS, mdlTimeOption));
// Starz selector
ValueModel mdlStarz = new PreferencesAdapter(prefs, PROP_WH_STARZ, 1);
starz = new StarsSelectionComponent(new BoundedRangeAdapter(mdlStarz, 0, 1, 5));
// Make toolbar fonts smaller
UifUtilities.smallerFont(chOnlyUnread);
UifUtilities.smallerFont(cbTimeOptions);
// Setup button
btnSetup = new JButton(Strings.message("whatshot.setup"));
btnSetup.addActionListener(new ActionListener()
{
public void actionPerformed(ActionEvent e)
{
SetupDialog sd = new SetupDialog(GlobalModel.SINGLETON.getUserPreferences());
sd.open();
}
});
UifUtilities.smallerFont(btnSetup);
// Results model and list
model = new ListModel(engine, mdlStarz, mdlOnlyUnread, mdlTimeOption);
onSetupChange();
itemsList = new ResultsList(model)
{
@Override
protected ResultGroupPanel createGroupPanel(ResultGroup group)
{
return new ResultGroupPanel(group.getName(), group.getName(),
GROUP_COLOR_1, GROUP_COLOR_2);
}
};
itemsList.addActionListener(listener);
// Start populating
SwingWorker scanner = model.scan();
scanner.addPropertyChangeListener(new ScannerListener());
scanner.execute();
}
/** Release resources before closing. */
public void close()
{
// Stop link resolution immediately
model.stopLinkResolution();
itemsList = null;
super.close();
getContentPane().removeAll();
}
/**
* Sets the dialog's resizable state. By default dialogs are non-resizable; subclasses may
* override.
*/
protected void setResizable()
{
setResizable(true);
}
/**
* Creates main content pane.
*
* @return the dialog's main content without header and border.
*/
protected JComponent buildContent()
{
JPanel panel = new JPanel(new BorderLayout());
panel.add(buildTopBar(), BorderLayout.NORTH);
panel.add(scrollPanel, BorderLayout.CENTER);
return panel;
}
/**
* Creates top bar with progress indicator, results count and search field.
*
* @return top bar component.
*/
private Component buildTopBar()
{
BBFormBuilder builder = new BBFormBuilder("p, 5px, p, 5px, p, 5px:grow, p");
builder.append(starz);
builder.append(cbTimeOptions);
if (SystemUtils.IS_OS_MAC)
{
CellConstraints cc = new CellConstraints();
JPanel p1 = new JPanel(new FormLayout("p", "p"));
p1.add(chOnlyUnread, cc.xy(1, 1));
builder.append(p1);
p1 = new JPanel(new FormLayout("p", "p, 4px"));
p1.add(btnSetup, cc.xy(1, 1));
builder.append(p1);
} else
{
builder.append(chOnlyUnread);
builder.append(btnSetup);
}
builder.appendUnrelatedComponentsGapRow();
return builder.getPanel();
}
/**
* Creates results panel with results list and controls.
*
* @return results panel component.
*/
private JScrollPane buildResultsPanel()
{
JScrollPane sp = new JScrollPane();
sp.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
sp.setBackground(Color.WHITE);
sp.getViewport().setBackground(Color.WHITE);
return sp;
}
/**
* Returns currently selected item.
*
* @return item.
*/
public ResultItem getSelectedItem()
{
return itemsList == null ? null : itemsList.getSelectedItem();
}
/**
* Invoked when user changes the setup.
*/
private void onSetupChange()
{
UserPreferences prefs = GlobalModel.SINGLETON.getUserPreferences();
// Find the target guide
IGuide guide = null;
String title = prefs.getWhTargetGuide();
if (StringUtils.isNotEmpty(title))
{
Collection<IGuide> guides = guidesSet.findGuidesByTitle(title);
if (guides.size() > 0) guide = guides.iterator().next();
}
model.setSetup(prefs.getWhIgnore(), prefs.isWhNoSelfLinks(), prefs.isWhSuppressSameSourceLinks(), guide);
}
/**
* Resizes the specified component. This method is called during the build process and enables
* subclasses to achieve a better aspect ratio, by applying a resizer, e.g. the
* <code>Resizer</code>.
*
* @param component the component to be resized
*/
protected void resizeHook(JComponent component)
{
component.setPreferredSize(Resizer.ONE2ONE.fromWidth(MIN_SIZE.width));
}
// ---------------------------------------------------------------------------------------------
/**
* Scanner listener.
*/
private class ScannerListener implements PropertyChangeListener
{
/**
* Invoked when the state or progress changes.
*
* @param evt event.
*/
public void propertyChange(PropertyChangeEvent evt)
{
String prop = evt.getPropertyName();
if ("state".equals(prop)) onState((SwingWorker.StateValue)evt.getNewValue()); else
if ("progress".equals(prop)) onProgress((Integer)evt.getNewValue());
}
/**
* Invoked when the scanner state changes.
*
* @param state new state.
*/
private void onState(SwingWorker.StateValue state)
{
Component set = null;
switch (state)
{
case STARTED:
// Show the progress panel
set = pnlProgress;
break;
case DONE:
// Show the results
set = itemsList;
break;
}
if (set != null) scrollPanel.setViewportView(set);
}
/**
* Invoked when the scanner progress changes.
*
* @param progress [0 - 100].
*/
private void onProgress(int progress)
{
pnlProgress.setProgress(progress);
}
}
/**
* Setup dialog.
*/
private class SetupDialog extends AbstractDialog
{
private JComboBox cbGuides;
private JTextArea taIgnorePatterns;
private JCheckBox chDontCountSelfLinks;
private JCheckBox chSuppressSameSource;
/**
* Creates the dialog.
*
* @param prefs user preferences to manipulate.
*/
public SetupDialog(UserPreferences prefs)
{
super(Dialog.this, Strings.message("whatshot.dialog.title") + " - " + Strings.message("whatshot.setup"));
taIgnorePatterns = new JTextArea();
taIgnorePatterns.setLineWrap(false);
taIgnorePatterns.setDocument(new DocumentAdapter(new BufferedValueModel(new PropertyAdapter(
prefs, UserPreferences.PROP_WH_IGNORE), getTriggerChannel())));
chDontCountSelfLinks = ComponentsFactory.createCheckBox(
Strings.message("whatshot.setup.no.self.links"),
prefs, UserPreferences.PROP_WH_NOSELFLINKS, getTriggerChannel());
chSuppressSameSource = ComponentsFactory.createCheckBox(
Strings.message("whatshot.setup.no.same.source"),
prefs, UserPreferences.PROP_WH_SUPPRESS_SAME_SOURCE_LINKS, getTriggerChannel());
// Guide selector
final Map<String, FGuide> guidesMap = new HashMap<String, FGuide>();
Vector<FGuide> guides = new Vector<FGuide>();
FGuide allGuides = new FGuide("", Strings.message("whatshot.setup.all.guides"));
guides.add(allGuides);
guidesMap.put(allGuides.key, allGuides);
int cnt = guidesSet.getGuidesCount();
for (int i = 0; i < cnt; i++)
{
String t = guidesSet.getGuideAt(i).getTitle();
FGuide fg = new FGuide(t);
guides.add(fg);
guidesMap.put(fg.key, fg);
}
ValueModel mdlGuides = new AbstractConverter(
new PropertyAdapter(prefs, UserPreferences.PROP_WH_TARGET_GUIDE))
{
public Object convertFromSubject(Object o)
{
return guidesMap.get(o);
}
public void setValue(Object o)
{
FGuide guide = (FGuide)o;
subject.setValue(guide.key);
}
};
cbGuides = new JComboBox(new ComboBoxAdapter(guides, mdlGuides));
// Listening for setup changes
getTriggerChannel().addValueChangeListener(new PropertyChangeListener()
{
public void propertyChange(PropertyChangeEvent evt)
{
if (ValueHolder.PROPERTYNAME_VALUE.equals(evt.getPropertyName()))
{
if ((Boolean)evt.getNewValue()) onSetupChange();
}
}
});
}
/**
* Builds the dialog content.
*
* @return panel.
*/
protected JComponent buildContent()
{
JPanel panel = new JPanel(new BorderLayout());
panel.add(buildMainPanel(), BorderLayout.CENTER);
panel.add(buildButtonBarWithOKCancel(), BorderLayout.SOUTH);
return panel;
}
/**
* Builds main panel.
*
* @return panel.
*/
private Component buildMainPanel()
{
BBFormBuilder builder = new BBFormBuilder("min(p;200dlu)");
builder.setDefaultDialogBorder();
builder.append(new JLabel(Strings.message("whatshot.source.guides")));
builder.append(cbGuides);
builder.appendUnrelatedComponentsGapRow(2);
builder.append(new JLabel(Strings.message("whatshot.ignore.links")));
builder.appendRelatedComponentsGapRow(2);
builder.appendRow("100dlu");
builder.append(new JScrollPane(taIgnorePatterns), 1, CellConstraints.FILL, CellConstraints.FILL);
builder.appendUnrelatedComponentsGapRow(2);
builder.append(chDontCountSelfLinks);
builder.append(chSuppressSameSource);
return builder.getPanel();
}
@Override
public void doAccept()
{
// Set the change time
long time = DateUtils.localToUTC(System.currentTimeMillis());
GlobalModel.SINGLETON.getUserPreferences().setWhSettingsChangeTime(time);
super.doAccept();
}
/**
* Helper class to hold a guide key / title.
*/
private class FGuide
{
private String key;
private String title;
/**
* Creates a guide holder.
*
* @param title title.
*/
private FGuide(String title)
{
this(title, title);
}
/**
* Creates a guide holder.
*
* @param key key.
* @param title title.
*/
private FGuide(String key, String title)
{
this.key = key;
this.title = title;
}
@Override
public String toString()
{
return title;
}
}
}
}