1 /*
   2  * $Id$
   3  *
   4  * Copyright (c) 2006, 2013, Oracle and/or its affiliates. All rights reserved.
   5  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   6  *
   7  * This code is free software; you can redistribute it and/or modify it
   8  * under the terms of the GNU General Public License version 2 only, as
   9  * published by the Free Software Foundation.  Oracle designates this
  10  * particular file as subject to the "Classpath" exception as provided
  11  * by Oracle in the LICENSE file that accompanied this code.
  12  *
  13  * This code is distributed in the hope that it will be useful, but WITHOUT
  14  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  15  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  16  * version 2 for more details (a copy is included in the LICENSE file that
  17  * accompanied this code).
  18  *
  19  * You should have received a copy of the GNU General Public License version
  20  * 2 along with this work; if not, write to the Free Software Foundation,
  21  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  22  *
  23  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  24  * or visit www.oracle.com if you need additional information or have any
  25  * questions.
  26  */
  27 package com.sun.javatest.mrep;
  28 
  29 import java.awt.BorderLayout;
  30 import java.awt.Cursor;
  31 import java.awt.Dimension;
  32 import java.awt.EventQueue;
  33 import java.awt.GridBagConstraints;
  34 import java.awt.GridBagLayout;
  35 import java.awt.event.ActionEvent;
  36 import java.awt.event.ActionListener;
  37 import java.awt.event.HierarchyEvent;
  38 import java.awt.event.HierarchyListener;
  39 import java.awt.event.ItemEvent;
  40 import java.awt.event.ItemListener;
  41 import java.io.*;
  42 import java.net.MalformedURLException;
  43 import java.net.URL;
  44 import java.nio.charset.StandardCharsets;
  45 import java.util.Map;
  46 
  47 import javax.swing.BorderFactory;
  48 import javax.swing.JEditorPane;
  49 import javax.swing.JFileChooser;
  50 import javax.swing.JMenu;
  51 import javax.swing.JMenuBar;
  52 import javax.swing.JPanel;
  53 import javax.swing.event.HyperlinkEvent;
  54 import javax.swing.event.HyperlinkListener;
  55 import javax.swing.text.JTextComponent;
  56 import javax.swing.text.html.HTMLDocument;
  57 import javax.swing.text.html.HTMLEditorKit;
  58 import javax.swing.text.html.HTMLFrameHyperlinkEvent;
  59 
  60 import com.sun.javatest.report.CustomReport;
  61 import com.sun.javatest.report.Report;
  62 import com.sun.javatest.report.ReportSettings;
  63 import com.sun.javatest.tool.Desktop;
  64 import com.sun.javatest.tool.Tool;
  65 import com.sun.javatest.tool.ToolManager;
  66 import com.sun.javatest.report.HTMLWriterEx;
  67 import com.sun.javatest.util.I18NResourceBundle;
  68 import java.awt.Component;
  69 import java.lang.reflect.InvocationTargetException;
  70 import javax.swing.JButton;
  71 import javax.swing.JDialog;
  72 import javax.swing.Timer;
  73 import javax.swing.SwingUtilities;
  74 
  75 
  76 class ReportTool extends Tool {
  77 
  78     // desktop is used to get custom reports
  79     // for results processing
  80     private Desktop desktop;
  81 
  82     protected ReportTool(ToolManager m, Desktop d) {
  83         super(m, "report", "mergeReports.window.csh");
  84         setI18NTitle("tool.title");
  85         setShortTitle(uif.getI18NString("tool.shortTitle"));
  86         this.desktop = d;
  87         initGUI();
  88         I18NResourceBundle i18n = I18NResourceBundle.getBundleForClass(ReportTool.class);
  89         textShowing = i18n.getString("tool.helptext.showing");
  90         textHidden = i18n.getString("tool.helptext.hidden");
  91     }
  92 
  93     public JMenuBar getMenuBar() {
  94         return menuBar;
  95     }
  96 
  97     @Override
  98     protected void restore(Map map) {
  99     }
 100 
 101     protected void save(Map m) {
 102     }
 103 
 104     private void initGUI(){
 105         int dpi = uif.getDotsPerInch();
 106         setPreferredSize(new Dimension(6 * dpi, 4 * dpi));
 107         setLayout(new BorderLayout());
 108         htmlKit = new HTMLEditorKit();
 109 
 110         addHierarchyListener(listener);
 111 
 112         menuBar = uif.createMenuBar("tool");
 113         String[] reportMenuEntries = {
 114             NEW,
 115             OPEN
 116         };
 117 
 118         JMenu reportMenu = uif.createMenu("tool.report", reportMenuEntries, new Listener());
 119         menuBar.add(reportMenu);
 120         menuBar.add(uif.createHorizontalGlue("tool.pad"));
 121 
 122         JPanel head = uif.createPanel("head", false);
 123         head.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
 124         head.setLayout(new GridBagLayout());
 125 
 126         GridBagConstraints c = new GridBagConstraints();
 127         c.fill = GridBagConstraints.BOTH;
 128         c.gridwidth = 2;
 129         c.gridheight = GridBagConstraints.REMAINDER;
 130         c.gridx = 0;
 131         c.gridy = 2;
 132         c.weightx = 1.0;
 133         c.weighty = 1.0;
 134         browserPane = new BrowserPane(uif);
 135         head.add(browserPane, c);
 136         add(head);
 137         updateGUI();
 138 
 139     }
 140 
 141     void updateGUI() {
 142         if (!EventQueue.isDispatchThread()) {
 143             EventQueue.invokeLater(new Runnable() {
 144                 public void run() {
 145                     updateGUI();
 146                 }
 147             });
 148             return;
 149         }
 150 
 151         if (browserPane.isEmpty()) {
 152             if (optionsDialog == null || !optionsDialog.isVisible()) {
 153                 browserPane.setPlainText(textHidden);
 154             } else {
 155                 browserPane.setPlainText(textShowing);
 156             }
 157         } else {
 158             if (optionsDialog == null || !optionsDialog.isVisible()) {
 159                 browserPane.setPlainTextHomePage(textHidden);
 160             } else {
 161                 browserPane.setPlainTextHomePage(textShowing);
 162             }
 163         }
 164     }
 165 
 166     private synchronized void showOptions() {
 167         if (worker != null) {
 168             uif.showError("tool.reportInProgress");
 169             return;
 170         }
 171 
 172         if (optionsDialog == null) {
 173             // should arguably worry about standard ownership problem
 174             optionsDialog = new OptionsDialog(this, new OkListener(), uif, desktop);
 175         }
 176         optionsDialog.updateCustomReports();
 177         optionsDialog.setVisible(true);
 178     }
 179 
 180     private boolean setOptions() {
 181 
 182         if (!optionsDialog.checkInput()) {
 183             return false;
 184         }
 185 
 186         out = new File(optionsDialog.getResultPath());
 187 
 188         in = new File[optionsDialog.getXmlFiles().length];
 189         for (int i = 0; i < optionsDialog.getXmlFiles().length; i++) {
 190             in[i] = new File(optionsDialog.getXmlFiles()[i]);
 191         }
 192 
 193         resolveByRecent = optionsDialog.resolveByRecent();
 194         customReports = optionsDialog.getCustomReports();
 195         isXmlReport = optionsDialog.isXmlReport();
 196 
 197         if (!isXmlReport && (customReports == null || customReports.length ==0)) {
 198             uif.showError("tool.no_report_types");
 199             return false;
 200         }
 201 
 202         File outXML = new File(out, xmlreportFileName);
 203         for (int i = 0; i < in.length; i++) {
 204             String path = outXML.getAbsolutePath();
 205             if (in[i].getAbsolutePath().equals(path)) {
 206                 uif.showError("tool.outinput", path);
 207                 return false;
 208             }
 209         }
 210 
 211         try {
 212             optionsDialog.setVisible(false);
 213             startMerge();
 214             return true;
 215         } catch (Exception e) {
 216             uif.showError("tool.execpt", e.getMessage());
 217         }
 218         return false;
 219     }
 220 
 221     private synchronized void startMerge() {
 222         if (worker != null) {
 223             uif.showError("tool.reportInProgress");
 224             return;
 225         }
 226 
 227         waitDialog = uif.createWaitDialog("tool.wait", this);
 228         GridBagConstraints gbc = new GridBagConstraints();
 229         gbc.fill = GridBagConstraints.NONE;
 230         gbc.anchor = GridBagConstraints.CENTER;
 231         gbc.insets.bottom = 10;
 232         gbc.insets.top = 10;
 233         gbc.gridy = 2;
 234         gbc.gridx = 0;
 235         JButton cancelBtn = uif.createButton("tool.cancel");
 236         waitDialog.getContentPane().add(cancelBtn, gbc);
 237         waitDialog.pack();
 238         waitDialogController = new WaitDialogController(waitDialog);
 239         final String cancelling = uif.getI18NString("tool.cancelling");
 240         cancelBtn.addActionListener( new ActionListener() {
 241             public void actionPerformed(ActionEvent e) {
 242                 JButton butt = (JButton) e.getSource();
 243                 butt.setEnabled(false);
 244                 Component[] cmp = waitDialog.getContentPane().getComponents();
 245                 if (worker != null && worker.isAlive()) {
 246                     worker.interrupt();
 247                 }
 248                 for ( int i = 0; i < cmp.length; i++) {
 249                     if("tool.wait".equals(cmp[i].getName())) {
 250                         if (cmp[i] instanceof JTextComponent) {
 251                             ((JTextComponent)cmp[i]).setText(cancelling);
 252                         }
 253                         break;
 254                     }
 255                 }
 256 
 257             }
 258         });
 259 
 260         worker = new Thread() {
 261             public void run() {
 262                 try {
 263                     Merger merger = new Merger();
 264                     ConflictResolver resolver;
 265                     if (!resolveByRecent) {
 266                         resolver = new ManualConfilctResolver(waitDialogController);
 267                     } else {
 268                         resolver = new MostRecentConfilctResolver();
 269                     }
 270                     out.mkdir();
 271                     File xmlOut = new File(out, xmlreportFileName);
 272                     if (merger.merge(in,xmlOut, resolver)) {
 273                         for (int i = 0; i < customReports.length; i++) {
 274                             if (Thread.currentThread().isInterrupted()) {
 275                                 return ;
 276                             }
 277                             CustomReport cr = customReports[i];
 278                             ReportSettings re = cr.getReportEnviroment();
 279                             re.setMergingFiles(in);
 280                             re.setXMLReportFile(xmlOut);
 281                             cr.createReport(new File(out, cr.getReportId()));
 282                         }
 283 
 284                         waitDialogController.finish();
 285 
 286                         if (!isXmlReport) {
 287                             xmlOut.delete();
 288                         }
 289                         showReportDialog(out);
 290                     }
 291                 } catch (Exception e) {
 292                     showError("tool.exceptionInProgress", e.getMessage(), waitDialog, waitDialogController);
 293                 } finally {
 294 
 295                     if (!waitDialogController.wasFinished)
 296                         waitDialogController.finish();
 297 
 298                     synchronized (ReportTool.this) {
 299                         worker = null;
 300                     }
 301                     updateGUI();
 302                 }
 303             }
 304 
 305         };
 306 
 307         ActionListener al = new ActionListener() {
 308             public void actionPerformed(ActionEvent evt) {
 309                 // show dialog if still processing
 310                 if (worker != null && worker.isAlive()) {
 311                     waitDialogController.show();
 312                 }
 313             }
 314         };
 315 
 316         // show wait dialog if operation is still running after
 317         // WAIT_DIALOG_DELAY
 318         Timer timer = new Timer(WAIT_DIALOG_DELAY, al);
 319         timer.setRepeats(false);
 320         timer.start();
 321 
 322         worker.start();
 323 
 324         updateGUI();
 325     }
 326 
 327     private void showError(final String uiKey, final String msg,
 328                            final JDialog waitDialog, final WaitDialogController waitDialogController) {
 329         if (!waitDialogController.wasFinished)
 330             waitDialogController.finish();
 331         // switch back to GUI thread
 332         EventQueue.invokeLater(new Runnable() {
 333                 public void run() {
 334                     uif.showError(uiKey, msg);
 335                 }
 336             }
 337         );
 338     }
 339 
 340     private static class WaitDialogController {
 341         WaitDialogController(JDialog waitDialog) {
 342             this.waitDialog = waitDialog;
 343         }
 344 
 345         synchronized void show() {
 346             // show only once
 347             if (!wasFinished) {
 348                 wasShown = true;
 349                 if (!wasHidden) {
 350                     setVisible(true);
 351                 }
 352             }
 353         }
 354 
 355         synchronized void finish() {
 356             wasFinished = true;
 357             setVisible(false);
 358         }
 359 
 360 
 361         synchronized void hide() {
 362             wasHidden = true;
 363             if (wasShown && ! wasFinished) {
 364                 setVisible(false);
 365             }
 366         }
 367 
 368         synchronized void restore() {
 369             wasHidden = false;
 370             if (wasShown && ! wasFinished) {
 371                 setVisible(true);
 372             }
 373         }
 374 
 375         private synchronized void setVisible(final boolean b) {
 376             // should we care about EventDispatchThread here? Yes I guess.
 377             if (EventQueue.isDispatchThread()) {
 378                 waitDialog.setVisible(b);
 379             }  else {
 380                 try {
 381                     SwingUtilities.invokeAndWait(new Runnable() {
 382                         public void run() {
 383                             waitDialog.setVisible(b);
 384                         }
 385                     });
 386                 } catch (InterruptedException e) {
 387                     e.printStackTrace();
 388                 } catch (InvocationTargetException e) {
 389                     e.printStackTrace();
 390                 }
 391             }
 392         }
 393 
 394         private JDialog waitDialog;
 395         private boolean wasFinished = false;
 396         private boolean wasShown = false;
 397         private boolean wasHidden = false;
 398 
 399     }
 400 
 401     class MostRecentConfilctResolver implements ConflictResolver {
 402 
 403         public int resolve(String testUrl, TestResultDescr[] descrs) {
 404             int res = 0;
 405 
 406             for (int i = 1; i < descrs.length; i++) {
 407                 // priority of NOT_RUN status is the lowerest
 408                 boolean newer = descrs[i].getTime() > descrs[res].getTime();
 409                 if (descrs[res].isNotRun()) {
 410                     if (!descrs[i].isNotRun() || newer) {
 411                         res = i;
 412                     }
 413                 } else {
 414                     if (!descrs[i].isNotRun() && newer) {
 415                         res = i;
 416                     }
 417                 }
 418             }
 419             return res;
 420 
 421         }
 422 
 423     }
 424 
 425     class ManualConfilctResolver implements ConflictResolver {
 426         private File preffered = null;
 427         private ConflictResolutionDialog conflictResolutionDialog;
 428         private WaitDialogController wdc;
 429 
 430         public ManualConfilctResolver(WaitDialogController wdc) {
 431             this.wdc = wdc;
 432         }
 433 
 434         public int resolve(String testUrl, TestResultDescr[] descrs) {
 435             String[] conflictFiles = new String[descrs.length];
 436             for (int i = 0; i < descrs.length; i++) {
 437                 conflictFiles[i] = descrs[i].getFile().getAbsolutePath() + " "
 438                         + descrs[i].getStatus() ;
 439             }
 440 
 441             if (preffered != null) {
 442                 for (int i = 0; i < descrs.length; i++) {
 443                     if (descrs[i].getFile().equals(preffered))
 444                         return i;
 445                 }
 446             }
 447 
 448             conflictResolutionDialog =
 449                     new ConflictResolutionDialog(
 450                     null, testUrl, conflictFiles, false, uif);
 451 
 452             wdc.hide();
 453             conflictResolutionDialog.setVisible(true);
 454 
 455             if (conflictResolutionDialog.wasCanceled()) {
 456                 return -1;
 457             }
 458             wdc.restore();
 459 
 460             if (conflictResolutionDialog.getUseMostRecent()) {
 461                 int res = 0;
 462                 for (int i = 0; i < descrs.length; i++) {
 463                     if (descrs[i].getTime() > descrs[res].getTime()) {
 464                         res = i;
 465                     }
 466                 }
 467                 return res;
 468             }
 469 
 470 
 471             int selected = conflictResolutionDialog.getSelectedIndex();
 472             if (selected != -1) {
 473                 if (conflictResolutionDialog.getPreferredReport()) {
 474                     preffered = descrs[selected].getFile();
 475                 }
 476                 return selected;
 477             }
 478             return 0;
 479         }
 480 
 481     }
 482 
 483 
 484     private String listLocalDirectory(File dir) {
 485         if (!dir.isAbsolute())
 486             dir = dir.getAbsoluteFile();
 487 
 488         String displayPath = dir.getPath();
 489 
 490         String[] filelist = dir.list();
 491         StringWriter sw = new StringWriter();
 492         try {
 493             HTMLWriterEx out = new HTMLWriterEx(sw, uif.getI18NResourceBundle());
 494 
 495             out.startTag(HTMLWriterEx.HTML);
 496             out.startTag(HTMLWriterEx.HEAD);
 497             out.writeContentMeta();
 498             out.startTag(HTMLWriterEx.TITLE);
 499             out.write(displayPath);
 500             out.endTag(HTMLWriterEx.TITLE);
 501             out.endTag(HTMLWriterEx.HEAD);
 502             out.startTag(HTMLWriterEx.BODY);
 503             out.writeStyleAttr("font-family: SansSerif; font-size: 12pt");
 504             out.startTag(HTMLWriterEx.H3);
 505             out.writeI18N("fp.head", displayPath);
 506             out.endTag(HTMLWriterEx.H3);
 507             out.startTag(HTMLWriterEx.UL);
 508             out.writeStyleAttr("margin-left:0");
 509 
 510             File parent = dir.getParentFile();
 511             if (parent != null) {
 512                 out.startTag(HTMLWriterEx.LI);
 513                 out.startTag(HTMLWriterEx.OBJECT);
 514                 out.writeAttr(HTMLWriterEx.CLASSID, "com.sun.javatest.tool.IconLabel");
 515                 out.writeParam("type", "up");
 516                 out.endTag(HTMLWriterEx.OBJECT);
 517                 out.writeEntity("&nbsp;");
 518                 try {
 519                     out.startTag(HTMLWriterEx.A);
 520                     out.writeAttr(HTMLWriterEx.HREF, parent.toURL().toString());
 521                     out.writeI18N("fp.parent");
 522                     out.endTag(HTMLWriterEx.A);
 523                 } catch (MalformedURLException e) {
 524                     out.writeI18N("fp.parent");
 525                 }
 526             }
 527 
 528             for (int i = 0; i < filelist.length; i++) {
 529                 File file = new File(dir, filelist[i]);
 530                 out.startTag(HTMLWriterEx.LI);
 531                 out.startTag(HTMLWriterEx.OBJECT);
 532                 out.writeAttr(HTMLWriterEx.CLASSID, "com.sun.javatest.tool.IconLabel");
 533                 out.writeParam("type", (file.isDirectory() ? "folder" : "file"));
 534                 out.endTag(HTMLWriterEx.OBJECT);
 535                 out.writeEntity("&nbsp;");
 536                 try {
 537                     out.writeLink(file.toURL(), file.getName());
 538                 } catch (MalformedURLException e) {
 539                     out.write(file.getName());
 540                 }
 541             }
 542 
 543             out.endTag(HTMLWriterEx.UL);
 544             out.endTag(HTMLWriterEx.BODY);
 545             out.endTag(HTMLWriterEx.HTML);
 546             out.close();
 547         } catch (IOException e) {
 548             // should not happen, writing to StringWriter
 549         }
 550 
 551         return sw.toString();
 552     }
 553 
 554     private void loadPage(URL url) {
 555         // avoid recursive callbacks from updating combo
 556         // URL.equals can result in a big performance hit
 557         if (currURL != null && url.toString().equals(currURL.toString()))
 558             return;
 559 
 560         currURL = url;
 561 
 562         String protocol = url.getProtocol();
 563         File file = new File(url.getFile());
 564         if (protocol.equals("file") && file.isDirectory()) {
 565             String list = listLocalDirectory(file);
 566             HTMLDocument htmlDoc = (HTMLDocument) (htmlKit.createDefaultDocument());
 567             textArea.setDocument(htmlDoc);
 568             htmlDoc.setBase(url);
 569             textArea.setContentType("text/html");
 570             textArea.setText(list);
 571         } else if (protocol.equals("file")
 572         && !url.getFile().endsWith(".htm")
 573         && !url.getFile().endsWith(".html")) {
 574             textArea.setContentType("text/plain");
 575             try {
 576                 Reader r = new BufferedReader(new InputStreamReader(new FileInputStream(file), StandardCharsets.UTF_8));
 577                 textArea.read(r, url);
 578                 r.close();
 579             } catch (IOException e) {
 580                 uif.showError("fp.load.error", new Object[] { url, e });
 581             }
 582 
 583         } else {
 584             try {
 585                 URL loaded = textArea.getPage();
 586                 // this next stuff is just to avoid some screen flash if a new doc
 587                 // is being read
 588                 if (loaded == null || !loaded.sameFile(url)) {
 589                     HTMLDocument htmlDoc = (HTMLDocument) (htmlKit.createDefaultDocument());
 590                     textArea.setDocument(htmlDoc);
 591                 }
 592                 textArea.setPage(url);
 593             } catch (IOException e) {
 594                 uif.showError("fp.load.error", new Object[] { url, e });
 595             }
 596         }
 597 
 598     }
 599 
 600 
 601     // The UI components
 602     private JMenuBar menuBar;
 603     private BrowserPane browserPane;
 604     private JDialog waitDialog;
 605     private WaitDialogController waitDialogController;
 606     private static final int WAIT_DIALOG_DELAY = 3000;      // 3 second delay
 607 
 608     // The merge options
 609     private File[] in;
 610     private File out;
 611     private boolean resolveByRecent;
 612     private boolean isXmlReport;
 613     private CustomReport[] customReports;
 614 
 615     private String xmlreportFileName = "report.xml";
 616 
 617     private boolean autoShowOptions = true;
 618 
 619     private Thread worker;
 620     private URL currURL;
 621 
 622     private HTMLEditorKit htmlKit;
 623     private JEditorPane textArea;
 624 
 625     private static final String NEW = "new";
 626     private static final String OPEN = "open";
 627 
 628     private OptionsDialog optionsDialog;
 629     private Listener listener = new Listener();
 630 
 631     private final String textShowing;
 632     private final String textHidden;
 633 
 634     class OkListener implements ActionListener {
 635         public OkListener() {
 636         }
 637 
 638         public void actionPerformed(ActionEvent e) {
 639             if(setOptions())
 640                 optionsDialog.cleanUp();
 641             updateGUI();
 642         }
 643     };
 644 
 645     private void showReportBrowser(File reportDir) {
 646         // if if is a dir, try to find a particular file to show
 647         // since there may be multiple choices, use the one with the
 648         // most recent date
 649         String[] names = Report.getHtmlReportFilenames();
 650         File target = reportDir;
 651         long newestTime = 0;
 652 
 653         for (int i = 0; i < names.length; i++) {
 654             File f = new File(reportDir, names[i]);
 655             if (f.exists()  && f.lastModified() > newestTime) {
 656                 target = f;
 657                 newestTime = f.lastModified();
 658             }
 659         }
 660 
 661         try {
 662             browserPane.setFile(target.toURL());
 663         } catch (MalformedURLException e) {
 664             uif.showError("tool.report.browser", e.getMessage());
 665         }
 666     }
 667 
 668     void showReportDialog(File init) {
 669         JFileChooser rdc = new JFileChooser(init);
 670 
 671         int option = rdc.showDialog(this, uif.getI18NString("tool.report.open"));
 672         if (option != JFileChooser.APPROVE_OPTION)
 673             return;
 674 
 675         File f = rdc.getSelectedFile();
 676         showReportBrowser(f);
 677 
 678     }
 679 
 680     private class Listener implements ActionListener, HierarchyListener {
 681         public void actionPerformed(ActionEvent e) {
 682             String cmd = e.getActionCommand();
 683             if (cmd.equals(NEW)) {
 684                 showOptions();
 685             } else if (cmd.equals(OPEN)) {
 686                 showReportDialog(out);
 687             }
 688             updateGUI();
 689         }
 690 
 691         public void hierarchyChanged(HierarchyEvent e) {
 692             if (isShowing() && autoShowOptions) {
 693                 EventQueue.invokeLater(new Runnable() {
 694                     public void run() {
 695                         showOptions();
 696                         updateGUI();
 697                     }
 698                 });
 699                 autoShowOptions = false;
 700             }
 701         }
 702 
 703     };
 704 
 705     private class HTMLListener implements HyperlinkListener, ItemListener {
 706         public void hyperlinkUpdate(HyperlinkEvent e) {
 707             HyperlinkEvent.EventType et = e.getEventType();
 708             if (et == HyperlinkEvent.EventType.ACTIVATED) {
 709                 if (e instanceof HTMLFrameHyperlinkEvent) {
 710                     HTMLDocument doc = (HTMLDocument)
 711                     ((JEditorPane) e.getSource()).getDocument();
 712                     doc.processHTMLFrameHyperlinkEvent((HTMLFrameHyperlinkEvent) e);
 713                 } else
 714                     loadPage(e.getURL());
 715             } else if (et == HyperlinkEvent.EventType.ENTERED) {
 716                 URL u = e.getURL();
 717                 if (u != null) {
 718                     textArea.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
 719                 }
 720             } else if (et == HyperlinkEvent.EventType.EXITED) {
 721                 textArea.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
 722             }
 723         }
 724 
 725         public void itemStateChanged(ItemEvent e) {
 726             if (e.getStateChange() == ItemEvent.SELECTED) {
 727                 URL url = (URL) e.getItem();
 728                 loadPage(url);
 729             }
 730         }
 731     }
 732 
 733 }