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