001/*
002 * CDDL HEADER START
003 *
004 * The contents of this file are subject to the terms of the
005 * Common Development and Distribution License, Version 1.0 only
006 * (the "License").  You may not use this file except in compliance
007 * with the License.
008 *
009 * You can obtain a copy of the license at legal-notices/CDDLv1_0.txt
010 * or http://forgerock.org/license/CDDLv1.0.html.
011 * See the License for the specific language governing permissions
012 * and limitations under the License.
013 *
014 * When distributing Covered Code, include this CDDL HEADER in each
015 * file and include the License file at legal-notices/CDDLv1_0.txt.
016 * If applicable, add the following below this CDDL HEADER, with the
017 * fields enclosed by brackets "[]" replaced with your own identifying
018 * information:
019 *      Portions Copyright [yyyy] [name of copyright owner]
020 *
021 * CDDL HEADER END
022 *
023 *
024 *      Copyright 2008-2010 Sun Microsystems, Inc.
025 *      Portions Copyright 2015 ForgeRock AS
026 */
027package org.opends.guitools.controlpanel.ui;
028
029import static org.opends.messages.AdminToolMessages.*;
030import static org.opends.messages.QuickSetupMessages.INFO_CLOSE_BUTTON_LABEL;
031
032import java.awt.Component;
033import java.awt.Dimension;
034import java.awt.GridBagConstraints;
035import java.awt.GridBagLayout;
036import java.awt.Insets;
037import java.awt.Window;
038import java.awt.event.ActionEvent;
039import java.awt.event.ActionListener;
040
041import javax.swing.BorderFactory;
042import javax.swing.Box;
043import javax.swing.JButton;
044import javax.swing.JCheckBox;
045import javax.swing.JEditorPane;
046import javax.swing.JFrame;
047import javax.swing.JPanel;
048import javax.swing.JProgressBar;
049import javax.swing.JScrollPane;
050import javax.swing.SwingUtilities;
051import javax.swing.text.html.HTMLDocument;
052
053import org.opends.guitools.controlpanel.datamodel.ControlPanelInfo;
054import org.opends.guitools.controlpanel.event.ConfigurationChangeEvent;
055import org.opends.guitools.controlpanel.event.PrintStreamListener;
056import org.opends.guitools.controlpanel.ui.components.BasicExpander;
057import org.opends.guitools.controlpanel.util.ApplicationPrintStream;
058import org.opends.guitools.controlpanel.util.Utilities;
059import org.forgerock.i18n.LocalizableMessage;
060
061/**
062 * The dialog that is used to display progress in a task.
063 */
064public class ProgressDialog extends GenericDialog
065{
066  private static final long serialVersionUID = -6462866257463062629L;
067  private ProgressPanel progressPanel;
068
069  /**
070   * Constructor of the dialog.
071   * @param parentFrame the parent frame.
072   * @param relativeTo the component to use as reference to set the position
073   * of this dialog.
074   * @param title the title of the dialog.
075   * @param info the control panel information.
076   */
077  public ProgressDialog(JFrame parentFrame, Component relativeTo,
078      LocalizableMessage title, ControlPanelInfo info)
079  {
080    super(parentFrame, getPanel(info));
081    Utilities.centerGoldenMean(this, relativeTo);
082    setTitle(title.toString());
083    progressPanel = (ProgressPanel)panel;
084    getRootPane().setDefaultButton(progressPanel.closeButton);
085  }
086
087  /**
088   * Creates the panel that will be contained in the dialog.
089   * @param info the control panel information.
090   * @return the panel that will be contained in the dialog.
091   */
092  private static StatusGenericPanel getPanel(ControlPanelInfo info)
093  {
094    ProgressPanel panel = new ProgressPanel();
095    panel.setInfo(info);
096    return panel;
097  }
098
099  /**
100   * Adds two print stream listeners.
101   * @param outPrintStream the output stream listener.
102   * @param errorPrintStream the error stream listener.
103   */
104  public void addPrintStreamListeners(ApplicationPrintStream outPrintStream,
105      ApplicationPrintStream errorPrintStream)
106  {
107    errorPrintStream.addListener(new PrintStreamListener()
108    {
109      public void newLine(final String msg)
110      {
111        SwingUtilities.invokeLater(new Runnable()
112        {
113          /** {@inheritDoc} */
114          public void run()
115          {
116            progressPanel.appendErrorLine(msg);
117          }
118        });
119      }
120    });
121    outPrintStream.addListener(new PrintStreamListener()
122    {
123      public void newLine(final String msg)
124      {
125        /** {@inheritDoc} */
126        SwingUtilities.invokeLater(new Runnable()
127        {
128          public void run()
129          {
130            progressPanel.appendOutputLine(msg);
131          }
132        });
133      }
134    });
135  }
136
137  /**
138   * Returns the progress bar of the dialog.
139   * @return the progress bar of the dialog.
140   */
141  public JProgressBar getProgressBar()
142  {
143    return progressPanel.getProgressBar();
144  }
145
146  /**
147   * Appends some text in HTML format to the 'Details' section of the dialog.
148   * @param text the text in HTML format to be appended.
149   */
150  public void appendProgressHtml(String text)
151  {
152    progressPanel.appendHtml(text);
153  }
154
155  /**
156   * Resets the contents of the 'Details' section of the dialog.
157   *
158   */
159  public void resetProgressLogs()
160  {
161    progressPanel.resetLogs();
162  }
163
164  /**
165   * Sets the text to be displayed in the summary area of the progress
166   * dialog.
167   * @param text the text to be displayed.
168   */
169  public void setSummary(LocalizableMessage text)
170  {
171    progressPanel.setSummary(text);
172  }
173
174  /** {@inheritDoc} */
175  public void setEnabledClose(boolean enable)
176  {
177    progressPanel.closeButton.setEnabled(enable);
178  }
179
180  /**
181   * Note: this will make the dialog to be closed asynchronously.  So that
182   * sequential calls to setTaskIsOver(true) and setTaskIsOver(false) on the
183   * event thread are guaranteed not to close the dialog.
184   * @param taskIsOver whether the task is finished or not.
185   */
186  public void setTaskIsOver(boolean taskIsOver)
187  {
188    progressPanel.taskIsOver = taskIsOver;
189    progressPanel.closeWhenOverClicked();
190  }
191
192  /**
193   * The panel contained in the progress dialog.
194   *
195   */
196  static class ProgressPanel extends StatusGenericPanel
197  {
198    private static final long serialVersionUID = -364496083928260306L;
199    private BasicExpander details;
200    private JEditorPane logs;
201    private JScrollPane scroll;
202    private JCheckBox closeWhenOver;
203    private final String LASTID = "lastid";
204    private final String INIT_TEXT = "<span id=\""+LASTID+
205    "\" style=\"bold\">&nbsp;</span>";
206    private JProgressBar progressBar;
207    private Component extraStrut;
208    private JButton closeButton;
209    private static final String FAKE_PROGRESS_TEXT =
210      "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"+
211      "<br><br><br><br><br><br><br><br><br><br><br><br><br><br><br>"+
212      "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
213    private int heightDiff;
214    private int lastCollapsedHeight = -1;
215    private int lastExpandedHeight = -1;
216
217    private static boolean lastShowDetails;
218    private static boolean lastCloseWhenOver;
219
220    private boolean taskIsOver;
221
222    /**
223     * Default constructor.
224     *
225     */
226    public ProgressPanel()
227    {
228      super();
229      createLayout();
230    }
231
232    /** {@inheritDoc} */
233    public LocalizableMessage getTitle()
234    {
235      return null;
236    }
237
238    /** {@inheritDoc} */
239    public boolean requiresScroll()
240    {
241      return false;
242    }
243
244    /** {@inheritDoc} */
245    public boolean requiresBorder()
246    {
247      return false;
248    }
249
250    /** {@inheritDoc} */
251    public boolean isDisposeOnClose()
252    {
253      return true;
254    }
255
256    /**
257     * Appends a line to the logs (Details are) section of the panel.  The text
258     * will have a new-line char at the end (is similar to println()).
259     * @param msg the HTML formatted text to be appended.
260     */
261    public void appendErrorLine(String msg)
262    {
263      msg = filterForBugID4988885(msg+"<br>");
264      msg = Utilities.applyFont(msg, ColorAndFontConstants.progressFont);
265      appendHtml(msg);
266    }
267
268    /**
269     * Sets the text to be displayed in the summary area of the progress
270     * dialog.
271     * @param msg the text to be displayed.
272     */
273    public void setSummary(LocalizableMessage msg)
274    {
275      errorPane.setText(msg.toString());
276
277      if (!details.isSelected() && isVisible())
278      {
279        LocalizableMessage wrappedText = Utilities.wrapHTML(msg, 70);
280        JEditorPane pane = new JEditorPane();
281        pane.setContentType("text/html");
282        pane.setText(wrappedText.toString());
283        ProgressDialog dlg = (ProgressDialog)Utilities.getParentDialog(this);
284        int width = Math.max(pane.getPreferredSize().width + 40,
285        dlg.getWidth());
286        int height = Math.max(pane.getPreferredSize().height + 40 +
287            extraStrut.getHeight() + details.getPreferredSize().height,
288        dlg.getHeight());
289        // We might want to resize things.
290        if (width > dlg.getWidth() || height > dlg.getHeight())
291        {
292          Dimension newDim = new Dimension(width, height);
293          dlg.setSize(newDim);
294        }
295      }
296    }
297
298    /**
299     * Appends a line to the logs (Details are) section of the panel.  The text
300     * will be preceded by a new line (is similar to println()).
301     * @param msg the HTML formatted text to be appended.
302     */
303    public void appendOutputLine(String msg)
304    {
305      appendErrorLine(msg);
306    }
307
308    /**
309     * Appends text to the logs (Details are) section of the panel.  The text
310     * will be appended as it is (is similar to print()).
311     * @param msg the HTML formatted text to be appended.
312     */
313    public void appendHtml(String msg)
314    {
315      HTMLDocument doc = (HTMLDocument)logs.getDocument();
316
317      try
318      {
319        msg = filterForBugID4988885(msg);
320        doc.insertBeforeStart(doc.getElement(LASTID), msg);
321      }
322      catch (Throwable t)
323      {
324        // Bug
325        t.printStackTrace();
326      }
327    }
328
329    /**
330     * Resets the contents of the logs (Details) section.
331     *
332     */
333    public void resetLogs()
334    {
335      logs.setText(INIT_TEXT);
336    }
337
338    /**
339     * Creates the layout of the panel (but the contents are not populated
340     * here).
341     *
342     */
343    private void createLayout()
344    {
345      GridBagConstraints gbc = new GridBagConstraints();
346      addErrorPane(gbc);
347
348      errorPane.setVisible(true);
349      errorPane.setText(Utilities.applyFont(
350              INFO_CTRL_PANEL_PLEASE_WAIT_SUMMARY.get(),
351              ColorAndFontConstants.defaultFont));
352
353      gbc.anchor = GridBagConstraints.WEST;
354      gbc.gridwidth = 1;
355      gbc.gridx = 0;
356      gbc.gridy = 1;
357
358      progressBar = new JProgressBar();
359      progressBar.setMaximum(100);
360      gbc.weightx = 1.0;
361      gbc.fill = GridBagConstraints.HORIZONTAL;
362      gbc.insets = new Insets(10, 20, 0, 30);
363      add(progressBar, gbc);
364
365      gbc.insets.top = 10;
366      gbc.insets.bottom = 5;
367      details =
368        new BasicExpander(INFO_CTRL_PANEL_PROGRESS_DIALOG_DETAILS_LABEL.get());
369      gbc.gridy ++;
370      add(details, gbc);
371
372      logs = Utilities.makeHtmlPane(FAKE_PROGRESS_TEXT,
373          ColorAndFontConstants.progressFont);
374      gbc.gridy ++;
375      gbc.weighty = 1.0;
376      gbc.fill = GridBagConstraints.BOTH;
377      gbc.insets.top = 5;
378      gbc.insets.right = 20;
379      gbc.insets.bottom = 5;
380      scroll = Utilities.createScrollPane(logs);
381      scroll.setOpaque(false);
382      scroll.getViewport().setOpaque(false);
383      add(scroll, gbc);
384      Dimension scrollDim = scroll.getPreferredSize();
385
386      gbc.weighty = 1.0;
387      extraStrut = Box.createRigidArea(new Dimension(scrollDim.width, 50));
388      add(extraStrut, gbc);
389      gbc.gridy ++;
390      gbc.weighty = 0.0;
391      add(Box.createHorizontalStrut(scrollDim.width), gbc);
392
393      heightDiff = scrollDim.height - extraStrut.getHeight();
394
395      logs.setText(INIT_TEXT);
396
397      scroll.setPreferredSize(scrollDim);
398
399      updateVisibility(lastShowDetails);
400      details.addActionListener(new ActionListener()
401      {
402        /** {@inheritDoc} */
403        public void actionPerformed(ActionEvent ev)
404        {
405          lastShowDetails = details.isSelected();
406          updateVisibility(lastShowDetails);
407        }
408      });
409
410      // The button panel
411      gbc.gridy ++;
412      gbc.weighty = 0.0;
413      gbc.insets = new Insets(0, 0, 0, 0);
414      add(createButtonsPanel(), gbc);
415    }
416
417    private JPanel createButtonsPanel()
418    {
419      JPanel buttonsPanel = new JPanel(new GridBagLayout());
420      GridBagConstraints gbc = new GridBagConstraints();
421      gbc.gridx = 0;
422      gbc.gridy = 0;
423      gbc.anchor = GridBagConstraints.WEST;
424      gbc.fill = GridBagConstraints.HORIZONTAL;
425      gbc.gridwidth = 1;
426      gbc.gridy = 0;
427      closeWhenOver = Utilities.createCheckBox(
428          INFO_CTRL_PANEL_CLOSE_WINDOW_WHEN_OPERATION_COMPLETES_LABEL.get());
429      closeWhenOver.setOpaque(false);
430      closeWhenOver.addActionListener(new ActionListener()
431      {
432        /** {@inheritDoc} */
433        public void actionPerformed(ActionEvent ev)
434        {
435          closeWhenOverClicked();
436        }
437      });
438      closeWhenOver.setSelected(lastCloseWhenOver);
439      gbc.insets = new Insets(10, 10, 10, 10);
440      buttonsPanel.add(closeWhenOver, gbc);
441      gbc.weightx = 1.0;
442      gbc.gridx ++;
443      buttonsPanel.add(Box.createHorizontalStrut(150));
444      buttonsPanel.add(Box.createHorizontalGlue(), gbc);
445      buttonsPanel.setOpaque(true);
446      buttonsPanel.setBackground(ColorAndFontConstants.greyBackground);
447      gbc.gridx ++;
448      gbc.weightx = 0.0;
449      buttonsPanel.add(Box.createHorizontalStrut(100));
450      gbc.gridx ++;
451      closeButton = Utilities.createButton(INFO_CLOSE_BUTTON_LABEL.get());
452      closeButton.setOpaque(false);
453      gbc.gridx ++;
454      gbc.insets.left = 5;
455      gbc.insets.right = 10;
456      buttonsPanel.add(closeButton, gbc);
457      closeButton.addActionListener(new ActionListener()
458      {
459        /** {@inheritDoc} */
460        public void actionPerformed(ActionEvent ev)
461        {
462          closeClicked();
463        }
464      });
465
466      buttonsPanel.setBorder(BorderFactory.createMatteBorder(1, 0, 0, 0,
467          ColorAndFontConstants.defaultBorderColor));
468
469      return buttonsPanel;
470    }
471
472    private void updateVisibility(boolean showDetails)
473    {
474      scroll.setVisible(showDetails);
475      extraStrut.setVisible(!showDetails);
476      details.setSelected(showDetails);
477
478      final Window dialog = Utilities.getParentDialog(this);
479      if (dialog != null)
480      {
481        final Runnable repaint = new Runnable()
482        {
483          public void run()
484          {
485            invalidate();
486            dialog.invalidate();
487            dialog.repaint();
488          }
489        };
490
491        final Dimension dialogSize = dialog.getSize();
492        if (showDetails)
493        {
494          lastCollapsedHeight = dialogSize.height;
495          if (lastExpandedHeight == -1)
496          {
497            dialog.setSize(new Dimension(dialogSize.width, dialogSize.height + heightDiff));
498          }
499          else
500          {
501            dialog.setSize(new Dimension(dialogSize.width, lastExpandedHeight));
502          }
503          SwingUtilities.invokeLater(repaint);
504        }
505        else
506        {
507          lastExpandedHeight = dialogSize.height;
508          if (lastCollapsedHeight == -1)
509          {
510            packParentDialog();
511          }
512          else
513          {
514            dialog.setSize(new Dimension(dialogSize.width, lastCollapsedHeight));
515            SwingUtilities.invokeLater(repaint);
516          }
517        }
518      }
519    }
520
521    /** {@inheritDoc} */
522    public GenericDialog.ButtonType getButtonType()
523    {
524      return GenericDialog.ButtonType.NO_BUTTON;
525    }
526
527    /** {@inheritDoc} */
528    public void configurationChanged(ConfigurationChangeEvent ev)
529    {
530    }
531
532    /** {@inheritDoc} */
533    public Component getPreferredFocusComponent()
534    {
535      return details;
536    }
537
538    /** {@inheritDoc} */
539    public void okClicked()
540    {
541      Utilities.getParentDialog(this).setVisible(false);
542    }
543
544    /**
545     * Returns the progress bar of the dialog.
546     * @return the progress bar of the dialog.
547     */
548    public JProgressBar getProgressBar()
549    {
550      return progressBar;
551    }
552
553    /**
554     * Checks if the 'Close when over' check box is selected and if it is the
555     * case, closes the dialog after waiting for 2 seconds (so that the user
556     * can see the result, or cancel the automatic closing of the dialog).
557     *
558     */
559    private void closeWhenOverClicked()
560    {
561      lastCloseWhenOver = closeWhenOver.isSelected();
562      if (lastCloseWhenOver && taskIsOver)
563      {
564        Thread t = new Thread(new Runnable()
565        {
566          /** {@inheritDoc} */
567          public void run()
568          {
569            try
570            {
571              Thread.sleep(2000);
572              SwingUtilities.invokeLater(new Runnable()
573              {
574                public void run()
575                {
576                  if (closeWhenOver.isSelected() && taskIsOver)
577                  {
578                    closeClicked();
579                  }
580                }
581              });
582            }
583            catch (Throwable t)
584            {
585            }
586          }
587        });
588        t.start();
589      }
590    }
591  }
592
593  /**
594   * This is necessary because of bug 4988885.
595   * http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4988885
596   * @param msg the message.
597   * @return the message filtered.
598   */
599  private static String filterForBugID4988885(String msg)
600  {
601    return msg.replaceAll("<br>", "&#10;<br>");
602  }
603}