QedeqPane.java
001 /* This file is part of the project "Hilbert II" - http://www.qedeq.org
002  *
003  * Copyright 2000-2013,  Michael Meyling <mime@qedeq.org>.
004  *
005  * "Hilbert II" is free software; you can redistribute
006  * it and/or modify it under the terms of the GNU General Public
007  * License as published by the Free Software Foundation; either
008  * version 2 of the License, or (at your option) any later version.
009  *
010  * This program is distributed in the hope that it will be useful,
011  * but WITHOUT ANY WARRANTY; without even the implied warranty of
012  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
013  * GNU General Public License for more details.
014  */
015 
016 package org.qedeq.gui.se.pane;
017 
018 import java.awt.BorderLayout;
019 import java.awt.event.MouseEvent;
020 import java.io.IOException;
021 import java.util.List;
022 
023 import javax.swing.BorderFactory;
024 import javax.swing.JPanel;
025 import javax.swing.JScrollPane;
026 import javax.swing.JTextArea;
027 import javax.swing.JViewport;
028 import javax.swing.SwingUtilities;
029 import javax.swing.UIManager;
030 import javax.swing.plaf.TextUI;
031 import javax.swing.text.BadLocationException;
032 
033 import org.qedeq.base.io.SourceArea;
034 import org.qedeq.base.trace.Trace;
035 import org.qedeq.base.utility.EqualsUtility;
036 import org.qedeq.gui.se.control.SelectionListener;
037 import org.qedeq.gui.se.element.CPTextArea;
038 import org.qedeq.gui.se.util.CurrentLineHighlighterUtility;
039 import org.qedeq.gui.se.util.DocumentMarker;
040 import org.qedeq.gui.se.util.DocumentMarkerPainter;
041 import org.qedeq.gui.se.util.GuiHelper;
042 import org.qedeq.kernel.bo.KernelContext;
043 import org.qedeq.kernel.bo.common.QedeqBo;
044 import org.qedeq.kernel.se.common.SourceFileException;
045 import org.qedeq.kernel.se.common.SourceFileExceptionList;
046 
047 /**
048  * View source of QEDEQ module.
049  *
050  @author  Michael Meyling
051  */
052 
053 public class QedeqPane extends JPanel implements SelectionListener {
054 
055     /** This class. */
056     private static final Class CLASS = QedeqPane.class;
057 
058     /** Reference to module properties. */
059     private QedeqBo prop;
060 
061     /** Here is the QEDEQ module source. */
062     private JTextArea qedeq = new CPTextArea(false) {
063         public String getToolTipText(final MouseEvent e) {
064             if (errorMarker == null || warningMarker == null) {
065                 setToolTipText(null);
066                 return super.getToolTipText();
067             }
068             // --- determine locations ---
069             TextUI mapper = qedeq.getUI();
070             final int i = mapper.viewToModel(qedeq, e.getPoint());
071             final List errNos =  errorMarker.getBlockNumbersForOffset(i);
072             final List warningNos =  warningMarker.getBlockNumbersForOffset(i);
073             if (errNos.size() == && warningNos.size() == 0) {
074                 setToolTipText(null);
075                 return super.getToolTipText(e);
076             }
077             final StringBuffer buffer = new StringBuffer();
078             for (int j = 0; j < errNos.size(); j++) {
079                 if (j > 0) {
080                     buffer.append("\n");
081                 }
082                 buffer.append((prop.getErrors().get(((IntegererrNos.get(j)).intValue())
083                     .getMessage()));
084             }
085             for (int j = 0; j < warningNos.size(); j++) {
086                 if (j > 0) {
087                     buffer.append("\n");
088                 }
089                 buffer.append((prop.getWarnings().get(((IntegerwarningNos.get(j)).intValue())
090                     .getMessage()));
091             }
092             setToolTipText(GuiHelper.getToolTipText(buffer.toString()));
093             return getToolTipText();
094         }
095     };
096 
097     /** Our error highlighter for the text areas. */
098     private DocumentMarker errorMarker;
099 
100     /** Our warning highlighter for the text areas. */
101     private DocumentMarker warningMarker;
102 
103     /**
104      * Creates new Panel.
105      */
106     public QedeqPane() {
107         super(false);
108         this.prop = null;
109         setupView();
110         updateView();
111     }
112 
113     /**
114      * Assembles the GUI components of the panel.
115      */
116     public void setupView() {
117         final JScrollPane scroller = new JScrollPane();
118         final JViewport vp = scroller.getViewport();
119         vp.add(qedeq);
120         this.setLayout(new BorderLayout(00));
121         this.add(scroller);
122         setBorder(BorderFactory.createEmptyBorder());
123         qedeq.setEditable(false);
124         qedeq.setToolTipText("");
125         qedeq.putClientProperty("JTextArea.infoBackground", Boolean.TRUE);
126         qedeq.setLineWrap(true)// TODO mime 20080509: make this configurable
127     }
128 
129     /**
130      * Set new model. To make the new model visible {@link #updateView} must be called.
131      *
132      @param   prop    New QEDEQ module.
133      */
134     public synchronized void setModel(final QedeqBo prop) {
135         Trace.trace(CLASS, this, "setModel", prop);
136         if (!EqualsUtility.equals(this.prop, prop)) {
137             this.prop = prop;
138             Runnable setModel = new Runnable() {
139                 public void run() {
140                     updateView();
141                 }
142             };
143             SwingUtilities.invokeLater(setModel);
144         }
145     }
146 
147 
148     public void setLineWrap(final boolean wrap) {
149         qedeq.setLineWrap(wrap);
150     }
151 
152     public boolean getLineWrap() {
153         return qedeq.getLineWrap();
154     }
155 
156     /**
157      * Update from model.
158      */
159     public synchronized void updateView() {
160         Trace.begin(CLASS, this, "updateView");
161         if (prop != null) {
162             try {
163                 qedeq.setText(KernelContext.getInstance().getSource(prop.getModuleAddress()));
164                 CurrentLineHighlighterUtility.install(qedeq);
165                 qedeq.setLineWrap(getLineWrap());
166                 if (prop.getModuleAddress().isFileAddress()) {
167                     qedeq.setEditable(true);
168                 else {
169                     // TODO 20100319 m31: show "readonly" as label somewhere
170                     qedeq.setEditable(false);
171                 }
172                 // we want the background be same even if area is not editable
173                 qedeq.setBackground(UIManager.getColor("TextArea.background"));
174                 qedeq.setCaretPosition(0);
175                 qedeq.getCaret().setSelectionVisible(true);
176 
177                 // TODO m31 20100707: duplicate code, refactor
178                 warningMarker = new DocumentMarker(qedeq, new DocumentMarkerPainter(
179                         GuiHelper.getWarningTextBackgroundColor()));
180                 final SourceFileExceptionList pw = prop.getWarnings();
181                 if (pw != null) {
182                     for (int i = 0; i < pw.size(); i++) {
183                         if (pw.get(i).getSourceArea() != null) {
184                             try {
185                                 final SourceArea sa = pw.get(i).getSourceArea();
186                                 if (sa != null) {
187                                     final int from = sa.getStartPosition().getRow() 1;
188                                     int to = sa.getEndPosition().getRow() 1;
189 // for line marking only:
190 //                                    warningMarker.addWarningLines(from, to,
191 //                                        sa.getStartPosition().getColumn() - 1);
192                                     warningMarker.addMarkedBlock(from,
193                                         sa.getStartPosition().getColumn() 1,
194                                         to, sa.getEndPosition().getColumn() 1);
195                                     continue;
196                                 }
197                             catch (BadLocationException e) {  // should not occur
198                                 Trace.fatal(CLASS, this, "updateView""Programming error?", e);
199                             }
200                         else {
201                             warningMarker.addEmptyBlock();
202                         }
203                     }
204                 }
205                 errorMarker = new DocumentMarker(qedeq, new DocumentMarkerPainter(
206                     GuiHelper.getErrorTextBackgroundColor()));
207                 final SourceFileExceptionList pe = prop.getErrors();
208                 if (pe != null) {
209                     for (int i = 0; i < pe.size(); i++) {
210                         if (pe.get(i).getSourceArea() != null) {
211                             try {
212                                 final SourceArea sa = pe.get(i).getSourceArea();
213                                 if (sa != null) {
214                                     final int from = sa.getStartPosition().getRow() 1;
215                                     int to = sa.getEndPosition().getRow() 1;
216 // for line marking only:
217 //                                    errorMarker.addMarkedLines(from, to,
218 //                                        sa.getStartPosition().getColumn() - 1);
219                                     errorMarker.addMarkedBlock(from,
220                                         sa.getStartPosition().getColumn() 1,
221                                         to, sa.getEndPosition().getColumn() 1);
222                                     continue;
223                                 }
224                             catch (BadLocationException e) {  // should not occur
225                                 Trace.fatal(CLASS, this, "updateView""Programming error?", e);
226                             }
227                         else {
228                             errorMarker.addEmptyBlock();
229                         }
230                     }
231                 }
232                 Trace.trace(CLASS, this, "updateView""Text updated");
233             catch (IOException ioException) {
234                 qedeq.setEditable(false);
235                 qedeq.setText("");
236                 Trace.trace(CLASS, this, "updateView", ioException);
237             }
238         else {
239             errorMarker = null;
240             warningMarker = null;
241             CurrentLineHighlighterUtility.uninstall(qedeq);
242             qedeq.setEditable(false);
243             qedeq.setText("");
244             Trace.end(CLASS, this, "updateView");
245         }
246         this.repaint();
247     }
248 
249 
250     /**
251      * Get QEDEQ text, if QEDEQ text is editable.
252      *
253      @return  QEDEQ text source.
254      @throws  IllegalStateException   QEDEQ text is not editable.
255      */
256     public final String getEditedQedeq() {
257         if (qedeq.isEditable()) {
258             return this.qedeq.getText();
259         else {
260             throw new IllegalStateException("no editable QEDEQ text");
261         }
262     }
263 
264 
265     /**
266      * Was the content changed?
267      *
268      @return  content modified?
269      */
270     public final boolean isContentChanged() {
271         return !this.qedeq.isEditable();
272     }
273 
274 
275 // LATER m31 20100830: not used any longer
276 /*
277     private final int findCaretPosition(final int i) {
278         if (i == 1) {
279             return 0;
280         }
281         final String s = qedeq.getText();
282         int j = 0;
283         int k = 0;
284         for (; j < s.length(); j++) {
285             if (s.charAt(j) == '\n') {
286                 k++;
287             }
288             if (k == i - 1) {
289                 return j + 1;
290             }
291         }
292         return 0;
293     }
294 
295     private final void highlightLine(final int line) {
296         Trace.param(CLASS, this, "highlightLine", "line", line);
297         int j;
298         try {
299             j = qedeq.getLineStartOffset(line - 1);
300         } catch (BadLocationException e) {
301             Trace.trace(CLASS, this, "highlightLine", e);
302             j = 0;
303         }
304         int k;
305         try {
306             k = qedeq.getLineEndOffset(line - 1);
307         } catch (BadLocationException e) {
308             Trace.trace(CLASS, this, "highlightLine", e);
309             k = qedeq.getText().length();
310         }
311         Trace.trace(CLASS, this, "highlightLine", "from " + j + " to " + k);
312 
313         qedeq.setCaretPosition(j);
314         qedeq.moveCaretPosition(k);
315     }
316 
317 //    public void selectError(final int number) {
318 //        if (prop != null && prop.getException() != null && number < prop.getException().size()) {
319 //            final SourceFileException sfe = prop.getException().get(number);
320 //            if (sfe.getSourceArea() != null && sfe.getSourceArea().getStartPosition() != null) {
321 //                highlightLine(sfe.getSourceArea().getStartPosition().getLine());
322 //            }
323 //        }
324 //    }
325 */
326 
327     /**
328      * Jump to error location. Uses only <code>error</code> to select correct marker.
329      *
330      @param   error   Selected error number. Starts with 0.
331      @param   sf      Selected error.
332      */
333     public synchronized void selectError(final int error, final SourceFileException sf) {
334         if (errorMarker != null) {
335             this.requestFocus();
336             qedeq.requestFocus();
337             qedeq.setCaretPosition(errorMarker.getLandmarkOffsetForBlock(error));
338         else {
339             Trace.paramInfo(CLASS, "selectError""errorMarker""null");
340         }
341     }
342 
343     /**
344      * Jump to warning location. Uses only <code>warning</code> to select correct marker.
345      *
346      @param   warning Selected warning number. Starts with 0.
347      @param   sf      Selected warning.
348      */
349     public synchronized void selectWarning(final int warning, final SourceFileException sf) {
350         int block = warning;
351 //        if (prop != null && prop.getErrors().size() > 0) {
352 //            block += prop.getErrors().size();
353 //        }
354         if (warningMarker != null) {
355             this.requestFocus();
356             qedeq.requestFocus();
357             qedeq.setCaretPosition(warningMarker.getLandmarkOffsetForBlock(block));
358         else {
359             Trace.paramInfo(CLASS, "selectWarning""warningMarker""null");
360         }
361     }
362 
363 }