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