001: /*
002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
003: *
004: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
005: *
006: * The contents of this file are subject to the terms of either the GNU
007: * General Public License Version 2 only ("GPL") or the Common
008: * Development and Distribution License("CDDL") (collectively, the
009: * "License"). You may not use this file except in compliance with the
010: * License. You can obtain a copy of the License at
011: * http://www.netbeans.org/cddl-gplv2.html
012: * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
013: * specific language governing permissions and limitations under the
014: * License. When distributing the software, include this License Header
015: * Notice in each file and include the License file at
016: * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
017: * particular file as subject to the "Classpath" exception as provided
018: * by Sun in the GPL Version 2 section of the License file that
019: * accompanied this code. If applicable, add the following below the
020: * License Header, with the fields enclosed by brackets [] replaced by
021: * your own identifying information:
022: * "Portions Copyrighted [year] [name of copyright owner]"
023: *
024: * Contributor(s):
025: *
026: * The Original Software is NetBeans. The Initial Developer of the Original
027: * Software is Sun Microsystems, Inc. Portions Copyright 1997-2007 Sun
028: * Microsystems, Inc. All Rights Reserved.
029: *
030: * If you wish your version of this file to be governed by only the CDDL
031: * or only the GPL Version 2, indicate your decision by adding
032: * "[Contributor] elects to include this software in this distribution
033: * under the [CDDL or GPL Version 2] license." If you do not indicate a
034: * single choice of license, a recipient has the option to distribute
035: * your version of this file under either the CDDL, the GPL Version 2 or
036: * to extend the choice of license to its licensees as provided above.
037: * However, if you add GPL Version 2 code and therefore, elected the GPL
038: * Version 2 license, then the option applies only if the new code is
039: * made subject to such option by the copyright holder.
040: */
041: package org.netbeans.modules.visualweb.designer.jsf.ui;
042:
043: import org.netbeans.modules.visualweb.api.designer.markup.MarkupService;
044: import org.netbeans.modules.visualweb.insync.Util;
045: import java.awt.Color;
046: import java.awt.Component;
047: import java.awt.Dialog;
048: import java.awt.Font;
049: import java.awt.FontMetrics;
050: import java.awt.Graphics;
051: import java.awt.Point;
052: import java.awt.Rectangle;
053: import java.awt.event.ActionEvent;
054: import java.awt.event.ActionListener;
055: import java.awt.event.MouseAdapter;
056: import java.awt.event.MouseEvent;
057:
058: import java.io.ByteArrayInputStream;
059: import java.io.IOException;
060: import java.io.InputStream;
061: import java.io.OutputStream;
062: import java.io.StringBufferInputStream;
063: import java.io.StringReader;
064:
065: import javax.swing.DefaultListCellRenderer;
066: import javax.swing.DefaultListModel;
067: import javax.swing.JLabel;
068: import javax.swing.JList;
069: import javax.swing.JPanel;
070: import javax.swing.UIManager;
071: import javax.swing.plaf.metal.MetalLabelUI;
072: import javax.swing.text.AbstractDocument;
073: import javax.swing.text.BadLocationException;
074: import javax.swing.text.Document;
075: import javax.swing.text.StyledDocument;
076:
077: import javax.xml.parsers.DocumentBuilder;
078:
079: import org.netbeans.api.diff.Diff;
080: import org.netbeans.editor.BaseDocument;
081: import org.netbeans.modules.visualweb.designer.jsf.JsfForm;
082:
083: import org.openide.DialogDescriptor;
084: import org.openide.DialogDisplayer;
085: import org.openide.ErrorManager;
086: import org.openide.NotifyDescriptor;
087: import org.openide.cookies.LineCookie;
088: import org.openide.filesystems.FileObject;
089: import org.openide.loaders.DataObject;
090: import org.openide.loaders.DataObjectNotFoundException;
091: import org.openide.text.Line;
092: import org.openide.util.NbBundle;
093:
094: import org.w3c.tidy.Tidy;
095:
096: import org.xml.sax.EntityResolver;
097:
098: import org.netbeans.modules.visualweb.insync.ParserAnnotation;
099: import org.netbeans.modules.visualweb.insync.models.FacesModel;
100:
101: // XXX Moved from designer/ErrorPanel.
102:
103: // XXX Moved from designer/ErrorPanel.
104: /**
105: * Panel which shows an error label, and a listbox containing errors
106: *
107: * @todo Unify this code with the code in ImportPagePanel such that JSP tag
108: * handling etc. is handled correctly in both places. There's already some
109: * deviation in what these do.
110: * @todo Trap the case where conversion fails, and tell the user that it failed,
111: * in addition to graying out the buttons.
112: *
113: * @author Tor Norbye
114: */
115: public class ErrorPanelImpl extends JPanel implements
116: JsfForm.ErrorPanel {
117: private ParserAnnotation[] errors;
118: private DefaultListModel model;
119: // private WebForm webform;
120: private final FacesModel facesModel;
121: private final JsfForm.ErrorPanelCallback errorPanelCallback;
122: private ParserAnnotation activated = null;
123:
124: // Variables declaration - do not modify//GEN-BEGIN:variables
125: private javax.swing.JButton convertButton;
126: private javax.swing.JList errorList;
127: private javax.swing.JLabel jLabel3;
128: private javax.swing.JPanel jPanel1;
129: private javax.swing.JPanel jPanel2;
130: private javax.swing.JScrollPane listScrollPane;
131: private javax.swing.JButton previewButton;
132: private javax.swing.JTextArea textArea;
133:
134: // End of variables declaration//GEN-END:variables
135:
136: /** Creates new form ErrorPanel */
137: public ErrorPanelImpl(/*WebForm webform,*/FacesModel facesModel,
138: final ParserAnnotation[] errors,
139: JsfForm.ErrorPanelCallback errorPanelCallback) {
140: // if(DesignerUtils.DEBUG) {
141: // DesignerUtils.debugLog(getClass().getName() + "()");
142: // }
143: // if(webform == null) {
144: // throw(new IllegalArgumentException("Null webform."));
145: if (facesModel == null) {
146: throw (new IllegalArgumentException("Null FacesModel."));
147: }
148: if (errors == null) {
149: throw (new IllegalArgumentException("Null errors array."));
150: }
151: if (errorPanelCallback == null) {
152: throw new NullPointerException("Null ErrorPanelCallback!"); // NOI18N
153: }
154: this .errors = errors;
155: // this.webform = webform;
156: this .facesModel = facesModel;
157: this .errorPanelCallback = errorPanelCallback;
158: initComponents();
159: updateErrors();
160:
161: // XXX #100175 Do not hardcode font sizes.
162: // But how to provide larger font nicely?
163: Font titleFont = jLabel3.getFont();
164: if (titleFont != null) {
165: int size = titleFont.getSize();
166: float newSize = 2 * size;
167: Font newFont = titleFont.deriveFont(newSize);
168: jLabel3.setFont(newFont);
169: }
170:
171: convertButton.setText(NbBundle.getMessage(ErrorPanelImpl.class,
172: "Convert"));
173: previewButton.setText(NbBundle.getMessage(ErrorPanelImpl.class,
174: "Preview"));
175: convertButton.addActionListener(new ActionListener() {
176: public void actionPerformed(ActionEvent e) {
177: // convertToXHTML(ErrorPanel.this.webform);
178: convertToXHTML(ErrorPanelImpl.this .facesModel);
179: // XXX See uncommented line at the end of convertToXHTML.
180: ErrorPanelImpl.this .errorPanelCallback
181: .updateTopComponentForErrors();
182: }
183: });
184: previewButton.addActionListener(new ActionListener() {
185: public void actionPerformed(ActionEvent e) {
186: // preview(ErrorPanel.this.webform);
187: preview(ErrorPanelImpl.this .facesModel);
188: }
189: });
190:
191: // XXX what about previewButton?
192: textArea.setEnabled(false);
193:
194: //textArea.setColor(Color.BLACK);
195: textArea.setDisabledTextColor(Color.BLACK);
196: textArea.setFont((Font) UIManager.getDefaults().get(
197: "Label.font"));
198:
199: listScrollPane.setBorder(null); // no border around the error box
200: errorList.setCellRenderer(new ErrorCellRenderer());
201: errorList.addMouseListener(new MouseAdapter() {
202: private ParserAnnotation getSelected(MouseEvent e) {
203: Point p = e.getPoint();
204: int index = errorList.locationToIndex(p);
205: Rectangle bounds = errorList
206: .getCellBounds(index, index);
207:
208: if ((bounds != null) && bounds.contains(p)) {
209: return errors[index];
210: } else {
211: return null;
212: }
213: }
214:
215: public void mousePressed(MouseEvent e) {
216: // "Activate" hyperlink - make it render highlighted
217: activated = getSelected(e);
218: errorList.repaint();
219: }
220:
221: public void mouseReleased(MouseEvent e) {
222: // "Deactivate" hyperlink
223: activated = null;
224: errorList.repaint();
225: }
226:
227: public void mouseClicked(MouseEvent e) {
228: ParserAnnotation selected = getSelected(e);
229:
230: if (selected == null) {
231: return;
232: }
233:
234: FileObject fo = selected.getFileObject();
235: DataObject dobj;
236:
237: try {
238: dobj = DataObject.find(fo);
239: } catch (DataObjectNotFoundException ex) {
240: return;
241: }
242:
243: /* This workaround is hopefully not necessary
244: anymore now that the window system natively
245: supports multiview. This was necessary with
246: our own window manager to ensure that the
247: Source tab was shown.
248: WebForm webform = Utilities.getWebForm(dobj, true);
249: if (webform != null) {
250: TopComponent tc = webform.getSourceView();
251: if (tc != null) {
252: tc.requestActive();
253: }
254: }
255: */
256: LineCookie lc = (LineCookie) dobj
257: .getCookie(LineCookie.class);
258:
259: if (lc != null) {
260: Line.Set ls = lc.getLineSet();
261:
262: if (ls != null) {
263: // -1: convert line numbers to be zero-based
264: Line line = ls
265: .getCurrent(selected.getLine() - 1);
266: line.show(Line.SHOW_GOTO);
267: }
268: }
269: }
270: });
271: }
272:
273: public void updateErrors() {
274: textArea.setText(NbBundle.getMessage(ErrorPanelImpl.class,
275: "ErrorDescription"));
276:
277: // FacesModel model = webform.getModel();
278: FacesModel model = facesModel;
279: errors = model.getErrors();
280: DefaultListModel listModel = new DefaultListModel();
281:
282: boolean haveJspError = false;
283: for (int i = 0; i < errors.length; i++) {
284: listModel.addElement(errors[i]);
285: if (errors[i].getFileObject() != null) {
286: String extension = errors[i].getFileObject().getExt();
287: if (extension != null && extension.indexOf("jsp") != -1) { // includes jspf
288: haveJspError = true;
289: }
290: }
291: }
292: convertButton.setEnabled(haveJspError);
293: previewButton.setEnabled(haveJspError);
294:
295: this .model = listModel;
296: errorList.setModel(this .model);
297: }
298:
299: /** This method is called from within the constructor to
300: * initialize the form.
301: * WARNING: Do NOT modify this code. The content of this method is
302: * always regenerated by the NetBeans Form Editor.
303: */
304: // <editor-fold defaultstate="collapsed" desc=" Generated Code ">//GEN-BEGIN:initComponents
305: private void initComponents() {
306: java.awt.GridBagConstraints gridBagConstraints;
307:
308: jPanel2 = new javax.swing.JPanel();
309: jLabel3 = new javax.swing.JLabel();
310: textArea = new javax.swing.JTextArea();
311: convertButton = new javax.swing.JButton();
312: previewButton = new javax.swing.JButton();
313: jPanel1 = new javax.swing.JPanel();
314: listScrollPane = new javax.swing.JScrollPane();
315: errorList = new javax.swing.JList();
316:
317: setBackground(javax.swing.UIManager.getDefaults().getColor(
318: "TextArea.background"));
319: setLayout(new java.awt.GridBagLayout());
320:
321: jPanel2.setBackground(java.awt.Color.red);
322:
323: jLabel3.setForeground(java.awt.Color.white);
324: jLabel3.setText(NbBundle.getMessage(ErrorPanelImpl.class,
325: "SourceFileError")); // NOI18N
326: jPanel2.add(jLabel3);
327:
328: gridBagConstraints = new java.awt.GridBagConstraints();
329: gridBagConstraints.gridwidth = java.awt.GridBagConstraints.REMAINDER;
330: gridBagConstraints.fill = java.awt.GridBagConstraints.BOTH;
331: gridBagConstraints.weightx = 1.0;
332: add(jPanel2, gridBagConstraints);
333:
334: textArea.setLineWrap(true);
335: textArea.setWrapStyleWord(true);
336: gridBagConstraints = new java.awt.GridBagConstraints();
337: gridBagConstraints.gridwidth = java.awt.GridBagConstraints.REMAINDER;
338: gridBagConstraints.fill = java.awt.GridBagConstraints.BOTH;
339: gridBagConstraints.weightx = 1.0;
340: gridBagConstraints.insets = new java.awt.Insets(12, 12, 11, 11);
341: add(textArea, gridBagConstraints);
342: gridBagConstraints = new java.awt.GridBagConstraints();
343: gridBagConstraints.anchor = java.awt.GridBagConstraints.WEST;
344: gridBagConstraints.insets = new java.awt.Insets(0, 12, 12, 11);
345: add(convertButton, gridBagConstraints);
346: gridBagConstraints = new java.awt.GridBagConstraints();
347: gridBagConstraints.gridwidth = java.awt.GridBagConstraints.REMAINDER;
348: gridBagConstraints.anchor = java.awt.GridBagConstraints.WEST;
349: gridBagConstraints.insets = new java.awt.Insets(0, 12, 12, 11);
350: add(previewButton, gridBagConstraints);
351:
352: jPanel1.setBackground(java.awt.Color.white);
353: gridBagConstraints = new java.awt.GridBagConstraints();
354: gridBagConstraints.gridwidth = java.awt.GridBagConstraints.REMAINDER;
355: gridBagConstraints.weighty = 1.0;
356: add(jPanel1, gridBagConstraints);
357:
358: listScrollPane.setBackground(javax.swing.UIManager
359: .getDefaults().getColor("TextArea.background"));
360: listScrollPane.setViewportView(errorList);
361:
362: gridBagConstraints = new java.awt.GridBagConstraints();
363: gridBagConstraints.gridwidth = java.awt.GridBagConstraints.REMAINDER;
364: gridBagConstraints.fill = java.awt.GridBagConstraints.BOTH;
365: gridBagConstraints.weightx = 1.0;
366: gridBagConstraints.weighty = 1.0;
367: gridBagConstraints.insets = new java.awt.Insets(0, 12, 11, 11);
368: add(listScrollPane, gridBagConstraints);
369: }// </editor-fold>//GEN-END:initComponents
370:
371: private static void convertToXHTML(
372: /*WebForm webform*/FacesModel facesModel) {
373: // FacesModel model = webform.getModel();
374: FacesModel model = facesModel;
375: model.flush();
376:
377: StyledDocument doc = model.getMarkupUnit().getSourceDocument();
378:
379: if (doc == null) {
380: return;
381: }
382:
383: Tidy tidy = getTidy();
384:
385: /*
386: boolean confirmed = confirmConvert(tidy, doc, webform.getDataObject());
387: if (!confirmed) {
388: return;
389: }
390: */
391: String rewritten = rewrite(tidy, doc);
392:
393: if ((rewritten == null) || (rewritten.length() == 0)) {
394: return;
395: }
396:
397: try {
398: // Lock the document atomically
399: // TODO: BaseDocument.replace() should do this automatically;
400: if (doc instanceof BaseDocument) {
401: ((BaseDocument) doc).atomicLock();
402: }
403:
404: if (doc instanceof AbstractDocument) {
405: ((AbstractDocument) doc).replace(0, doc.getLength(),
406: rewritten, null);
407: } else {
408: doc.remove(0, doc.getLength());
409: doc.insertString(0, rewritten, null);
410: }
411: } catch (BadLocationException e) {
412: ErrorManager.getDefault().notify(e);
413: } finally {
414: // Lock the document atomically
415: if (doc instanceof BaseDocument) {
416: ((BaseDocument) doc).atomicUnlock();
417: }
418: }
419:
420: model.sync();
421:
422: // XXX See after the only usage of this method.
423: // webform.getTopComponent().updateErrors();
424: }
425:
426: private static String rewrite(Tidy tidy, Document doc) {
427: InputStream input = null;
428:
429: try {
430: String text = doc.getText(0, doc.getLength());
431: input = new StringBufferInputStream(text);
432: } catch (BadLocationException e) {
433: ErrorManager.getDefault().notify(ErrorManager.WARNING, e);
434:
435: return "";
436: }
437:
438: StringBuffer sb = new StringBuffer(doc.getLength() + 500);
439:
440: //OutputStream output = new StringBufferOutputStream(sb);
441: OutputStream output = new StringBufferOutputStream(sb);
442:
443: // For some reason, just setting the output writer here
444: // causes the output window to open! It should be delayed until
445: // there's output activity!
446: // Write the error message to the output tab:
447: // Add to bundle:
448: // HtmlParseError=Parse Errors
449: //InputOutput io = IOProvider.getDefault().getIO(NbBundle.getMessage(ErrorPanel.class, "HtmlParseError"), false);
450: //OutputWriter errors = io.getOut();
451: //tidy.setErrout(errors);
452: // Unfortunately we can't just do Tidy.parseDOM, then call the
453: // XMLSerializer on this DOM because the serializer chokes - I haven't
454: // debugged it but I suspect the Tidy dom has issues. I noticed the
455: // same thing when trying to call importNode on tidy nodes (see
456: // ImportPagePanel for details)
457: //org.w3c.dom.Document document = tidy.parseDOM(input, output);
458: //if (document == null) {
459: // return "";
460: //} else {
461: // So instead we tidy out to text, then reparse with xerces and
462: // serialize that!
463: boolean escape = tidy.getConfiguration().outputJspMode
464: && !tidy.getConfiguration().inputJspMode;
465: tidy.parse(new Tidy.EntityWrapperInputStream(input),
466: new Tidy.EntityWrapperOutputStream(output, escape));
467:
468: String tidied = sb.toString();
469:
470: if (tidied.length() == 0) {
471: return tidied;
472: } else {
473: org.w3c.dom.Document xercesDom = null;
474:
475: try {
476: org.xml.sax.InputSource is2 = new org.xml.sax.InputSource(
477: new StringReader(tidied));
478:
479: // We won't keep this DOM so there's no reason to enable
480: // CSS on it
481: boolean css = false;
482: DocumentBuilder parser = MarkupService
483: .createRaveSourceDocumentBuilder(css);
484:
485: parser.setEntityResolver(new EntityResolver() {
486: public org.xml.sax.InputSource resolveEntity(
487: String pubid, String sysid) {
488: return new org.xml.sax.InputSource(
489: new ByteArrayInputStream(new byte[0]));
490: }
491: });
492:
493: xercesDom = parser.parse(is2);
494: } catch (java.io.IOException e) {
495: ErrorManager.getDefault().notify(
496: ErrorManager.INFORMATIONAL, e);
497:
498: return null;
499: } catch (org.xml.sax.SAXException e) {
500: ErrorManager.getDefault().notify(
501: ErrorManager.INFORMATIONAL, e);
502:
503: return null;
504: } catch (javax.xml.parsers.ParserConfigurationException e) {
505: ErrorManager.getDefault().notify(
506: ErrorManager.INFORMATIONAL, e);
507:
508: return null;
509: }
510:
511: if (xercesDom == null) {
512: return "";
513: }
514:
515: // String result = InSyncService.getProvider().getHtmlStream(xercesDom);
516: String result = Util.getHtmlStream(xercesDom);
517:
518: return result;
519: }
520: }
521:
522: // XXX Copy from ImportPagePanel (now in project/importpage module).
523: private static class StringBufferOutputStream extends OutputStream {
524: private StringBuffer sb;
525:
526: public StringBufferOutputStream(StringBuffer sb) {
527: this .sb = sb;
528: }
529:
530: public void write(int b) {
531: sb.append((char) b);
532: }
533: }
534:
535: private static void preview(
536: /*WebForm webform*/FacesModel facesModel) {
537: // FacesModel model = webform.getModel();
538: FacesModel model = facesModel;
539: model.flush();
540:
541: StyledDocument doc = model.getMarkupUnit().getSourceDocument();
542:
543: if (doc == null) {
544: return;
545: }
546:
547: Tidy tidy = getTidy();
548: // diff(tidy, doc, webform.getDataObject());
549: // See DomProviderImpl.getJspDataObject();
550: FileObject file = model.getMarkupFile();
551: DataObject dobj;
552: try {
553: dobj = DataObject.find(file);
554: } catch (DataObjectNotFoundException ex) {
555: ErrorManager.getDefault().notify(
556: ErrorManager.INFORMATIONAL, ex);
557: dobj = null;
558: }
559: diff(tidy, doc, dobj);
560: }
561:
562: /** Preview the converted source, and return true if the user
563: * wants to continue, false otherwise. */
564:
565: //private boolean confirmConvert(Tidy tidy, Document doc, DataObject dobj) {
566: private static boolean diff(Tidy tidy, Document doc, DataObject dobj) {
567: String before;
568:
569: try {
570: before = doc.getText(0, doc.getLength());
571: } catch (BadLocationException e) {
572: ErrorManager.getDefault().notify(ErrorManager.WARNING, e);
573:
574: return false;
575: }
576:
577: String after = rewrite(tidy, doc);
578: String mime = dobj.getPrimaryFile().getMIMEType();
579:
580: if ((after == null) || (after.length() == 0)) {
581: ErrorManager.getDefault().log(
582: "Somehow got empty diff output");
583:
584: return false;
585: }
586:
587: Diff diff = Diff.getDefault();
588:
589: if (diff == null) {
590: // TODO Check for this condition and hide the Diff button
591: // if this is the case
592: return false;
593: }
594:
595: String beforeDesc = NbBundle.getMessage(ErrorPanelImpl.class,
596: "DiffBefore"); // NOI18N
597: String afterDesc = NbBundle.getMessage(ErrorPanelImpl.class,
598: "DiffAfter"); // NOI18N
599: String beforeTitle = beforeDesc;
600: String afterTitle = afterDesc;
601:
602: Component tp = null;
603:
604: try {
605: tp = diff.createDiff(beforeDesc, beforeTitle,
606: new StringReader(before), afterDesc, afterTitle,
607: new StringReader(after), mime);
608: } catch (IOException ioex) {
609: ErrorManager.getDefault().notify(ioex);
610:
611: return false;
612: }
613:
614: if (tp == null) {
615: return false;
616: }
617:
618: //NotifyDescriptor d =
619: // new NotifyDescriptor.Message("Hello...", NotifyDescriptor.INFORMATION_MESSAGE);
620: // TopManager.getDefault().notify(d);
621: DialogDescriptor d = new DialogDescriptor(tp, NbBundle
622: .getMessage(ErrorPanelImpl.class, "TITLE_diff")); // NOI18N
623: d.setModal(true);
624: d.setMessageType(NotifyDescriptor.PLAIN_MESSAGE);
625: d.setOptionType(NotifyDescriptor.OK_CANCEL_OPTION);
626:
627: Dialog dlg = DialogDisplayer.getDefault().createDialog(d);
628: dlg.pack();
629: // dlg.show();
630: dlg.setVisible(true);
631:
632: // Do OK/Cancel thingy here
633: return d.getValue() == NotifyDescriptor.OK_OPTION;
634: }
635:
636: private static Tidy getTidy() {
637: // Set configuration settings
638: Tidy tidy = new Tidy();
639: tidy.getConfiguration().outputJspMode = true;
640: tidy.getConfiguration().inputJspMode = true;
641: tidy.setOnlyErrors(false);
642: tidy.setShowWarnings(false);
643: tidy.setQuiet(true);
644:
645: // XXX Apparently JSP pages (at least those involving
646: // JSF) need XML handling in order for JTidy not to choke on them
647: //tidy.setXmlTags(true);
648: tidy.setXmlTags(false);
649:
650: tidy.setXHTML(true); // XXX ?
651:
652: //tidy.setMakeClean(panel.getReplace());
653: //tidy.setIndentContent(panel.getIndent());
654: //tidy.setSmartIndent(panel.getIndent());
655: //tidy.setUpperCaseTags(panel.getUpper());
656: //tidy.setHideEndTags(panel.getOmit());
657: //tidy.setWraplen(panel.getWrapCol());
658: return tidy;
659: }
660:
661: // Give the errors in the list a "hyperlink" look
662: private class ErrorCellRenderer extends DefaultListCellRenderer {
663: ErrorCellRenderer() {
664: setUI(new HyperlinkLabelUI());
665: }
666:
667: public Component getListCellRendererComponent(JList list,
668: Object value, int index, boolean isSelected,
669: boolean cellHasFocus) {
670: cellHasFocus = false; // no focus-feedback for the hyperlink
671:
672: Component c = super .getListCellRendererComponent(list,
673: value, index, isSelected, cellHasFocus);
674: assert c == this ; // if not, gotta cast and call the below methods on c.
675:
676: if (value instanceof ParserAnnotation) {
677: ParserAnnotation p = (ParserAnnotation) value;
678: String filename = p.getFileObject().getNameExt();
679: int baseIndex = filename.lastIndexOf('/');
680: if (baseIndex != -1) {
681: filename = filename.substring(baseIndex);
682: }
683: setText(NbBundle.getMessage(ErrorPanelImpl.class,
684: "LineFormat", filename, Integer.toString(p
685: .getLine()), p.getMessage()));
686:
687: if (p.getIcon() != null) {
688: setIcon(p.getIcon());
689: }
690:
691: if (p == activated) {
692: c.setForeground(Color.RED);
693: } else {
694: c.setForeground(Color.BLUE);
695: }
696:
697: setBackground(list.getBackground());
698: }
699:
700: return c;
701: }
702: }
703:
704: // Paint underlined labels
705: private static final class HyperlinkLabelUI extends MetalLabelUI {
706: protected void paintEnabledText(JLabel l, Graphics g,
707: String text, int textX, int textY) {
708: super .paintEnabledText(l, g, text, textX, textY);
709:
710: FontMetrics fm = g.getFontMetrics();
711: int underlineRectX = textX;
712: int underlineRectY = textY;
713: int underlineRectWidth = fm.stringWidth(text);
714: int underlineRectHeight = 1;
715: g.fillRect(underlineRectX, underlineRectY + 1,
716: underlineRectWidth, underlineRectHeight);
717: }
718: }
719:
720: /*
721: // Grr... tidy uses input/output stream instead of input/output writer
722: private class StringBufferOutputStream extends OutputStream {
723: private StringBuffer sb;
724: StringBufferOutputStream(StringBuffer sb) {
725: this.sb = sb;
726: }
727:
728: public void write(int b) {
729: sb.append((char)b);
730: }
731: }
732: */
733: }
|