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.Component; 31 import java.awt.Cursor; 32 import java.awt.GridBagConstraints; 33 import java.awt.GridBagLayout; 34 import java.awt.Insets; 35 import java.awt.event.ActionEvent; 36 import java.awt.event.ItemEvent; 37 import java.awt.event.ItemListener; 38 import java.io.*; 39 import java.net.MalformedURLException; 40 import java.net.URL; 41 import java.nio.charset.StandardCharsets; 42 import java.util.Vector; 43 44 import javax.swing.Action; 45 import javax.swing.BorderFactory; 46 import javax.swing.DefaultComboBoxModel; 47 import javax.swing.DefaultListCellRenderer; 48 import javax.swing.JButton; 49 import javax.swing.JComboBox; 50 import javax.swing.JEditorPane; 51 import javax.swing.JLabel; 52 import javax.swing.JList; 53 import javax.swing.JPanel; 54 import javax.swing.JScrollPane; 55 import javax.swing.JTextField; 56 import javax.swing.JToolBar; 57 import javax.swing.ScrollPaneConstants; 58 import javax.swing.event.HyperlinkEvent; 59 import javax.swing.event.HyperlinkListener; 60 import javax.swing.plaf.basic.BasicComboBoxUI; 61 import javax.swing.plaf.basic.BasicComboPopup; 62 import javax.swing.plaf.basic.ComboPopup; 63 import javax.swing.text.html.HTMLDocument; 64 import javax.swing.text.html.HTMLEditorKit; 65 import javax.swing.text.html.HTMLFrameHyperlinkEvent; 66 67 import com.sun.javatest.tool.ToolAction; 68 import com.sun.javatest.tool.UIFactory; 69 import com.sun.javatest.report.HTMLWriterEx; 70 import com.sun.javatest.util.I18NResourceBundle; 71 72 class BrowserPane extends JPanel { 73 /** 74 * This exception is used to report problems that arise when using 75 * the FilesPane. 76 */ 77 static class Fault extends Exception { 78 Fault(I18NResourceBundle i18n, String s) { 79 super(i18n.getString(s)); 80 } 81 82 Fault(I18NResourceBundle i18n, String s, Object o) { 83 super(i18n.getString(s, o)); 84 } 85 } 86 87 BrowserPane(UIFactory uif) { 88 this.uif = uif; 89 90 history = new History(); 91 92 initGUI(); 93 } 94 95 void setBaseDirectory(File base) { 96 baseDir = base; 97 } 98 99 File getBaseDirectory() { 100 return baseDir; 101 } 102 103 void setFile(URL file) { 104 if (file != null) 105 setFiles(new URL[] { file }); 106 } 107 108 boolean isEmpty() { 109 return currURL == null; 110 } 111 112 void setPlainText(String text) { 113 textArea.setContentType("text/html"); 114 textArea.setText(text); 115 textHomePage = text; 116 } 117 118 void setPlainTextHomePage(String text) { 119 textHomePage = text; 120 } 121 122 void setFiles(URL[] files) { 123 clear(); 124 125 for (int i = 0; i < files.length; i++) { 126 // set the first file as home and show it 127 if (i == 0) { 128 // homeURL = files[i]; 129 homeAction.setEnabled(true); 130 loadPage(files[i]); 131 } 132 updateCombo(files[i]); 133 } 134 135 // updateCombo leaves the last inserted selected, which is 136 // useful elsewhere, but not here, we reset the selection 137 if (model.getSize() > 0) 138 selectBox.setSelectedIndex(0); 139 } 140 141 URL getPage() { 142 return textArea.getPage(); 143 } 144 145 //------------------------------------------------------------------------------------ 146 147 private void initGUI() { 148 setName("fp"); 149 setFocusable(false); 150 151 htmlKit = new HTMLEditorKit(); 152 initActions(); 153 154 setLayout(new BorderLayout()); 155 initHead(); 156 initBody(); 157 158 add(head, BorderLayout.NORTH); 159 add(body, BorderLayout.CENTER); 160 161 noteField = uif.createOutputField("fp.note"); 162 add(noteField, BorderLayout.SOUTH); 163 } 164 165 private void initActions() { 166 homeAction = new ToolAction(uif, "fp.home", true) { 167 public void actionPerformed(ActionEvent e) { 168 if (homeURL == null) 169 if (textHomePage != null) { 170 currURL = null; 171 updateCombo(null); 172 textArea.setContentType("text/html"); 173 textArea.setText(textHomePage); 174 } else 175 textArea.setDocument(new HTMLDocument()); 176 else 177 loadPage(homeURL); 178 } 179 }; 180 181 backAction = new ToolAction(uif, "fp.back", true) { 182 public void actionPerformed(ActionEvent e) { 183 URL url = history.prev(); 184 if (url != null) 185 loadPage(url); 186 } 187 }; 188 189 forwardAction = new ToolAction(uif, "fp.forward", true) { 190 public void actionPerformed(ActionEvent e) { 191 URL url = history.next(); 192 if (url != null) 193 loadPage(url); 194 } 195 }; 196 } 197 198 private void initHead() { 199 head = uif.createPanel("fp.head", false); 200 head.setLayout(new GridBagLayout()); 201 head.setBorder(BorderFactory.createEmptyBorder(0, 5, 0, 5)); 202 203 GridBagConstraints c = new GridBagConstraints(); 204 c.anchor = GridBagConstraints.EAST; 205 c.gridx = 0; 206 c.gridy = 0; 207 c.insets = new Insets(0,0,0,5); 208 209 JLabel fileLbl = uif.createLabel("fp.file", true); 210 head.add(fileLbl, c); 211 212 selectBox = uif.createChoice("fp.choice", fileLbl); 213 selectBox.setRenderer(new Renderer()); 214 selectBox.setModel(createModel()); 215 selectBox.addItemListener(listener); 216 selectBox.setMaximumRowCount(MAX_ROWS_DISPLAY); 217 selectBox.setUI(new BasicComboBoxUI() { 218 // wrap the content with a scrolling interface 219 // would be nice if Swing did this for us 220 protected ComboPopup createPopup() { 221 BasicComboPopup popup = new BasicComboPopup(selectBox) { 222 protected JScrollPane createScroller() { 223 return new JScrollPane(list, 224 ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED, 225 ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED); 226 } 227 }; 228 return popup; 229 } 230 } // class 231 ); 232 uif.setAccessibleName(selectBox, "fp.choice"); // override default a11y name 233 234 c.gridx = 1; 235 c.weightx = 2.0; 236 c.fill = GridBagConstraints.HORIZONTAL; 237 238 head.add(selectBox, c); 239 240 Action[] actions = { backAction, forwardAction, null, homeAction }; 241 242 toolBar = uif.createToolBar("fp.toolbar", actions ); 243 toolBar.setFloatable(false); 244 245 c.weightx = 0; 246 c.gridx = 2; 247 c.insets.left = 5; 248 249 head.add(toolBar, c); 250 backAction.setEnabled(history.hasPrev()); 251 forwardAction.setEnabled(history.hasNext()); 252 253 } 254 255 private void initBody() { 256 body = uif.createPanel("fp.body", false); 257 body.setLayout(new BorderLayout()); 258 259 textArea = new JEditorPane(); 260 textArea.setName("text"); 261 textArea.setEditable(false); 262 textArea.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5)); 263 textArea.addHyperlinkListener(listener); 264 uif.setAccessibleInfo(textArea, "fp"); 265 body.add(uif.createScrollPane(textArea, 266 JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, 267 JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED), 268 BorderLayout.CENTER); 269 } 270 271 //------------------------------------------------------------------------------------ 272 273 private void clear() { 274 if (model != null) 275 model.removeAllElements(); 276 277 history.clear(); 278 backAction.setEnabled(false); 279 forwardAction.setEnabled(false); 280 homeAction.setEnabled(false); 281 282 homeURL = null; 283 currURL = null; 284 textArea.setDocument(new HTMLDocument()); 285 } 286 287 private void loadPage(URL url) { 288 // avoid recursive callbacks from updating combo 289 // URL.equals can result in a big performance hit 290 if (currURL!= null && url.toString().equals(currURL.toString())) 291 return; 292 293 currURL = url; 294 295 String protocol = url.getProtocol(); 296 File file = new File(url.getFile()); 297 if (protocol.equals("file") && file.isDirectory()) { 298 String list = listLocalDirectory(file); 299 HTMLDocument htmlDoc = (HTMLDocument) (htmlKit.createDefaultDocument()); 300 textArea.setDocument(htmlDoc); 301 htmlDoc.setBase(url); 302 textArea.setContentType("text/html"); 303 textArea.setText(list); 304 } 305 else if (protocol.equals("file") 306 && !url.getFile().endsWith(".htm") 307 && !url.getFile().endsWith(".html")) { 308 textArea.setContentType("text/plain"); 309 try { 310 BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream(file), StandardCharsets.UTF_8)); 311 textArea.read(br, url); 312 br.close(); 313 } 314 catch (IOException e) { 315 uif.showError("fp.load.error", new Object[] { url, e }); 316 } 317 318 } 319 else { 320 try { 321 URL loaded = textArea.getPage(); 322 // this next stuff is just to avoid some screen flash if a new doc 323 // is being read 324 if (loaded == null || !loaded.sameFile(url)) { 325 HTMLDocument htmlDoc = (HTMLDocument) (htmlKit.createDefaultDocument()); 326 textArea.setDocument(htmlDoc); 327 } 328 textArea.setPage(url); 329 } 330 catch (IOException e) { 331 uif.showError("fp.load.error", new Object[] { url, e }); 332 } 333 } 334 335 history.add(url); 336 backAction.setEnabled(history.hasPrev()); 337 forwardAction.setEnabled(history.hasNext()); 338 updateCombo(url); 339 } 340 341 private String listLocalDirectory(File dir) { 342 if (!dir.isAbsolute()) 343 dir = dir.getAbsoluteFile(); 344 345 String displayPath = dir.getPath(); 346 // if contains base dir, only show path relative to baseDir 347 if (baseDir != null) { 348 String p = baseDir.getParent(); 349 if (p != null 350 && displayPath.startsWith(p) && 351 (displayPath.length() > p.length())) { 352 displayPath = displayPath.substring(p.length()); 353 // in case of Unix 354 if (displayPath.startsWith(File.separator)) { 355 displayPath = displayPath.substring(1); 356 } 357 } 358 } 359 360 String[] filelist = dir.list(); 361 StringWriter sw = new StringWriter(); 362 try { 363 HTMLWriterEx out = new HTMLWriterEx(sw, uif.getI18NResourceBundle()); 364 365 out.startTag(HTMLWriterEx.HTML); 366 out.startTag(HTMLWriterEx.HEAD); 367 out.writeContentMeta(); 368 out.startTag(HTMLWriterEx.TITLE); 369 out.write(displayPath); 370 out.endTag(HTMLWriterEx.TITLE); 371 out.endTag(HTMLWriterEx.HEAD); 372 out.startTag(HTMLWriterEx.BODY); 373 out.writeStyleAttr("font-family: SansSerif; font-size: 12pt"); 374 out.startTag(HTMLWriterEx.H3); 375 out.writeI18N("fp.head", displayPath); 376 out.endTag(HTMLWriterEx.H3); 377 out.startTag(HTMLWriterEx.UL); 378 out.writeStyleAttr("margin-left:0"); 379 380 File parent = dir.getParentFile(); 381 if (parent != null) { 382 out.startTag(HTMLWriterEx.LI); 383 out.startTag(HTMLWriterEx.OBJECT); 384 out.writeAttr(HTMLWriterEx.CLASSID, "com.sun.javatest.tool.IconLabel"); 385 out.writeParam("type", "up"); 386 out.endTag(HTMLWriterEx.OBJECT); 387 out.writeEntity(" "); 388 try { 389 out.startTag(HTMLWriterEx.A); 390 out.writeAttr(HTMLWriterEx.HREF, parent.toURL().toString()); 391 out.writeI18N("fp.parent"); 392 out.endTag(HTMLWriterEx.A); 393 } 394 catch (MalformedURLException e) { 395 out.writeI18N("fp.parent"); 396 } 397 } 398 399 for (int i = 0; i < filelist.length; i++) { 400 File file = new File(dir, filelist[i]); 401 out.startTag(HTMLWriterEx.LI); 402 out.startTag(HTMLWriterEx.OBJECT); 403 out.writeAttr(HTMLWriterEx.CLASSID, "com.sun.javatest.tool.IconLabel"); 404 out.writeParam("type", (file.isDirectory() ? "folder" : "file")); 405 out.endTag(HTMLWriterEx.OBJECT); 406 out.writeEntity(" "); 407 try { 408 out.writeLink(file.toURL(), file.getName()); 409 } 410 catch (MalformedURLException e) { 411 out.write(file.getName()); 412 } 413 } 414 415 out.endTag(HTMLWriterEx.UL); 416 out.endTag(HTMLWriterEx.BODY); 417 out.endTag(HTMLWriterEx.HTML); 418 out.close(); 419 } 420 catch (IOException e) { 421 // should not happen, writing to StringWriter 422 } 423 424 return sw.toString(); 425 } 426 427 private void updateCombo(URL s) { 428 // check if the new element exists in the combo box... 429 if (model.getIndexOf(s) < 0) 430 model.addElement(s); 431 432 URL item = (URL) selectBox.getSelectedItem(); 433 // check if the new element is already selected. 434 // URL.equals can result in a big performance hit 435 if (s != null && !item.toString().equals(s.toString())) 436 selectBox.setSelectedItem(s); 437 } 438 439 private DefaultComboBoxModel<URL> createModel() { 440 if (model == null) 441 model = new DefaultComboBoxModel<>(); 442 return model; 443 } 444 445 private File baseDir; 446 private URL homeURL; 447 private File[] files; 448 private String textHomePage; 449 450 private JButton homeBtn; 451 private JButton backBtn; 452 private JButton forwardBtn; 453 454 private JComboBox<URL> selectBox; 455 private JPanel head; 456 private JPanel body; 457 private JTextField noteField; 458 459 private HTMLEditorKit htmlKit; 460 private JEditorPane textArea; 461 private URL currURL; 462 463 private History history; 464 465 private DefaultComboBoxModel<URL> model; 466 private Listener listener = new Listener(); 467 private JToolBar toolBar; 468 private UIFactory uif; 469 470 private Action homeAction; 471 private Action backAction; 472 private Action forwardAction; 473 474 private static final int MAX_ROWS_DISPLAY = 20; 475 476 static protected boolean debug = Boolean.getBoolean("debug." + BrowserPane.class.getName()); 477 478 //------------------------------------------------------------------------------------ 479 480 private class History { 481 boolean hasPrev() { 482 return (index > 0); 483 } 484 485 URL prev() { 486 if (index == 0) 487 return null; 488 489 return entries.elementAt(--index); 490 } 491 492 boolean hasNext() { 493 return (index < entries.size() - 1); 494 } 495 496 URL next() { 497 if (index == entries.size() - 1) 498 return null; 499 500 return entries.elementAt(++index); 501 } 502 503 void add(URL u) { 504 if (u == null) 505 throw new NullPointerException(); 506 507 // if there is a current entry, and it matches the one to be added, we're done 508 if (index >= 0 && index < entries.size() && entries.elementAt(index).equals(u)) 509 return; 510 511 // if current entry not the last one, truncate to the current entry 512 if (index < entries.size() - 1) 513 entries.setSize(index + 1); 514 515 // finally, add new entry 516 entries.addElement(u); 517 index = entries.size() - 1; 518 } 519 520 void clear() { 521 entries.setSize(0); 522 index = -1; 523 } 524 525 private Vector<URL> entries = new Vector<>(); 526 private int index; 527 } 528 529 private class Listener implements HyperlinkListener, ItemListener { 530 public void hyperlinkUpdate(HyperlinkEvent e) { 531 HyperlinkEvent.EventType et = e.getEventType(); 532 if (et == HyperlinkEvent.EventType.ACTIVATED) { 533 if (e instanceof HTMLFrameHyperlinkEvent) { 534 HTMLDocument doc = (HTMLDocument) 535 ((JEditorPane) e.getSource()).getDocument(); 536 doc.processHTMLFrameHyperlinkEvent((HTMLFrameHyperlinkEvent) e); 537 } 538 else 539 loadPage(e.getURL()); 540 } 541 else if (et == HyperlinkEvent.EventType.ENTERED) { 542 URL u = e.getURL(); 543 if (u != null) { 544 textArea.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)); 545 noteField.setText(u.toString()); 546 } 547 } 548 else if (et == HyperlinkEvent.EventType.EXITED) { 549 textArea.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); 550 noteField.setText(""); 551 } 552 } 553 554 public void itemStateChanged(ItemEvent e) { 555 if (e.getStateChange() == ItemEvent.SELECTED) { 556 URL url = (URL) e.getItem(); 557 loadPage(url); 558 } 559 } 560 } 561 562 //------------------------------------------------------------------------------------ 563 564 private class Renderer extends DefaultListCellRenderer { 565 public Component getListCellRendererComponent(JList<?> list, Object o, int index, boolean isSelected, boolean cellHasFocus) { 566 String name = null; 567 if (o instanceof URL) { 568 URL url = (URL) o; 569 570 // if not file URL 571 if (!url.getProtocol().equals("file")) { 572 name = url.toString(); 573 } 574 else { 575 // if file URL, remove the "file:" prefix 576 name = extractPrefix(url.toString(), "file:"); 577 String baseName = null; 578 name = new File(name).getAbsolutePath(); 579 if (baseDir != null && baseDir.getParentFile() != null) { 580 baseName = baseDir.getParentFile().getAbsolutePath(); 581 } 582 // if contains base dir, only show file name 583 if (baseName != null && 584 name.startsWith(baseName) && 585 (name.length() > baseName.length())) { 586 name = name.substring(baseName.length() ); 587 // in case of Unix 588 if (name.startsWith(File.separator)) { 589 name = name.substring(1); 590 } 591 } 592 } 593 } else if (o != null) { 594 name = String.valueOf(o); 595 return super.getListCellRendererComponent(list, name, index, isSelected, cellHasFocus); 596 } 597 598 return super.getListCellRendererComponent(list, o, index, isSelected, cellHasFocus); 599 } 600 601 private String extractPrefix(String origStr, String target) { 602 return (!origStr.startsWith(target)) ? origStr : origStr.substring(target.length()); 603 } 604 } 605 }