Java tutorial
// 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: DirectFeedPropertiesDialog.java,v 1.40 2008/03/05 16:34:17 spyromus Exp $ // package com.salas.bb.dialogs; import com.jgoodies.forms.factories.Borders; import com.jgoodies.forms.factories.ButtonBarFactory; import com.jgoodies.forms.layout.CellConstraints; import com.jgoodies.uif.AbstractDialog; import com.jgoodies.uif.util.Resizer; import com.salas.bb.core.FeedFormatter; import com.salas.bb.core.GlobalController; import com.salas.bb.core.GlobalModel; import com.salas.bb.core.ScoresCalculator; import com.salas.bb.domain.DirectFeed; import com.salas.bb.domain.prefs.StarzPreferences; import com.salas.bb.utils.BrowserLauncher; import com.salas.bb.utils.Constants; import com.salas.bb.utils.DateUtils; 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.utils.uif.LinkLabel; import javax.swing.*; import javax.swing.event.DocumentEvent; import javax.swing.event.DocumentListener; import java.awt.*; import java.awt.event.*; import java.net.MalformedURLException; import java.net.URL; import java.text.DecimalFormat; import java.text.MessageFormat; import java.util.Date; import java.util.Locale; import java.util.StringTokenizer; import java.util.logging.Level; import java.util.logging.Logger; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * Displays properties of channel. */ public class DirectFeedPropertiesDialog extends AbstractDialog { private static final Logger LOG = Logger.getLogger(DirectFeedPropertiesDialog.class.getName()); private static final Pattern PAT_EMAIL = Pattern.compile( "(^|[\\s\\[\\(\\{\\<])([a-zA-Z]\\w*(([_-]+|\\.)\\w+)*@[\\w-_]{2,}(\\.[\\w-_]{2,})+)($|[\\s\\]\\)\\}\\>])"); private static final int MAX_HEADER_TITLE_LENGTH = 40; private static final DecimalFormat DECIMAL_FORMAT = new DecimalFormat("#0.00"); private DirectFeed feed; private JTextField tfXmlUrl; private JTextField tfTitle; private JTextField tfAuthor; private JTextArea taDescription; private JTextField tfPurgeLimit; private JLabel lbFinalScore; private int blogStarzScore; private int rating; private String initialTitle; private String initialAuthor; private String initialDescription; private int initialPurgeLimit; private long initialUpdatePeriod; private JButton btnCancel; private LinkLabel tfSiteUrl; private JButton btnSendEmail; private DisplayPropertiesTabPanel displayTab; private FeedUpdatePeriodPanel pnlFeedUpdatePeriod; private FeedAutoSavePanel pnlFeedAutoSave; /** * Creates new properties dialog. * * @param owner owning frame for this Dialog. * @param aFeed feed for which we are showing properties. */ public DirectFeedPropertiesDialog(Frame owner, DirectFeed aFeed) { super(owner, Strings.message("show.feed.properties.dialog.title.directfeed")); setResizable(); feed = aFeed; } /** * Builds a pretty XP-style white header. * * @return header object. */ protected JComponent buildHeader() { String title = feed.getTitle(); if (title != null && title.length() > MAX_HEADER_TITLE_LENGTH) title = title.substring(0, MAX_HEADER_TITLE_LENGTH) + ("\u2026"); return new HeaderPanelExt(Strings.message("show.feed.properties.dialog.title.directfeed"), MessageFormat.format(Strings.message("show.feed.properties.dialog.header.direct"), title)); } /** * Returns the content of the dialog. * * @return contents. */ protected JComponent buildContent() { JPanel content = new JPanel(new BorderLayout()); content.add(buildBody(), BorderLayout.CENTER); content.add(buildButtonBar(), BorderLayout.SOUTH); return content; } /** * Builds buttons bar. * * @return bar. */ private Component buildButtonBar() { JButton btnOk = createOKButton(true); btnCancel = createCancelButton(); JPanel buttonPanel = ButtonBarFactory.buildOKCancelBar(btnOk, btnCancel); buttonPanel.setBorder(Borders.BUTTON_BAR_GAP_BORDER); return buttonPanel; } /** * Creates custom revert button. * * @return button. */ private JButton createRevertButton() { JButton btnRevert = ComponentsFactory .createButton(Strings.message("show.feed.properties.revert.to.default")); btnRevert.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { tfTitle.setText(feed.getBaseTitle()); tfAuthor.setText(feed.getBaseAuthor()); taDescription.setText(feed.getBaseDescription()); } }); return btnRevert; } @Override public void setVisible(boolean b) { // Request focus for the title tfTitle.requestFocusInWindow(); super.setVisible(b); } /** * Applies changes if some. */ public void doApply() { String msg = pnlFeedAutoSave == null ? null : pnlFeedAutoSave.validateData(); if (msg != null) { JOptionPane.showMessageDialog(this, msg, getTitle(), JOptionPane.ERROR_MESSAGE); return; } String newTitle = tfTitle.getText(); String newAuthor = tfAuthor.getText(); String newDescription = taDescription.getText(); int newPurgeLimit = getFormPurgeLimit(); long newUpdatePeriod = pnlFeedUpdatePeriod.getUpdatePeriod(); if (!newTitle.equals(initialTitle == null ? Constants.EMPTY_STRING : initialTitle)) { if (newTitle.equals(feed.getBaseTitle())) newTitle = null; feed.setCustomTitle(newTitle); } if (!newAuthor.equals(initialAuthor == null ? Constants.EMPTY_STRING : initialAuthor)) { if (newAuthor.equals(feed.getBaseAuthor())) newAuthor = null; feed.setCustomAuthor(newAuthor); } if (!newDescription.equals(initialDescription == null ? Constants.EMPTY_STRING : initialDescription)) { if (newDescription.equals(feed.getBaseDescription())) newDescription = null; feed.setCustomDescription(newDescription); } // Custom purge limit if (newPurgeLimit != initialPurgeLimit) { feed.setPurgeLimit(newPurgeLimit); } // Custom update period if (newUpdatePeriod != initialUpdatePeriod) { feed.setUpdatePeriod(newUpdatePeriod); } // Ratings if (rating != feed.getRating()) { feed.setRating(rating); } // Feed type displayTab.commitChanges(); if (pnlFeedAutoSave != null) pnlFeedAutoSave.commitChanges(feed); // Put XML url if the feed is not initialized if (!feed.isInitialized()) { String newUrl = tfXmlUrl.getText(); if (newUrl.trim().length() == 0) newUrl = null; try { feed.setXmlURL(newUrl == null ? null : new URL(newUrl)); } catch (MalformedURLException e) { // TODO possibly report an error or do some type validation LOG.log(Level.WARNING, MessageFormat.format(Strings.error("invalid.url"), newUrl), e); } } } /** * Returns purge limit setting from form. * * @return purge limit. */ private int getFormPurgeLimit() { int purgeLimit = -1; String purgeLimitString = tfPurgeLimit.getText(); if (purgeLimitString != null && purgeLimitString.trim().length() != 0) { purgeLimit = Integer.parseInt(purgeLimitString); } return purgeLimit; } /** * Sets new purge limit value. * * @param purgeLimit new purge limit. */ private void setFormPurgeLimit(int purgeLimit) { String text = purgeLimit == -1 ? Constants.EMPTY_STRING : Integer.toString(purgeLimit); tfPurgeLimit.setText(text); } /** * Performs cancel operation only if cancel button is enbled. */ public void doCancel() { if (btnCancel.isEnabled()) super.doCancel(); } /** * Performs close operation only if cancel button is enbled. */ public void doClose() { if (btnCancel.isEnabled()) super.doClose(); } /** * Returns the JPanel which will be the body of this little dialog. * * @return JComponent body of this Dialog Box. */ private JComponent buildBody() { initComponents(); JPanel panel = new JPanel(new BorderLayout()); JTabbedPane pane = new JTabbedPane(); panel.add(pane, BorderLayout.CENTER); displayTab = new DisplayPropertiesTabPanel(feed); pane.addTab(Strings.message("show.feed.properties.tab.basic"), createBasicTab()); pane.addTab(Strings.message("show.feed.properties.tab.display"), displayTab); pane.addTab(Strings.message("show.feed.properties.tab.blogstarz"), createBlogStarzTab()); pane.addTab(Strings.message("show.feed.properties.tab.advanced"), createAdvancedTab()); adjustReadOnly(); return panel; } /** * Creates basic tab component. * * @return basic tab. */ private Component createBasicTab() { JScrollPane spDescription = new JScrollPane(taDescription); BBFormBuilder builder = new BBFormBuilder("pref, 4dlu, 0:grow, 2dlu, p"); builder.setDefaultDialogBorder(); builder.append(Strings.message("show.feed.properties.tab.basic.title"), tfTitle, 3); builder.appendRelatedComponentsGapRow(2); builder.appendRow("50dlu"); JLabel label = builder.append(Strings.message("show.feed.properties.tab.basic.description"), 1, CellConstraints.FILL, CellConstraints.TOP); label.setLabelFor(taDescription); builder.append(spDescription, 3, CellConstraints.FILL, CellConstraints.FILL); builder.append(Strings.message("show.feed.properties.tab.basic.author"), tfAuthor); builder.append(btnSendEmail); builder.append(Strings.message("show.feed.properties.tab.basic.siteurl"), tfSiteUrl, 3); builder.append(Strings.message("show.feed.properties.tab.basic.xmlurl"), tfXmlUrl, 3); builder.append(Strings.message("show.feed.properties.tab.basic.language"), new JLabel(convertLang2String(feed.getLanguage())), 3); builder.appendUnrelatedComponentsGapRow(2); builder.append(ButtonBarFactory.buildCenteredBar(createRevertButton()), 5); builder.appendRow("14dlu:grow"); return builder.getPanel(); } /** * Initializes components. */ private void initComponents() { initialUpdatePeriod = feed.getUpdatePeriod(); // Basic Tab initialTitle = feed.getTitle(); tfTitle = new JTextField(initialTitle); initialAuthor = feed.getAuthor(); tfAuthor = new JTextField(initialAuthor); tfAuthor.getDocument().addDocumentListener(new AuthorFieldUpdateListener()); initialDescription = feed.getDescription(); taDescription = new JTextArea(initialDescription); taDescription.setLineWrap(true); taDescription.setWrapStyleWord(true); URL siteURL = feed.getSiteURL(); tfSiteUrl = new LinkLabel(); tfSiteUrl.setText(siteURL == null ? null : siteURL.toString()); tfSiteUrl.setLink(siteURL); URL xmlURL = feed.getXmlURL(); tfXmlUrl = new JTextField(xmlURL == null ? null : xmlURL.toString()); btnSendEmail = ComponentsFactory.createButton(Strings.message("show.feed.properties.tab.basic.send.email")); btnSendEmail.addActionListener(new SendEmailToAuthorAction()); updateSendEmailButton(); pnlFeedUpdatePeriod = new FeedUpdatePeriodPanel(feed.getUpdatePeriod()); pnlFeedAutoSave = new FeedAutoSavePanel(feed, GlobalController.SINGLETON.getFeatureManager().isAutoSaving()); } // Updates the state of the send email button private void updateSendEmailButton() { btnSendEmail.setEnabled(getAuthorEmail() != null); } /** * Returns first email mentioned in the author field. * * @return email or NULL if not found. */ private String getAuthorEmail() { String email = null; String author = tfAuthor.getText(); if (!StringUtils.isEmpty(author)) { Matcher matcher = PAT_EMAIL.matcher(author); if (matcher.find()) email = matcher.group(2); } return email; } /** * Adjusts the state of components. */ private void adjustReadOnly() { boolean feedInitialized = feed.isInitialized(); tfXmlUrl.setEditable(!feedInitialized); tfAuthor.setEditable(feedInitialized); taDescription.setEditable(feedInitialized); tfTitle.setEditable(feedInitialized); } /** * Creates advanced tab. * * @return advanced tab. */ private Component createAdvancedTab() { initialPurgeLimit = feed.getPurgeLimitCombined(); tfPurgeLimit = new JTextField(); setFormPurgeLimit(initialPurgeLimit); JLabel lbArticleCount = new JLabel(Integer.toString(feed.getArticlesCount())); JLabel lbRetrievals = new JLabel(String.valueOf(feed.getRetrievals())); JLabel lbLastUpdate = new JLabel(DateUtils.dateToString(new Date(feed.getLastPollTime()))); JLabel lbFormat = new JLabel(feed.getFormat()); BBFormBuilder builder = new BBFormBuilder("pref, 4dlu, max(pref;40px), 2dlu, pref:grow"); builder.setDefaultDialogBorder(); builder.append(Strings.message("show.feed.properties.tab.advanced.articles"), lbArticleCount, 3); builder.append(Strings.message("show.feed.properties.tab.advanced.retrievals"), lbRetrievals, 3); builder.append(Strings.message("show.feed.properties.tab.advanced.last.update"), lbLastUpdate, 3); builder.append(Strings.message("show.feed.properties.tab.advanced.format"), lbFormat, 3); builder.append(Strings.message("show.feed.properties.tab.advanced.purge.limit"), tfPurgeLimit); builder.nextLine(); builder.append(Strings.message("show.feed.properties.tab.advanced.update.period"), 1, CellConstraints.LEFT, CellConstraints.TOP); builder.append(pnlFeedUpdatePeriod, 3); if (pnlFeedAutoSave != null) { builder.appendUnrelatedComponentsGapRow(2); builder.append(pnlFeedAutoSave, 5); } return builder.getPanel(); } /** * Creates blog starz tab. * * @return tab. */ private Component createBlogStarzTab() { ScoresCalculator calc = GlobalModel.SINGLETON.getScoreCalculator(); double activity = calc.calcActivity(feed); double inlinks = calc.calcInlinksScore(feed); double views = calc.calcFeedViewsScore(feed); double clickthroughs = calc.calcClickthroughsScore(feed); blogStarzScore = calc.calcBlogStarzScore(feed); rating = feed.getRating(); StarzPreferences prefs = GlobalModel.SINGLETON.getStarzPreferences(); int activityWeight = prefs.getActivityWeight(); int inlinksWeight = prefs.getInlinksWeight(); int viewsWeight = prefs.getFeedViewsWeight(); int clickthroughsWeight = prefs.getClickthroughsWeight(); String msg = feed.getTextualInboundLinks(); lbFinalScore = new JLabel(); lbFinalScore.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)); lbFinalScore.addMouseListener(new FinalScoreStarzListener()); updateFinalScoreIcon(); JLabel lbTechnoratiInlinks = new JLabel(msg); JLabel lbActivity = new JLabel( MessageFormat.format(Strings.message("show.feed.properties.tab.blogstarz.0.weight.1"), DECIMAL_FORMAT.format(activity), activityWeight)); JLabel lbInLinks = new JLabel( MessageFormat.format(Strings.message("show.feed.properties.tab.blogstarz.0.weight.1"), DECIMAL_FORMAT.format(inlinks), inlinksWeight)); JLabel lbViews = new JLabel( MessageFormat.format(Strings.message("show.feed.properties.tab.blogstarz.0.weight.1"), DECIMAL_FORMAT.format(views), viewsWeight)); JLabel lbClickthroughs = new JLabel( MessageFormat.format(Strings.message("show.feed.properties.tab.blogstarz.0.weight.1"), DECIMAL_FORMAT.format(clickthroughs), clickthroughsWeight)); JLabel lbRecommendation = new JLabel(FeedFormatter.getStarzIcon(blogStarzScore, true)); BBFormBuilder builder = new BBFormBuilder("pref, 4dlu, pref, 4dlu, pref:grow"); builder.setDefaultDialogBorder(); builder.append(Strings.message("show.feed.properties.tab.blogstarz.technorati.inlinks"), lbTechnoratiInlinks, 3); builder.append(Strings.message("show.feed.properties.tab.blogstarz.activity"), lbActivity, 3); builder.append(Strings.message("show.feed.properties.tab.blogstarz.inlinks"), lbInLinks, 3); builder.append(Strings.message("show.feed.properties.tab.blogstarz.views"), lbViews, 3); builder.append(Strings.message("show.feed.properties.tab.blogstarz.clickthroughs"), lbClickthroughs, 3); builder.append(Strings.message("show.feed.properties.tab.blogstarz.recommendation"), 1); builder.append(lbRecommendation, 1, CellConstraints.LEFT, CellConstraints.CENTER); builder.appendUnrelatedComponentsGapRow(2); builder.append(Strings.message("show.feed.properties.tab.blogstarz.final.rating"), 1); builder.append(lbFinalScore, 1, CellConstraints.LEFT, CellConstraints.CENTER); builder.append(new JButton(new RevertAction()), 1, CellConstraints.RIGHT, CellConstraints.CENTER); builder.appendRow("pref:grow"); builder.append( ComponentsFactory .createWrappedMultilineLabel(Strings.message("show.feed.properties.tab.blogstarz.notes")), 5, CellConstraints.FILL, CellConstraints.BOTTOM); return builder.getPanel(); } /** * Converts compressed language-country code into human-readable form. * * @param language language. * * @return string. */ private static String convertLang2String(String language) { if (language == null) return null; language = language.replaceAll("-", "_"); StringTokenizer st = new StringTokenizer(language, "_"); String lang; String country = ""; String variant = ""; String display = language; if (language.length() > 0) { try { lang = st.nextToken(); if (st.hasMoreTokens()) country = st.nextToken().toUpperCase(); if (st.hasMoreTokens()) variant = st.nextToken(); Locale l = new Locale(lang, country, variant); display = l.getDisplayName(); } catch (Exception e) { if (LOG.isLoggable(Level.WARNING)) { LOG.log(Level.WARNING, MessageFormat.format( Strings.error("failed.to.transform.the.language.into.printable.form"), language), e); } } } return display; } // Updates the icon according to the rating setting. private void updateFinalScoreIcon() { if (rating == -1) { lbFinalScore.setIcon(FeedFormatter.getStarzIcon(blogStarzScore, true)); } else { lbFinalScore.setIcon(FeedFormatter.getStarzIcon(rating, false)); } } /** * 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.fromHeight(component.getPreferredSize().height)); } /** * Listens for clicks over the final score starz and updates the view by applying or * resetting user supplied rating. */ private class FinalScoreStarzListener extends MouseAdapter { public void mouseClicked(MouseEvent e) { if ((e.getModifiers() & InputEvent.SHIFT_MASK) == 0) { rating = (int) (5.0 * e.getX() / lbFinalScore.getIcon().getIconWidth()); } else { rating = -1; } updateFinalScoreIcon(); } } /** Reverts rating to default value. */ private class RevertAction extends AbstractAction { public RevertAction() { super(Strings.message("show.feed.properties.revert.to.recommendation")); } public void actionPerformed(ActionEvent e) { rating = -1; updateFinalScoreIcon(); } } /** * Sends email to author mentioned in the author field. */ private class SendEmailToAuthorAction implements ActionListener { /** * Invoked when an action occurs. */ public void actionPerformed(ActionEvent e) { String email = getAuthorEmail(); if (email != null) { String browser = GlobalModel.SINGLETON.getUserPreferences().getInternetBrowser(); BrowserLauncher.emailThis(email, tfTitle.getText(), "", browser); } } } private class AuthorFieldUpdateListener implements DocumentListener { /** * Gives notification that there was an insert into the document. The * range given by the DocumentEvent bounds the freshly inserted region. * * @param e the document event */ public void insertUpdate(DocumentEvent e) { updateSendEmailButton(); } /** * Gives notification that a portion of the document has been * removed. The range is given in terms of what the view last * saw (that is, before updating sticky positions). * * @param e the document event */ public void removeUpdate(DocumentEvent e) { updateSendEmailButton(); } /** * Gives notification that an attribute or set of attributes changed. * * @param e the document event */ public void changedUpdate(DocumentEvent e) { updateSendEmailButton(); } } }