swingx-master/swingx-core/src/main/java/org/jdesktop/swingx/plaf/basic/BasicTipOfTheDayUI.java
/*
 * $Id$
 *
 * Copyright 2004 Sun Microsystems, Inc., 4150 Network Circle,
 * Santa Clara, California 95054, U.S.A. All rights reserved.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 * 
 * This library 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
 * Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 */
package org.jdesktop.swingx.plaf.basic;

import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Dialog;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Frame;
import java.awt.GridLayout;
import java.awt.Window;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.Locale;

import javax.swing.AbstractAction;
import javax.swing.ActionMap;
import javax.swing.BorderFactory;
import javax.swing.Icon;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JComponent;
import javax.swing.JDialog;
import javax.swing.JEditorPane;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.KeyStroke;
import javax.swing.LookAndFeel;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.plaf.ActionMapUIResource;
import javax.swing.plaf.ComponentUI;
import javax.swing.plaf.basic.BasicHTML;
import javax.swing.text.html.HTMLDocument;

import org.jdesktop.swingx.JXTipOfTheDay;
import org.jdesktop.swingx.SwingXUtilities;
import org.jdesktop.swingx.JXTipOfTheDay.ShowOnStartupChoice;
import org.jdesktop.swingx.plaf.TipOfTheDayUI;
import org.jdesktop.swingx.plaf.UIManagerExt;
import org.jdesktop.swingx.tips.TipOfTheDayModel.Tip;

/**
 * Base implementation of the <code>JXTipOfTheDay</code> UI.
 * 
 * @author <a href="mailto:fred@L2FProd.com">Frederic Lavigne</a>
 */
public class BasicTipOfTheDayUI extends TipOfTheDayUI {

  public static ComponentUI createUI(JComponent c) {
    return new BasicTipOfTheDayUI((JXTipOfTheDay)c);
  }

  protected JXTipOfTheDay tipPane;
  protected JPanel tipArea;
  protected Component currentTipComponent;

  protected Font tipFont;
  protected PropertyChangeListener changeListener;

  public BasicTipOfTheDayUI(JXTipOfTheDay tipPane) {
    this.tipPane = tipPane;
  }

  @Override
  public JDialog createDialog(Component parentComponent,
    final ShowOnStartupChoice choice) {
    return createDialog(parentComponent, choice, true);
  }
  
  protected JDialog createDialog(Component parentComponent,
    final ShowOnStartupChoice choice,
    boolean showPreviousButton) {
    Locale locale = parentComponent==null ? null : parentComponent.getLocale();
    String title = UIManagerExt.getString("TipOfTheDay.dialogTitle", locale);

    final JDialog dialog;

    Window window;
    if (parentComponent == null) {
      window = JOptionPane.getRootFrame();
    } else {
      window = (parentComponent instanceof Window)?(Window)parentComponent
        :SwingUtilities.getWindowAncestor(parentComponent);
    }

    if (window instanceof Frame) {
      dialog = new JDialog((Frame)window, title, true);
    } else {
      dialog = new JDialog((Dialog)window, title, true);
    }

    dialog.getContentPane().setLayout(new BorderLayout(10, 10));
    dialog.getContentPane().add(tipPane, BorderLayout.CENTER);
    ((JComponent)dialog.getContentPane()).setBorder(BorderFactory
      .createEmptyBorder(10, 10, 10, 10));

    final JCheckBox showOnStartupBox;

    // tip controls
    JPanel controls = new JPanel(new BorderLayout());
    dialog.add("South", controls);

    if (choice != null) {
      showOnStartupBox = new JCheckBox(UIManagerExt
        .getString("TipOfTheDay.showOnStartupText", locale), choice
        .isShowingOnStartup());
      controls.add(showOnStartupBox, BorderLayout.CENTER);
    } else {
      showOnStartupBox = null;
    }

    JPanel buttons =
      new JPanel(new GridLayout(1, showPreviousButton?3:2, 9, 0));
    controls.add(buttons, BorderLayout.LINE_END);
    
    if (showPreviousButton) {
      JButton previousTipButton = new JButton(UIManagerExt
        .getString("TipOfTheDay.previousTipText", locale));
      buttons.add(previousTipButton);
      previousTipButton.addActionListener(getActionMap().get("previousTip"));
    }
    
    JButton nextTipButton = new JButton(UIManagerExt
      .getString("TipOfTheDay.nextTipText", locale));
    buttons.add(nextTipButton);
    nextTipButton.addActionListener(getActionMap().get("nextTip"));
    
    JButton closeButton = new JButton(UIManagerExt
      .getString("TipOfTheDay.closeText", locale));
    buttons.add(closeButton);
    
    final ActionListener saveChoice = new ActionListener() {
      @Override
    public void actionPerformed(ActionEvent e) {
        if (choice != null) {
          choice.setShowingOnStartup(showOnStartupBox.isSelected());
        }
        dialog.setVisible(false);
      }
    };

    closeButton.addActionListener(new ActionListener() {
      @Override
    public void actionPerformed(ActionEvent e) {        
        dialog.setVisible(false);
        saveChoice.actionPerformed(null);
      }
    });
    dialog.getRootPane().setDefaultButton(closeButton);
    
    dialog.addWindowListener(new WindowAdapter() {
      @Override
    public void windowClosing(WindowEvent e) {
        saveChoice.actionPerformed(null);
      }
    });
    
    ((JComponent)dialog.getContentPane()).registerKeyboardAction(saveChoice,
      KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0),
      JComponent.WHEN_IN_FOCUSED_WINDOW);
    
    dialog.pack();
    dialog.setLocationRelativeTo(parentComponent);

