// 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: AdvancedPreferencesPanel.java,v 1.76 2007/10/11 09:34:34 spyromus Exp $ // package com.salas.bb.dialogs; 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.ValueModel; import com.jgoodies.uif.util.SystemUtils; import com.jgoodies.uifextras.convenience.TipOfTheDayDialog; import com.salas.bb.core.ApplicationLauncher; import com.salas.bb.core.GlobalController; import com.salas.bb.domain.prefs.StarzPreferences; import com.salas.bb.domain.prefs.UserPreferences; import com.salas.bb.utils.BrowserLauncher; import com.salas.bb.utils.OSSettings; import com.salas.bb.utils.SpinnerModelAdapter; import com.salas.bb.utils.StringUtils; import com.salas.bb.utils.i18n.Strings; import com.salas.bb.utils.notification.NotificationArea; import com.salas.bb.utils.uif.BBFormBuilder; import com.salas.bb.utils.uif.ComponentsFactory; import com.salas.bb.utils.uif.StateUpdatingToggleListener; import com.salas.bb.utils.uif.VertialScrollablePanel; import com.salas.bb.views.settings.FeedRenderingSettings; 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.regex.Pattern; /** * Builds the Advanced Options Panel for the General Preferences Dialog. */ public class AdvancedPreferencesPanel extends JPanel { private static final String PAT_IP = "([1-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-4])"; private static final String PAT_SN = "[a-z][a-z0-9]+"; private static final String PAC_REC = "\\s*("+ PAT_IP +"(\\."+ PAT_IP +"){3}|"+ PAT_SN +"(\\."+ PAT_SN +")*)\\s*"; private static final Pattern PAT_VALID_PROXY_EX = Pattern.compile("^"+ PAC_REC +"(,"+ PAC_REC +")*$", Pattern.CASE_INSENSITIVE); private UserPreferences userPrefs; private StarzPreferences starzPrefs; private final FeedRenderingSettings feedRS; private ValueModel triggerChannel; private JCheckBox chShowTipsBox; private JCheckBox chDoUpdatesCheck; private JTextField tfInternetBrowser; private JCheckBox chProxyEnabled; private JTextField tfProxyHost; private JLabel lbProxyPort; private JSpinner spinProxyPort; private JLabel lbProxyExclusions; private JTextField tfProxyExclusions; private int initActivityLimit; private int initHightlightsLimit; private JCheckBox chAAText; private JCheckBox chShowUnreadButtonMenu; private JSpinner spinFeedImportLimit; private JTextField tfPingURL; private JCheckBox chPing; private JRadioButton rbGSMFirst; private JRadioButton rbGSMLastSeen; private JRadioButton rbGSMNoFeed; private JCheckBox chHideOldArticles; private JCheckBox chDisplayFullTitles; private JCheckBox chSortingAscending; private JTextField tfSuppressOlderThan; private JCheckBox chCopyLinksInHREFFormat; private JCheckBox chBrowseOnTitleDblClick; private JCheckBox chShowAppIconInSystray; private JCheckBox chMinimizeToSystray; private JComboBox cbBIDMode; private JCheckBox chAlwaysUseEnglish; /** * Constructs the <i>Advanced</i> panel for the preferences dialog. * * @param aUserPrefs user preferences. * @param aStarzPrefs starz preferences. * @param aFeedRS feed rendering settings. * @param aTriggerChannel triggers a commit when apply is pressed. */ public AdvancedPreferencesPanel(UserPreferences aUserPrefs, StarzPreferences aStarzPrefs, FeedRenderingSettings aFeedRS, ValueModel aTriggerChannel) { feedRS = aFeedRS; this.userPrefs = aUserPrefs; this.starzPrefs = aStarzPrefs; this.triggerChannel = aTriggerChannel; initComponents(triggerChannel); build(); } // Component Creation and Initialization ******************************** private void initComponents(ValueModel triggerChannel) { chShowTipsBox = ComponentsFactory.createCheckBox( Strings.message("userprefs.tab.general.show.tip.of.the.day"), new ToggleButtonAdapter(new BufferedValueModel(TipOfTheDayDialog.showingTipsModel(), triggerChannel))); String msg = Strings.message("userprefs.tab.general.check.for.new.versions"); if (ApplicationLauncher.isAutoUpdatesEnabled()) { chDoUpdatesCheck = ComponentsFactory.createCheckBox( msg, new ToggleButtonAdapter(new BufferedValueModel(new PropertyAdapter(userPrefs, UserPreferences.PROP_CHECKING_FOR_UPDATES_ON_STARTUP), triggerChannel))); } else { msg = msg.replaceAll("&", ""); chDoUpdatesCheck = new JCheckBox(msg); chDoUpdatesCheck.setSelected(true); chDoUpdatesCheck.setEnabled(false); } chAlwaysUseEnglish = ComponentsFactory.createCheckBox( Strings.message("userprefs.tab.advanced.always.use.english"), new ToggleButtonAdapter(new BufferedValueModel(new PropertyAdapter(userPrefs, UserPreferences.PROP_ALWAYS_USE_ENGLISH), triggerChannel))); tfInternetBrowser = new JTextField(); tfInternetBrowser.setDocument(new DocumentAdapter(new BufferedValueModel( new PropertyAdapter(userPrefs, UserPreferences.PROP_INTERNET_BROWSER), triggerChannel))); if (BrowserLauncher.isUsingJWSBrowser()) { tfInternetBrowser.setEnabled(false); tfInternetBrowser.setToolTipText(Strings.message("userprefs.tab.advanced.browser.tooltip.disabled")); } else { tfInternetBrowser.setEnabled(true); tfInternetBrowser.setToolTipText(Strings.message("userprefs.tab.advanced.browser.tooltip.enabled")); } initProxyComponents(triggerChannel); SpinnerModelAdapter spinModelFeedSelectionDelay = new SpinnerModelAdapter( new BoundedRangeAdapter(new BufferedValueModel(new PropertyAdapter(userPrefs, UserPreferences.PROP_FEED_SELECTION_DELAY), triggerChannel), 0, 0, 1000)); spinModelFeedSelectionDelay.setStepSize(100); chAAText = createCheckBox(userPrefs, Strings.message("userprefs.tab.advanced.antialiased.fonts"), UserPreferences.PROP_AA_TEXT); // The following code registers listener on Apply/Accept actions to track down // changes in BlogStarz limits. If changes are detected then channel list is // repainted. saveInitialLimits(); triggerChannel.addValueChangeListener(new PropertyChangeListener() { public void propertyChange(PropertyChangeEvent evt) { if (Boolean.TRUE.equals(evt.getNewValue())) doRepaintChannelList(); } }); chShowUnreadButtonMenu = createCheckBox(userPrefs, Strings.message("userprefs.tab.advanced.show.the.unread.buttons.menu"), UserPreferences.PROP_SHOW_UNREAD_BUTTON_MENU); SpinnerModelAdapter spinModelFeedImportLimit = new SpinnerModelAdapter(new BoundedRangeAdapter( new BufferedValueModel(new PropertyAdapter(userPrefs, UserPreferences.PROP_FEED_IMPORT_LIMIT), triggerChannel), 1, 1, UserPreferences.MAX_FEED_IMPORT_LIMITATION)); spinModelFeedImportLimit.setStepSize(100); spinFeedImportLimit = new JSpinner(spinModelFeedImportLimit); initGuideComponents(triggerChannel); chHideOldArticles = createCheckBox(feedRS, Strings.message("userprefs.tab.articles.hide.articles.older.than"), "suppressingOlderThan"); chDisplayFullTitles = createCheckBox(feedRS, Strings.message("userprefs.tab.articles.display.full.titles"), "displayingFullTitles"); chSortingAscending = createCheckBox(feedRS, Strings.message("userprefs.tab.articles.sort.earlier.articles.first"), "sortingAscending"); tfSuppressOlderThan = new JTextField(); tfSuppressOlderThan.setDocument(new DocumentAdapter(new BufferedValueModel( new PropertyAdapter(feedRS, "suppressOlderThanString"), triggerChannel))); chCopyLinksInHREFFormat = createCheckBox(userPrefs, Strings.message("userprefs.tab.articles.use.href.format.for.links.copied.to.clipboard"), UserPreferences.PROP_COPY_LINKS_IN_HREF_FORMAT); chBrowseOnTitleDblClick = createCheckBox(userPrefs, Strings.message("userprefs.tab.articles.browse.on.double.click.over.the.title"), UserPreferences.PROP_BROWSE_ON_DBL_CLICK); chShowAppIconInSystray = createCheckBox(userPrefs, Strings.message("userprefs.tab.advanced.show.appicon.in.systray"), UserPreferences.PROP_SHOW_APPICON_IN_SYSTRAY); chMinimizeToSystray = createCheckBox(userPrefs, Strings.message("userprefs.tab.advanced.minimize.to.systray"), UserPreferences.PROP_MINIMIZE_TO_SYSTRAY); // Dock icon ValueModel modeModel = new BufferedValueModel( new BIDModeToStringConverter(new PropertyAdapter(userPrefs, UserPreferences.PROP_DIB_MODE)), triggerChannel); cbBIDMode = new JComboBox(new ComboBoxAdapter(BIDModeToStringConverter.MODES, modeModel)); } private void initGuideComponents(ValueModel triggerChannel) { // Guide selection mode group ValueModel gsmModel = new BufferedValueModel( new PropertyAdapter(userPrefs, UserPreferences.PROP_GUIDE_SELECTION_MODE), triggerChannel); rbGSMFirst = createRadioButton(gsmModel, UserPreferences.GSM_FIRST_FEED, Strings.message("userprefs.tab.guides.select.first.feed")); rbGSMLastSeen = createRadioButton(gsmModel, UserPreferences.GSM_LAST_SEEN_FEED, Strings.message("userprefs.tab.guides.select.last.seen.feed")); rbGSMNoFeed = createRadioButton(gsmModel, UserPreferences.GSM_NO_FEED, Strings.message("userprefs.tab.guides.select.no.feed")); // Publication chPing = createCheckBox(userPrefs, Strings.message("userprefs.tab.guides.ping.url"), UserPreferences.PROP_PING_ON_RL_PUBLICATION); chPing.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { updatePingURLState(); } }); tfPingURL = new JTextField(); tfPingURL.setDocument(new DocumentAdapter(new BufferedValueModel( new PropertyAdapter(userPrefs, UserPreferences.PROP_PING_ON_RL_PUBLICATION_URL), triggerChannel))); updatePingURLState(); String ttPing = Strings.message("userprefs.tab.guides.ping.wording"); chPing.setToolTipText(ttPing); tfPingURL.setToolTipText(ttPing); } /** Updates the state of the field depending on the check-state of the box. */ private void updatePingURLState() { tfPingURL.setEnabled(chPing.isSelected()); } /** * Creates mode radio-button. * * @param model GSM model. * @param mode mode value. * @param caption caption. * * @return button. */ private JRadioButton createRadioButton(ValueModel model, int mode, String caption) { return ComponentsFactory.createRadioButton(caption, new RadioButtonAdapter(model, mode)); } /** * Initializes proxy components. * * @param triggerChannel trigger. */ private void initProxyComponents(ValueModel triggerChannel) { chProxyEnabled = createCheckBox(userPrefs, Strings.message("userprefs.tab.advanced.use.proxy"), UserPreferences.PROP_PROXY_ENABLED); tfProxyHost = new JTextField(); tfProxyHost.setDocument(new DocumentAdapter(new BufferedValueModel( new PropertyAdapter(userPrefs, UserPreferences.PROP_PROXY_HOST), triggerChannel))); lbProxyPort = ComponentsFactory.createLabel(Strings.message("userprefs.tab.advanced.use.proxy.port")); spinProxyPort = new JSpinner(new SpinnerModelAdapter(new BoundedRangeAdapter( new BufferedValueModel(new PropertyAdapter(userPrefs, UserPreferences.PROP_PROXY_PORT), triggerChannel), 80, 80, 65536))); spinProxyPort.setEditor(new JSpinner.NumberEditor(spinProxyPort, "#")); lbProxyExclusions = ComponentsFactory.createLabel(Strings.message("userprefs.tab.advanced.no.proxy.for")); tfProxyExclusions = new JTextField(); tfProxyExclusions.setDocument(new DocumentAdapter(new BufferedValueModel( new PropertyAdapter(userPrefs, UserPreferences.PROP_PROXY_EXCLUSIONS), triggerChannel))); StateUpdatingToggleListener.install(chProxyEnabled, tfProxyHost, lbProxyPort, spinProxyPort, lbProxyExclusions, tfProxyExclusions); } /** * Helper function to create a checkbox. * @param obj Object holding property. * @param label Label for checkbox. * @param propertyName Property to save on object. * @return new JCheckbox. */ private JCheckBox createCheckBox(Object obj, String label, String propertyName) { return ComponentsFactory.createCheckBox(label, new ToggleButtonAdapter( new BufferedValueModel(new PropertyAdapter(obj, propertyName), triggerChannel))); } /** * Saves the limits of parameters for future comparison. */ private void saveInitialLimits() { initActivityLimit = starzPrefs.getTopActivity(); initHightlightsLimit = starzPrefs.getTopHighlights(); } /** * Performs repainting of channels list if starz limits changed changed. */ private void doRepaintChannelList() { if (starzPrefs.getTopActivity() != initActivityLimit || starzPrefs.getTopHighlights() != initHightlightsLimit) { saveInitialLimits(); GlobalController.SINGLETON.getMainFrame().getFeedsPanel().repaint(); } } /** * Creates a FormLayout and adds the UI components using a PanelBuilder. */ private void build() { JPanel panel = new VertialScrollablePanel(); String version = System.getProperty("java.vm.version"); boolean is15 = version != null && version.startsWith("1.5"); BBFormBuilder builder = new BBFormBuilder("7dlu, p, 2dlu, 40dlu, 70dlu:grow", panel); builder.setDefaultDialogBorder(); builder.appendSeparator(Strings.message("userprefs.tab.advanced.separator.on.startup")); builder.setLeadingColumnOffset(1); builder.append(chShowTipsBox, 4); builder.append(chDoUpdatesCheck, 4); builder.append(chAlwaysUseEnglish, 4); builder.setLeadingColumnOffset(1); builder.appendSeparator(Strings.message("userprefs.tab.advanced.separator.network")); builder.nextColumn(); builder.append(Strings.message("userprefs.tab.advanced.browser"), tfInternetBrowser, 2); builder.append(chProxyEnabled); builder.append(buildProxyPanel(), 2); builder.append(lbProxyExclusions); builder.append(tfProxyExclusions, 2); tfProxyExclusions.setToolTipText(Strings.message("userprefs.tab.advanced.no.proxy.for.notes")); builder.appendSeparator(Strings.message("userprefs.tab.advanced.separator.interface")); builder.nextLine(); builder.append(Strings.message("userprefs.tab.advanced.feed.import.limit"), spinFeedImportLimit); builder.nextLine(); builder.append(chShowUnreadButtonMenu, 4); if (is15) builder.append(chAAText, 4); if (NotificationArea.isSupported()) { if (!SystemUtils.IS_OS_MAC) builder.append(chShowAppIconInSystray, 4); if (OSSettings.isMinimizeToSystraySupported()) builder.append(chMinimizeToSystray, 4); } // Mac-specific Dock icon settings if (SystemUtils.IS_OS_MAC) builder.append(Strings.message("userprefs.tab.advanced.dock.icon"), cbBIDMode, 2); builder.appendSeparator(Strings.message("userprefs.tab.guides.on.selection")); builder.append(rbGSMFirst, 4); builder.append(rbGSMLastSeen, 4); builder.append(rbGSMNoFeed, 4); builder.appendSeparator(Strings.message("userprefs.tab.guides.on.publication")); builder.append(chPing); builder.append(tfPingURL, 2); builder.appendSeparator(Strings.message("userprefs.tab.advanced.articles")); builder.append(chSortingAscending, 4); builder.append(chHideOldArticles, tfSuppressOlderThan); builder.nextLine(); builder.append(chDisplayFullTitles, 4); builder.append(chCopyLinksInHREFFormat, 4); builder.append(chBrowseOnTitleDblClick, 4); // Finish setLayout(new BorderLayout()); JScrollPane sp = new JScrollPane(panel); sp.setBorder(BorderFactory.createEmptyBorder()); sp.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER); add(sp, BorderLayout.CENTER); } /** * Creates proxy panel. * * @return panel. */ private JPanel buildProxyPanel() { BBFormBuilder builder = new BBFormBuilder("50dlu:grow, 10dlu, pref, 2dlu, 30dlu"); builder.append(tfProxyHost); builder.append(lbProxyPort, spinProxyPort); lbProxyPort.setLabelFor(spinProxyPort); return builder.getPanel(); } public String checkValidity() { String msg = null; String txt = tfProxyExclusions.getText(); if (!isValidProxyExclusions(txt)) { msg = "Please correct the list of Proxy Exclusions.\n" + "It should be the comma-separated list of IP\n" + "addresses or site names."; } return msg; } /** * Checks if the text is a valid list of proxy exclusions. * * @param txt text. * * @return <code>TRUE</code> if valid. */ static boolean isValidProxyExclusions(String txt) { return StringUtils.isEmpty(txt) || PAT_VALID_PROXY_EX.matcher(txt).matches(); } /** * Converter of BID mode to string and vice-versa. */ private static class BIDModeToStringConverter extends AbstractConverter { public static final String[] MODES = { Strings.message("userprefs.tab.advanced.dock.icon.invisible"), Strings.message("userprefs.tab.advanced.dock.icon.unread.articles"), Strings.message("userprefs.tab.advanced.dock.icon.unread.feeds") }; /** * Creates converter for some mode. * * @param model model. */ public BIDModeToStringConverter(ValueModel model) { super(model); } /** * Converts from the subject model. * * @param object object. * * @return converted version. */ public Object convertFromSubject(Object object) { int mode = (Integer)object; return MODES[mode]; } /** * Sets value into the subject mode. * * @param object value. */ public void setValue(Object object) { // Arrays.binarySearch doesn't work int index = -1; for (int i = 0; index < 0 && i < MODES.length; i++) { String mode = MODES[i]; if (mode.equals(object)) index = i; } subject.setValue(index); } } }