source: src/main/java/weka/gui/visualize/PrintableComponent.java @ 24

Last change on this file since 24 was 4, checked in by gnappo, 14 years ago

Import di weka.

File size: 19.8 KB
Line 
1 /*
2  *    This program is free software; you can redistribute it and/or modify
3  *    it under the terms of the GNU General Public License as published by
4  *    the Free Software Foundation; either version 2 of the License, or
5  *    (at your option) any later version.
6  *
7  *    This program is distributed in the hope that it will be useful,
8  *    but WITHOUT ANY WARRANTY; without even the implied warranty of
9  *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
10  *    GNU General Public License for more details.
11  *
12  *    You should have received a copy of the GNU General Public License
13  *    along with this program; if not, write to the Free Software
14  *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
15  */
16
17 /*
18  *    PrintableComponent.java
19  *    Copyright (C) 2005 University of Waikato, Hamilton, New Zealand
20  *
21  */
22
23package weka.gui.visualize;
24
25import weka.gui.ExtensionFileFilter;
26import weka.gui.GenericObjectEditor;
27
28import java.awt.Dimension;
29import java.awt.event.ItemEvent;
30import java.awt.event.ItemListener;
31import java.awt.event.MouseAdapter;
32import java.awt.event.MouseEvent;
33import java.io.BufferedOutputStream;
34import java.io.File;
35import java.io.FileOutputStream;
36import java.util.Collections;
37import java.util.Enumeration;
38import java.util.Hashtable;
39import java.util.Properties;
40import java.util.Vector;
41
42import javax.swing.JCheckBox;
43import javax.swing.JComponent;
44import javax.swing.JFileChooser;
45import javax.swing.JLabel;
46import javax.swing.JOptionPane;
47import javax.swing.JPanel;
48import javax.swing.JTextField;
49import javax.swing.event.DocumentEvent;
50import javax.swing.event.DocumentListener;
51
52/**
53 * This class extends the component which is handed over in the constructor
54 * by a print dialog.
55 * The Print dialog is accessible via Alt+Shift+LeftMouseClick. <p>
56 * The individual JComponentWriter-descendants can be accessed by the
57 * <code>getWriter(String)</code> method, if the parameters need to be changed.
58 *
59 * @see #getWriters()
60 * @see #getWriter(String)
61 * @author FracPete (fracpete at waikato dot ac dot nz)
62 * @version $Revision: 1.8 $
63 */
64public class PrintableComponent
65  implements PrintableHandler {
66 
67  /** the parent component of this print dialog. */
68  protected JComponent m_Component;
69 
70  /** the filechooser for saving the panel. */
71  protected static JFileChooser m_FileChooserPanel;
72
73  /** the checkbox for the custom dimensions. */
74  protected static JCheckBox m_CustomDimensionsCheckBox;
75 
76  /** the edit field for the custom width. */
77  protected static JTextField m_CustomWidthText;
78 
79  /** the edit field for the custom height. */
80  protected static JTextField m_CustomHeightText;
81
82  /** the checkbox for keeping the aspect ration. */
83  protected static JCheckBox m_AspectRatioCheckBox;
84 
85  /** the title of the save dialog. */
86  protected String m_SaveDialogTitle = "Save as...";
87 
88  /** the x scale factor. */
89  protected double m_xScale = 1.0;
90 
91  /** the y scale factor. */
92  protected double m_yScale = 1.0;
93
94  /** the aspect ratio. */
95  protected double m_AspectRatio;
96
97  /** whether to ignore the update of the text field (in case of "keep ratio"). */
98  protected boolean m_IgnoreChange;
99 
100  /** whether to print some debug information. */
101  private static final boolean DEBUG = false;
102 
103  /** whether the user was already asked about the tooltip behavior. */
104  protected static boolean m_ToolTipUserAsked = false;
105
106  /** the property name for showing the tooltip. */
107  protected final static String PROPERTY_SHOW = "PrintableComponentToolTipShow";
108
109  /** the property name whether the user was already asked. */
110  protected final static String PROPERTY_USERASKED = "PrintableComponentToolTipUserAsked";
111 
112  /** whether to display the tooltip or not. */
113  protected static boolean m_ShowToolTip = true;
114  static {
115    try {
116      m_ShowToolTip = Boolean.valueOf(
117          VisualizeUtils.VISUALIZE_PROPERTIES.getProperty(
118            PROPERTY_SHOW, 
119            "true")).booleanValue();
120      m_ToolTipUserAsked = Boolean.valueOf(
121          VisualizeUtils.VISUALIZE_PROPERTIES.getProperty(
122            PROPERTY_USERASKED, 
123            "false")).booleanValue();
124    }
125    catch (Exception e) {
126      // ignore exception
127      m_ToolTipUserAsked = false;
128      m_ShowToolTip      = true;
129    }
130  }
131 
132  /** output if we're in debug mode */
133  static {
134    if (DEBUG)
135      System.err.println(PrintablePanel.class.getName() + ": DEBUG ON");
136  }
137 
138  /**
139   * initializes the panel.
140   *
141   * @param component     the component to enhance with printing functionality
142   */
143  public PrintableComponent(JComponent component) {
144    super();
145   
146    m_Component   = component;
147    m_AspectRatio = Double.NaN;
148   
149    getComponent().addMouseListener(new PrintMouseListener(this));
150    getComponent().setToolTipText(getToolTipText(this));
151    initFileChooser();
152  }
153 
154  /**
155   * returns the GUI component this print dialog is part of.
156   *
157   * @return            the GUI component
158   */
159  public JComponent getComponent() {
160    return m_Component;
161  }
162
163  /**
164   * Returns a tooltip only if the user wants it. If retrieved for the first,
165   * a dialog pops up and asks the user whether the tooltip should always
166   * appear or not. The weka/gui/visualize/Visualize.props is then written
167   * in the user's home directory.
168   *
169   * @param component the PrintableComponent to ask for
170   * @return null if the user doesn't want the tooltip, otherwise the text
171   */
172  public static String getToolTipText(PrintableComponent component) {
173    String        result;
174    int           retVal;
175    Properties    props;
176    String        name;
177    Enumeration   names;
178    String        filename;
179
180    // ToolTip is disabled for the moment...
181    if (true)
182      return null;
183
184    // ask user whether the tooltip should be shown
185    if (!m_ToolTipUserAsked) {
186      m_ToolTipUserAsked = true;
187     
188      retVal = JOptionPane.showConfirmDialog(
189          component.getComponent(),
190            "Some panels enable the user to save the content as JPEG or EPS.\n"
191          + "In order to see which panels support this, a tooltip can be "
192          + "displayed. Enable tooltip?",
193          "ToolTip for Panels...",
194          JOptionPane.YES_NO_OPTION);
195
196      m_ShowToolTip = (retVal == JOptionPane.YES_OPTION);
197
198      // save props file
199      VisualizeUtils.VISUALIZE_PROPERTIES.setProperty(
200               PROPERTY_SHOW, "" + m_ShowToolTip);
201      VisualizeUtils.VISUALIZE_PROPERTIES.setProperty(
202               PROPERTY_USERASKED, "" + m_ToolTipUserAsked);
203      try {
204        // NOTE: properties that got inherited from another props file don't
205        //       get saved. I.e., one could overwrite the existing props
206        //       file with an (nearly) empty one.
207        //       => transfer all properties into a new one
208        props = new Properties();
209        names = VisualizeUtils.VISUALIZE_PROPERTIES.propertyNames();
210        while (names.hasMoreElements()) {
211          name = names.nextElement().toString();
212          props.setProperty(
213              name, 
214              VisualizeUtils.VISUALIZE_PROPERTIES.getProperty(name, ""));
215        }
216        filename = System.getProperty("user.home") + "/Visualize.props";
217        props.store(
218            new BufferedOutputStream(new FileOutputStream(filename)), null);
219
220        // inform user about location of props file and name of property
221        JOptionPane.showMessageDialog(
222            component.getComponent(), 
223            "You can still manually enable or disable the ToolTip via the following property\n"
224            + "    " + PROPERTY_SHOW + "\n"
225            + "in the following file\n"
226            + "    " + filename);
227      }
228      catch (Exception e) {
229        JOptionPane.showMessageDialog(
230            component.getComponent(), 
231              "Error saving the props file!\n"
232            + e.getMessage() + "\n\n"
233            + "Note:\n"
234            + "If you want to disable these messages from popping up, place a file\n"
235            + "called 'Visualize.props' either in your home directory or in the directory\n"
236            + "you're starting Weka from and add the following lines:\n"
237            + "    " + PROPERTY_USERASKED + "=true\n"
238            + "    " + PROPERTY_SHOW + "=" + m_ShowToolTip,
239            "Error...",
240            JOptionPane.ERROR_MESSAGE);
241      }
242    }
243   
244    if (m_ShowToolTip)
245      result = "Click left mouse button while holding <alt> and <shift> to display a save dialog.";
246    else
247      result = null;
248
249    return result;
250  }
251 
252  /**
253   * initializes the filechooser, i.e. locates all the available writers in
254   * the current package
255   */
256  protected void initFileChooser() {
257    Vector              writerNames;
258    int                 i;
259    Class               cls;
260    JComponentWriter    writer;
261    JPanel              accessory;
262    JLabel              label;
263
264    // already initialized?
265    if (m_FileChooserPanel != null)
266      return;
267
268    m_FileChooserPanel = new JFileChooser();
269    m_FileChooserPanel.resetChoosableFileFilters();
270    m_FileChooserPanel.setAcceptAllFileFilterUsed(false);
271
272    // setup the accessory
273    accessory = new JPanel();
274    accessory.setLayout(null);
275    accessory.setPreferredSize(new Dimension(200, 200));
276    accessory.revalidate();
277    m_FileChooserPanel.setAccessory(accessory);
278 
279    m_CustomDimensionsCheckBox = new JCheckBox("Use custom dimensions");
280    m_CustomDimensionsCheckBox.setBounds(14, 7, 200, 21);
281    m_CustomDimensionsCheckBox.addItemListener(new ItemListener() {
282      public void itemStateChanged(ItemEvent e) {
283        boolean custom = m_CustomDimensionsCheckBox.isSelected();
284        m_CustomWidthText.setEnabled(custom);
285        m_CustomHeightText.setEnabled(custom);
286        m_AspectRatioCheckBox.setEnabled(custom);
287        if (custom) {
288          m_IgnoreChange = true;
289          m_CustomWidthText.setText("" + m_Component.getWidth());
290          m_CustomHeightText.setText("" + m_Component.getHeight());
291          m_IgnoreChange = false;
292        }
293        else {
294          m_IgnoreChange = true;
295          m_CustomWidthText.setText("-1");
296          m_CustomHeightText.setText("-1");
297          m_IgnoreChange = false;
298        }
299      }
300    });
301    accessory.add(m_CustomDimensionsCheckBox);
302   
303    m_CustomWidthText = new JTextField(5);
304    m_CustomWidthText.setText("-1");
305    m_CustomWidthText.setEnabled(false);
306    m_CustomWidthText.setBounds(65, 35, 50, 21);
307    m_CustomWidthText.getDocument().addDocumentListener(new DocumentListener() {
308      public void changedUpdate(DocumentEvent e) {
309        updateDimensions(m_CustomWidthText);
310      }
311     
312      public void insertUpdate(DocumentEvent e) {
313        updateDimensions(m_CustomWidthText);
314      }
315     
316      public void removeUpdate(DocumentEvent e) {
317        updateDimensions(m_CustomWidthText);
318      }
319    });
320    label = new JLabel("Width");
321    label.setLabelFor(m_CustomWidthText);
322    label.setDisplayedMnemonic('W');
323    label.setBounds(14, 35, 50, 21);
324    accessory.add(label);
325    accessory.add(m_CustomWidthText);
326   
327    m_CustomHeightText = new JTextField(5);
328    m_CustomHeightText.setText("-1");
329    m_CustomHeightText.setEnabled(false);
330    m_CustomHeightText.setBounds(65, 63, 50, 21);
331    m_CustomHeightText.getDocument().addDocumentListener(new DocumentListener() {
332      public void changedUpdate(DocumentEvent e) {
333        updateDimensions(m_CustomHeightText);
334      }
335     
336      public void insertUpdate(DocumentEvent e) {
337        updateDimensions(m_CustomHeightText);
338      }
339     
340      public void removeUpdate(DocumentEvent e) {
341        updateDimensions(m_CustomHeightText);
342      }
343    });
344    label = new JLabel("Height");
345    label.setLabelFor(m_CustomHeightText);
346    label.setDisplayedMnemonic('H');
347    label.setBounds(14, 63, 50, 21);
348    accessory.add(label);
349    accessory.add(m_CustomHeightText);
350   
351    m_AspectRatioCheckBox = new JCheckBox("Keep aspect ratio");
352    m_AspectRatioCheckBox.setBounds(14, 91, 200, 21);
353    m_AspectRatioCheckBox.setEnabled(false);
354    m_AspectRatioCheckBox.setSelected(true);
355    m_AspectRatioCheckBox.addItemListener(new ItemListener() {
356      public void itemStateChanged(ItemEvent e) {
357        boolean keep = m_AspectRatioCheckBox.isSelected();
358        if (keep) {
359          m_IgnoreChange = true;
360          m_CustomWidthText.setText("" + m_Component.getWidth());
361          m_CustomHeightText.setText("" + m_Component.getHeight());
362          m_IgnoreChange = false;
363        }
364      }
365    });
366    accessory.add(m_AspectRatioCheckBox);
367   
368    // determine all available writers and add them to the filechooser
369    writerNames = GenericObjectEditor.getClassnames(JComponentWriter.class.getName());
370    Collections.sort(writerNames);
371    for (i = 0; i < writerNames.size(); i++) {
372      try {
373        cls    = Class.forName(writerNames.get(i).toString());
374        writer = (JComponentWriter) cls.newInstance();
375        m_FileChooserPanel.addChoosableFileFilter(
376            new JComponentWriterFileFilter(
377                writer.getExtension(), 
378                writer.getDescription() + " (*" + writer.getExtension() + ")", 
379                writer));
380      }
381      catch (Exception e) {
382        System.err.println(writerNames.get(i) + ": " + e);
383      }
384    }
385   
386    // set first filter as active filter
387    if (m_FileChooserPanel.getChoosableFileFilters().length > 0)
388      m_FileChooserPanel.setFileFilter(m_FileChooserPanel.getChoosableFileFilters()[0]);
389  }
390 
391  /**
392   * updates the dimensions if necessary (i.e., if aspect ratio is to be kept).
393   *
394   * @param sender      the JTextField which send the notification to update
395   */
396  protected void updateDimensions(JTextField sender) {
397    int         newValue;
398    int         baseValue;
399   
400    // some sanity checks
401    if (!m_AspectRatioCheckBox.isSelected() || m_IgnoreChange)
402      return;
403    if (!(sender instanceof JTextField) || (sender == null))
404      return;
405    if (sender.getText().length() == 0)
406      return;
407   
408    // is it a valid integer, greater than 0?
409    try {
410      baseValue = Integer.parseInt(sender.getText());
411      newValue  = 0;
412      if (baseValue <= 0)
413        return;
414
415      if (Double.isNaN(m_AspectRatio)) {
416        m_AspectRatio = (double) getComponent().getWidth() / 
417        (double) getComponent().getHeight();
418      }
419    }
420    catch (Exception e) {
421      // we can't parse the string!
422      return;
423    }
424
425    // computer and update
426    m_IgnoreChange = true;
427    if (sender == m_CustomWidthText) {
428      newValue = (int) (((double) baseValue) * (1/m_AspectRatio));
429      m_CustomHeightText.setText("" + newValue);
430    }
431    else if (sender == m_CustomHeightText) {
432      newValue = (int) (((double) baseValue) * m_AspectRatio);
433      m_CustomWidthText.setText("" + newValue);
434    }
435    m_IgnoreChange = false;
436  }
437 
438  /**
439   * returns a Hashtable with the current available JComponentWriters in the
440   * save dialog. the key of the Hashtable is the description of the writer.
441   *
442   * @return all currently available JComponentWriters
443   * @see JComponentWriter#getDescription()
444   */
445  public Hashtable getWriters() {
446    Hashtable         result;
447    int               i;
448    JComponentWriter  writer;
449   
450    result = new Hashtable();
451   
452    for (i = 0; i < m_FileChooserPanel.getChoosableFileFilters().length; i++) {
453      writer = ((JComponentWriterFileFilter) m_FileChooserPanel.getChoosableFileFilters()[i]).getWriter();
454      result.put(writer.getDescription(), writer);
455    }
456   
457    return result;
458  }
459 
460  /**
461   * returns the JComponentWriter associated with the given name, is
462   * <code>null</code> if not found.
463   *
464   * @param name the name of the writer
465   * @return the writer associated with the given name
466   * @see JComponentWriter#getDescription()
467   */
468  public JComponentWriter getWriter(String name) {
469    return (JComponentWriter) getWriters().get(name);
470  }
471
472  /**
473   * sets the title for the save dialog.
474   *
475   * @param title the title of the save dialog
476   */
477  public void setSaveDialogTitle(String title) {
478    m_SaveDialogTitle = title;
479  }
480 
481  /**
482   * returns the title for the save dialog.
483   *
484   * @return the title of the save dialog
485   */
486  public String getSaveDialogTitle() {
487    return m_SaveDialogTitle;
488  }
489 
490  /**
491   * sets the scale factor.
492   *
493   * @param x the scale factor for the x-axis
494   * @param y the scale factor for the y-axis
495   */
496  public void setScale(double x, double y) {
497    m_xScale = x;
498    m_yScale = y;
499    if (DEBUG)
500      System.err.println("x = " + x + ", y = " + y);
501  }
502 
503  /**
504   * returns the scale factor for the x-axis.
505   *
506   * @return the scale factor
507   */
508  public double getXScale() {
509    return m_xScale;
510  }
511 
512  /**
513   * returns the scale factor for the y-axis.
514   *
515   * @return the scale factor
516   */
517  public double getYScale() {
518    return m_xScale;
519  }
520 
521  /**
522   * displays a save dialog for saving the panel to a file. 
523   * Fixes a bug with the Swing JFileChooser: if you entered a new
524   * filename in the save dialog and press Enter the <code>getSelectedFile</code>
525   * method returns <code>null</code> instead of the filename.<br>
526   * To solve this annoying behavior we call the save dialog once again s.t. the
527   * filename is set. Might look a little bit strange to the user, but no
528   * NullPointerException! ;-)
529   */
530  public void saveComponent() {
531    int                           result;
532    JComponentWriter              writer;
533    File                          file;
534    JComponentWriterFileFilter    filter;
535   
536    // display save dialog
537    m_FileChooserPanel.setDialogTitle(getSaveDialogTitle());
538    do {
539      result = m_FileChooserPanel.showSaveDialog(getComponent());
540      if (result != JFileChooser.APPROVE_OPTION)
541        return;
542    }
543    while (m_FileChooserPanel.getSelectedFile() == null);
544   
545    // save the file
546    try {
547      filter = (JComponentWriterFileFilter) m_FileChooserPanel.getFileFilter();
548      file   = m_FileChooserPanel.getSelectedFile();
549      writer = filter.getWriter();
550      if (!file.getAbsolutePath().toLowerCase().endsWith(writer.getExtension().toLowerCase()))
551        file = new File(file.getAbsolutePath() + writer.getExtension()); 
552      writer.setComponent(getComponent());
553      writer.setFile(file);
554      writer.setScale(getXScale(), getYScale());
555      writer.setUseCustomDimensions(m_CustomDimensionsCheckBox.isSelected());
556      if (m_CustomDimensionsCheckBox.isSelected()) {
557        writer.setCustomWidth(Integer.parseInt(m_CustomWidthText.getText()));
558        writer.setCustomHeight(Integer.parseInt(m_CustomHeightText.getText()));
559      }
560      else {
561        writer.setCustomWidth(-1);
562        writer.setCustomHeight(-1);
563      }
564      writer.toOutput();
565    }
566    catch (Exception e) {
567      e.printStackTrace();
568    }
569  }
570 
571  /**
572   * a specialized filter that also contains the associated filter class.
573   */
574  protected class JComponentWriterFileFilter extends ExtensionFileFilter {
575    /** the associated writer. */
576    private JComponentWriter m_Writer; 
577   
578    /**
579     * Creates the ExtensionFileFilter.
580     *
581     * @param extension       the extension of accepted files.
582     * @param description     a text description of accepted files.
583     * @param writer          the associated writer
584     */
585    public JComponentWriterFileFilter(String extension, String description, JComponentWriter writer) {
586      super(extension, description);
587      m_Writer = writer;
588    }
589   
590    /**
591     * returns the associated writer.
592     *
593     * @return          the writer
594     */
595    public JComponentWriter getWriter() {
596      return m_Writer;
597    }
598  }
599
600  /**
601   * The listener to wait for Ctrl-Shft-Left Mouse Click.
602   */
603  private class PrintMouseListener extends MouseAdapter {
604    /** the listener's component. */
605    private PrintableComponent m_Component;
606   
607    /**
608     * initializes the listener.
609     *
610     * @param component the component for which to create the listener
611     */
612    public PrintMouseListener(PrintableComponent component){
613      m_Component = component;
614    }
615   
616    /**
617     * Invoked when the mouse has been clicked on a component.
618     *
619     * @param e the event
620     */
621    public void mouseClicked(MouseEvent e) {
622      int modifiers = e.getModifiers();
623      if (((modifiers & MouseEvent.SHIFT_MASK) == MouseEvent.SHIFT_MASK) && 
624          ((modifiers & MouseEvent.ALT_MASK) == MouseEvent.ALT_MASK) &&
625          ((modifiers & MouseEvent.BUTTON1_MASK) == MouseEvent.BUTTON1_MASK)) {
626        e.consume();
627        m_Component.saveComponent();
628      }
629    }
630  }
631}
Note: See TracBrowser for help on using the repository browser.