    return dialog;
  }

  @Override
  public void installUI(JComponent c) {
    super.installUI(c);
    installDefaults();
    installKeyboardActions();
    installComponents();
    installListeners();

    showCurrentTip();
  }

  protected void installKeyboardActions() {
    ActionMap map = getActionMap();
    if (map != null) {
      SwingUtilities.replaceUIActionMap(tipPane, map);
    }
  }

  ActionMap getActionMap() {
    ActionMap map = new ActionMapUIResource();
    map.put("previousTip", new PreviousTipAction());
    map.put("nextTip", new NextTipAction());
    return map;
  }

  protected void installListeners() {
    changeListener = createChangeListener();
    tipPane.addPropertyChangeListener(changeListener);
  }

  protected PropertyChangeListener createChangeListener() {
    return new ChangeListener();
  }

  protected void installDefaults() {
    LookAndFeel.installColorsAndFont(tipPane, "TipOfTheDay.background",
      "TipOfTheDay.foreground", "TipOfTheDay.font");
    LookAndFeel.installBorder(tipPane, "TipOfTheDay.border");
    LookAndFeel.installProperty(tipPane, "opaque", Boolean.TRUE);
    tipFont = UIManager.getFont("TipOfTheDay.tipFont");
  }

  protected void installComponents() {
    tipPane.setLayout(new BorderLayout());

    // tip icon
    JLabel tipIcon = new JLabel(UIManagerExt
      .getString("TipOfTheDay.didYouKnowText", tipPane.getLocale()));
    tipIcon.setIcon(UIManager.getIcon("TipOfTheDay.icon"));
    tipIcon.setBorder(BorderFactory.createEmptyBorder(22, 15, 22, 15));
    tipPane.add("North", tipIcon);

    // tip area
    tipArea = new JPanel(new BorderLayout(2, 2));
    tipArea.setOpaque(false);
    tipArea.setBorder(BorderFactory.createEmptyBorder(4, 4, 4, 4));
    tipPane.add("Center", tipArea);
  }

  @Override
  public Dimension getPreferredSize(JComponent c) {
    return new Dimension(420, 175);
  }

  protected void showCurrentTip() {
    if (currentTipComponent != null) {
      tipArea.remove(currentTipComponent);
    }

    int currentTip = tipPane.getCurrentTip();
    if (currentTip == -1) {
      JLabel label = new JLabel();
      label.setOpaque(true);
      label.setBackground(UIManager.getColor("TextArea.background"));
      currentTipComponent = label;
      tipArea.add("Center", currentTipComponent);
      return;
    }

    // tip does not fall in current tip range
    if (tipPane.getModel() == null || tipPane.getModel().getTipCount() == 0
      || (currentTip < 0 && currentTip >= tipPane.getModel().getTipCount())) {
      currentTipComponent = new JLabel();
    } else {    
      Tip tip = tipPane.getModel().getTipAt(currentTip);

      Object tipObject = tip.getTip();
      if (tipObject instanceof Component) {
        currentTipComponent = (Component)tipObject;
      } else if (tipObject instanceof Icon) {
        currentTipComponent = new JLabel((Icon)tipObject);
      } else {
        JScrollPane tipScroll = new JScrollPane();
        tipScroll.setBorder(null);
        tipScroll.setOpaque(false);
        tipScroll.getViewport().setOpaque(false);
        tipScroll.setBorder(null);

        String text = tipObject == null?"":tipObject.toString();

        if (BasicHTML.isHTMLString(text)) {
          JEditorPane editor = new JEditorPane("text/html", text);
          editor.setFont(tipPane.getFont());
//          BasicHTML.updateRenderer(editor, text);
          SwingXUtilities.setHtmlFont(
                  (HTMLDocument) editor.getDocument(), tipPane.getFont());
          editor.setEditable(false);
          editor.setBorder(null);
          editor.setMargin(null);
          editor.setOpaque(false);
          tipScroll.getViewport().setView(editor);
        } else {
          JTextArea area = new JTextArea(text);
          area.setFont(tipPane.getFont());
          area.setEditable(false);
          area.setLineWrap(true);
          area.setWrapStyleWord(true);
          area.setBorder(null);
          area.setMargin(null);
          area.setOpaque(false);
          tipScroll.getViewport().setView(area);
        }

        currentTipComponent = tipScroll;
      }
    }
    
    tipArea.add("Center", currentTipComponent);
    tipArea.revalidate();
    tipArea.repaint();
  }

  @Override
  public void uninstallUI(JComponent c) {
    uninstallListeners();
    uninstallComponents();
    uninstallDefaults();
    super.uninstallUI(c);
  }

  protected void uninstallListeners() {
    tipPane.removePropertyChangeListener(changeListener);
  }

  protected void uninstallComponents() {}

  protected void uninstallDefaults() {}

  class ChangeListener implements PropertyChangeListener {
    @Override
    public void propertyChange(PropertyChangeEvent evt) {
      if (JXTipOfTheDay.CURRENT_TIP_CHANGED_KEY.equals(evt.getPropertyName())) {
        showCurrentTip();
      }
    }
  }

  class PreviousTipAction extends AbstractAction {
    public PreviousTipAction() {
      super("previousTip");
    }
    @Override
    public void actionPerformed(ActionEvent e) {
      tipPane.previousTip();
    }
    @Override
    public boolean isEnabled() {
      return tipPane.isEnabled();
    }
  }

  class NextTipAction extends AbstractAction {
    public NextTipAction() {
      super("nextTip");
    }
    @Override
    public void actionPerformed(ActionEvent e) {
      tipPane.nextTip();
    }
    @Override
    public boolean isEnabled() {
      return tipPane.isEnabled();
    }
  }
  
}
