2
GeoGebra - Dynamic Mathematics for Everyone
3
http://www.geogebra.org
5
This file is part of GeoGebra.
7
This program is free software; you can redistribute it and/or modify it
8
under the terms of the GNU General Public License as published by
9
the Free Software Foundation.
16
* Created on 13. Oktober 2001, 17:40
19
package geogebra.euclidian;
21
import geogebra.kernel.Construction;
22
import geogebra.kernel.GeoElement;
23
import geogebra.kernel.GeoPoint;
24
import geogebra.kernel.GeoVector;
25
import geogebra.kernel.Kernel;
27
import java.awt.BasicStroke;
28
import java.awt.Color;
29
import java.awt.Dimension;
31
import java.awt.Graphics2D;
32
import java.awt.Point;
33
import java.awt.Rectangle;
34
import java.awt.RenderingHints;
35
import java.awt.Shape;
36
import java.awt.font.FontRenderContext;
37
import java.awt.font.TextLayout;
38
import java.util.ArrayList;
45
public abstract class Drawable {
46
BasicStroke objStroke = EuclidianView.getDefaultStroke();
47
BasicStroke selStroke = EuclidianView.getDefaultSelectionStroke();
48
BasicStroke decoStroke = EuclidianView.getDefaultStroke();
50
private int lineThickness = -1;
51
public int lineType = -1;
53
protected EuclidianView view;
54
protected GeoElement geo;
55
public int xLabel, yLabel;
56
int mouseX, mouseY; // for Previewables
57
protected String labelDesc; // label Description
58
private String oldLabelDesc;
59
private boolean labelHasIndex = false;
60
Rectangle labelRectangle = new Rectangle(); // for label hit testing
61
Shape strokedShape, strokedShape2;
64
private int lastFontSize = -1;
67
protected boolean isTracing = false;
69
boolean createdByDrawList = false;
71
public abstract void update();
72
public abstract void draw(Graphics2D g2);
73
public abstract boolean hit(int x, int y);
74
public abstract boolean isInside(Rectangle rect);
75
public abstract GeoElement getGeoElement();
76
public abstract void setGeoElement(GeoElement geo);
78
public double getxLabel() {
82
public double getyLabel() {
86
void updateFontSize() {
90
* Returns the bounding box of this Drawable in screen coordinates.
91
* @return null when this Drawable is infinite or undefined
93
public Rectangle getBounds() {
97
final protected void drawLabel(Graphics2D g2) {
98
if (labelDesc == null) return;
99
String label = labelDesc;
102
// label changed: check for bold or italic tags in caption
103
if (oldLabelDesc != labelDesc || labelDesc.startsWith("<")) {
104
boolean italic = false;
106
// support for bold and italic tags in captions
107
// must be whole caption
108
if (label.startsWith("<i>") && label.endsWith("</i>")) {
110
oldFont = g2.getFont();
112
// use Serif font so that we can get a nice curly italic x
113
g2.setFont(view.getApplication().getFont(true, oldFont.getStyle() | Font.ITALIC, oldFont.getSize()));
114
label = label.substring(3, label.length() - 4);
118
if (label.startsWith("<b>") && label.endsWith("</b>")) {
120
oldFont = g2.getFont();
122
g2.setFont(g2.getFont().deriveFont(Font.BOLD + (italic ? Font.ITALIC : 0)));
123
label = label.substring(3, label.length() - 4);
127
// no index in label: draw it fast
128
int fontSize = g2.getFont().getSize();
129
if (oldLabelDesc == labelDesc && !labelHasIndex && lastFontSize == fontSize) {
130
g2.drawString(label, xLabel, yLabel);
131
labelRectangle.setLocation(xLabel, yLabel - fontSize);
133
else { // label with index or label has changed:
134
// do the slower index drawing routine and check for indices
135
oldLabelDesc = labelDesc;
136
lastFontSize = fontSize;
138
Point p = drawIndexedString(g2, label, xLabel, yLabel);
139
labelHasIndex = p.y > 0;
140
labelRectangle.setBounds(xLabel, yLabel - fontSize, p.x, fontSize + p.y);
148
* Adapts xLabel and yLabel to make sure that the label rectangle fits fully on screen.
150
final public void ensureLabelDrawsOnScreen() {
152
drawLabel(view.g2Dtemp);
154
// make sure labelRectangle fits on screen horizontally
158
xLabel = Math.min(xLabel, view.width - labelRectangle.width - 3);
159
if (yLabel < view.fontSize)
160
yLabel = view.fontSize;
162
yLabel = Math.min(yLabel, view.height - 3);
164
// update label rectangle position
165
labelRectangle.setLocation(xLabel, yLabel - view.fontSize);
168
// Michael Borcherds 2008-06-10
169
final float textWidth(String str, Font font, FontRenderContext frc)
171
if (str.equals("")) return 0f;
172
TextLayout layout = new TextLayout(str , font, frc);
173
return layout.getAdvance();
179
final void drawMultilineLaTeX(Graphics2D g2, Font font, Color fgColor, Color bgColor) {
181
int fontSize = g2.getFont().getSize();
182
float lineSpread = fontSize * 1.0f;
183
float lineSpace = fontSize * 0.5f;
190
labelDesc=labelDesc.replaceAll("\\$\\$", "\\$"); // replace $$ with $
191
labelDesc=labelDesc.replaceAll("\\\\\\[", "\\$");// replace \[ with $
192
labelDesc=labelDesc.replaceAll("\\\\\\]", "\\$");// replace \] with $
193
labelDesc=labelDesc.replaceAll("\\\\\\(", "\\$");// replace \( with $
194
labelDesc=labelDesc.replaceAll("\\\\\\)", "\\$");// replace \) with $
197
String[] lines=labelDesc.split("\n");
200
for (int k=0 ; k<lines.length ; k++)
203
String[] strings=lines[k].split("\\$");
204
int heights[] = new int[strings.length];
207
if (lines[k].indexOf('$') == -1 && lines.length == 1)
209
latex=true; // just latex
213
// calculate heights of each element
214
for (int j=0 ; j<strings.length ; j++)
217
if (!strings[j].equals(str(" ",strings[j].length()))) // check not empty or just spaces
221
dim = drawEquation(view.getTempGraphics2D(),0,0, strings[j], font, fgColor, bgColor);
222
//dim = sHotEqn.getSizeof(strings[j]);
223
//widths[j] = dim.width;
224
heights[j] = dim.height;
228
heights[j] = (int)lineSpread; //p.y;
236
if (heights[j] > maxHeight) maxHeight=heights[j];
240
if (k!=0) maxHeight += lineSpace;
245
if (lines[k].indexOf('$') == -1 && lines.length == 1)
247
latex=true; // just latex
248
//Application.debug("just latex");
252
for (int j=0 ; j<strings.length ; j++)
255
if (!strings[j].equals(str(" ",strings[j].length()))) // check not empty or just spaces
258
int vOffset = (maxHeight - heights[j] )/2; // vertical centering
263
dim = drawEquation(g2,xLabel + hOffset,(int)(yLabel + height) + vOffset, strings[j], font, fgColor, bgColor);
268
Point p = drawIndexedString(g2, strings[j], xLabel + hOffset, yLabel + height + vOffset + lineSpread);
274
if (hOffset > maxhOffset) maxhOffset = hOffset;
277
labelRectangle.setBounds(xLabel, yLabel, maxhOffset, (int)height);
280
// returns a string consisting of n consecutive "str"s
281
final private String str(String str, int n)
283
if (n == 0) return "";
284
else if (n == 1) return str;
286
StringBuffer ret = new StringBuffer();
288
for (int i=0 ; i<n ; i++) ret.append(str);
289
return ret.toString();
295
* Draw a multiline LaTeX label.
297
* TODO: Improve performance (caching, etc.)
304
final void drawMultilineLaTeX(Graphics2D g2, Font font, Color fgColor, Color bgColor) {
305
int fontSize = g2.getFont().getSize();
306
int lineSpread = (int)(fontSize * 1.0f);
307
int lineSpace = (int)(fontSize * 0.5f);
309
// latex delimiters \[ \] \( \) $$ -> $
310
labelDesc = labelDesc.replaceAll("(\\$\\$|\\\\\\[|\\\\\\]|\\\\\\(|\\\\\\))", "\\$");
312
// split on $ but not \$
313
String[] elements = labelDesc.split("(?<![\\\\])(\\$)", -1);
315
ArrayList lineHeights = new ArrayList();
316
lineHeights.add(new Integer(lineSpread + lineSpace));
317
ArrayList elementHeights = new ArrayList();
319
// use latex by default just if there is just a single element
320
boolean isLaTeX = (elements.length == 1);
322
// calculate the required space of every element
323
for(int i = 0, currentLine = 0, currentElement = 0; i < elements.length; ++i) {
325
// save the height of this element by drawing it to a temporary buffer
326
int height = drawEquation(view.getTempGraphics2D(font), 0, 0, elements[i], font, fgColor, bgColor).height;
327
elementHeights.add(new Integer(height));
329
// check if this element is taller than every else in the line
330
if(height > ((Integer)lineHeights.get(currentLine)).intValue())
331
lineHeights.set(currentLine, new Integer(height));
335
elements[i] = elements[i].replaceAll("\\\\\\$", "\\$");
336
String[] lines = elements[i].split("\\n", -1);
338
for(int j = 0; j < lines.length; ++j) {
339
elementHeights.add(new Integer(lineSpread));
342
if(j + 1 < lines.length) {
345
lineHeights.add(new Integer(lineSpread + lineSpace));
358
// use latex by default just if there is just a single element
359
isLaTeX = (elements.length == 1);
364
// now draw all elements
365
for(int i = 0, currentLine = 0, currentElement = 0; i < elements.length; ++i) {
367
// calculate the y offset of this element by: (lineHeight - elementHeight) / 2
368
yOffset = (((Integer)(lineHeights.get(currentLine))).intValue() - ((Integer)(elementHeights.get(currentElement))).intValue()) / 2;
370
// draw the equation and save the x offset
371
xOffset += drawEquation(g2, xLabel + xOffset, (int)(yLabel + height) + yOffset, elements[i], font, fgColor, bgColor).width;
375
String[] lines = elements[i].split("\\n", -1);
377
for(int j = 0; j < lines.length; ++j) {
378
// calculate the y offset like done with the element
379
yOffset = (((Integer)(lineHeights.get(currentLine))).intValue() - ((Integer)(elementHeights.get(currentElement))).intValue()) / 2;
382
xOffset += drawIndexedString(g2, lines[j], xLabel + xOffset, yLabel + height + yOffset + lineSpread).x;
384
// add the height of this line if more lines follow
385
if(j + 1 < lines.length) {
386
height += ((Integer)(lineHeights.get(currentLine))).intValue();
392
// create a new line if more will follow
393
if(j + 1 < lines.length) {
402
// last element, increase total height and check if this is the most wide element
403
if(i + 1 == elements.length) {
404
height += ((Integer)(lineHeights.get(currentLine))).intValue();
412
labelRectangle.setBounds(xLabel - 3, yLabel - 3, width + 6, height + 6);
416
private geogebra.gui.hoteqn.sHotEqn eqn;
418
final public Dimension drawEquation(Graphics2D g2, int x, int y, String text, Font font, Color fgColor, Color bgColor)
422
eqn = new geogebra.gui.hoteqn.sHotEqn(text);
423
//Application.debug(eqn.getSize());
424
eqn.setDoubleBuffered(false);
425
eqn.setEditable(false);
426
eqn.removeMouseListener(eqn);
427
eqn.removeMouseMotionListener(eqn);
429
eqn.setOpaque(false);
433
eqn.setEquation(text);
437
int size = (font.getSize() / 2) * 2;
443
eqn.setFontname(font.getName());
444
eqn.setFontsizes(size, size - 2, size - 4, size - 6);
445
eqn.setFontStyle(font.getStyle());
448
eqn.setForeground(fgColor);
449
eqn.setBackground(bgColor);
452
//eqn.paintComponent(g2Dtemp,0,0);
453
//dim=eqn.getSizeof(text);
454
eqn.paintComponent(g2,x,y);
457
//Application.debug(size);
461
final void drawMultilineText(Graphics2D g2) {
463
if (labelDesc == null) return;
466
int fontSize = g2.getFont().getSize();
467
float lineSpread = fontSize * 1.5f;
469
Font font = g2.getFont();
470
FontRenderContext frc = g2.getFontRenderContext();
471
int xoffset = 0, yoffset = 0;
474
if (oldLabelDesc == labelDesc && !labelHasIndex) {
475
// draw text line by line
477
int length = labelDesc.length();
478
for (int i=0; i < length-1; i++) {
479
if (labelDesc.charAt(i) == '\n') {
480
//end of line reached: draw this line
481
g2.drawString(labelDesc.substring(lineBegin, i), xLabel, yLabel + lines * lineSpread);
483
int width=(int)textWidth(labelDesc.substring(lineBegin, i), font, frc);
484
if (width > xoffset) xoffset = width;
491
float ypos = yLabel + lines * lineSpread;
492
g2.drawString(labelDesc.substring(lineBegin), xLabel, ypos);
494
int width=(int)textWidth(labelDesc.substring(lineBegin), font, frc);
495
if (width > xoffset) xoffset = width;
497
// Michael Borcherds 2008-06-10
498
// changed setLocation to setBounds (bugfix)
499
// and added final float textWidth()
500
//labelRectangle.setLocation(xLabel, yLabel - fontSize);
501
int height = (int) ( (lines +1)*lineSpread);
502
labelRectangle.setBounds(xLabel, yLabel - fontSize, xoffset, height );
506
// label description has changed, search for possible indices
507
oldLabelDesc = labelDesc;
509
// draw text line by line
511
int length = labelDesc.length();
514
for (int i=0; i < length-1; i++) {
515
if (labelDesc.charAt(i) == '\n') {
516
//end of line reached: draw this line
517
Point p = drawIndexedString(g2, labelDesc.substring(lineBegin, i), xLabel, yLabel + lines * lineSpread);
518
if (p.x > xoffset) xoffset = p.x;
519
if (p.y > yoffset) yoffset = p.y;
525
float ypos = yLabel + lines * lineSpread;
526
Point p = drawIndexedString(g2, labelDesc.substring(lineBegin), xLabel, ypos);
527
if (p.x > xoffset) xoffset = p.x;
528
if (p.y > yoffset) yoffset = p.y;
529
labelHasIndex = yoffset > 0;
530
int height = (int) ( (lines +1)*lineSpread);
531
labelRectangle.setBounds(xLabel, yLabel - fontSize, xoffset, height );
536
* Draws a string str with possible indices to g2 at position x, y.
537
* The indices are drawn using the given indexFont.
538
* Examples for strings with indices: "a_1" or "s_{ab}"
541
* @return additional pixel needed to draw str (x-offset, y-offset)
543
public static Point drawIndexedString(Graphics2D g2, String str, float xPos, float yPos) {
544
Font g2font = g2.getFont();
545
Font indexFont = getIndexFont(g2font);
548
FontRenderContext frc = g2.getFontRenderContext();
550
int indexOffset = indexFont.getSize() / 2;
556
if (str == null) return null;
557
int length = str.length();
559
for (int i=0; i < length; i++) {
560
switch (str.charAt(i)) {
562
// draw everything before _
564
font = (depth == 0) ? g2font : indexFont;
565
y = yPos + depth * indexOffset;
566
if (y > maxY) maxY = y;
567
String tempStr = str.substring(startPos, i);
568
layout = new TextLayout(tempStr, font, frc);
570
g2.drawString(tempStr, x, y);
571
x += layout.getAdvance();
576
// check if next character is a '{' (beginning of index with several chars)
577
if (startPos < length && str.charAt(startPos) != '{') {
578
font = (depth == 0) ? g2font : indexFont;
579
y = yPos + depth * indexOffset;
580
if (y > maxY) maxY = y;
581
String tempStr = str.substring(startPos, startPos+1);
582
layout = new TextLayout(tempStr, font, frc);
584
g2.drawString(tempStr, x, y);
585
x += layout.getAdvance();
592
case '}': // end of index with several characters
595
font = (depth == 0) ? g2font : indexFont;
596
y = yPos + depth * indexOffset;
597
if (y > maxY) maxY = y;
598
String tempStr = str.substring(startPos, i);
599
layout = new TextLayout(tempStr, font, frc);
601
g2.drawString(tempStr, x, y);
602
x += layout.getAdvance();
611
if (startPos < length) {
612
font = (depth == 0) ? g2font : indexFont;
613
y = yPos + depth * indexOffset;
614
if (y > maxY) maxY = y;
615
String tempStr = str.substring(startPos);
616
layout = new TextLayout(tempStr, font, frc);
618
g2.drawString(tempStr, x, y);
619
x += layout.getAdvance();
622
return new Point(Math.round(x - xPos), Math.round(maxY - yPos));
625
private static Font getIndexFont(Font f) {
626
// index font size should be at least 8pt
627
int newSize = Math.max( (int) (f.getSize() * 0.9) , 8);
628
return f.deriveFont(f.getStyle(), newSize);
632
* Adds geo's label offset to xLabel and yLabel.
634
* @return whether something was changed
636
final protected boolean addLabelOffset() {
637
return addLabelOffset(false);
641
* Adds geo's label offset to xLabel and yLabel.
642
* @param ensureLabelOnScreen: if true we make sure that the label is drawn on screen
644
* @return whether something was changed
646
final protected boolean addLabelOffset(boolean ensureLabelOnScreen) {
647
if (ensureLabelOnScreen) {
648
// MAKE SURE LABEL STAYS ON SCREEN
649
int xLabelOld = xLabel;
650
int yLabelOld = yLabel;
651
xLabel += geo.labelOffsetX;
652
yLabel += geo.labelOffsetY;
654
// change xLabel and yLabel so that label stays on screen
655
ensureLabelDrawsOnScreen();
657
// something changed?
658
return xLabelOld != xLabel || yLabelOld != yLabel;
661
// STANDARD BEHAVIOUR
662
if (geo.labelOffsetX == 0 && geo.labelOffsetY == 0) return false;
664
int x = xLabel + geo.labelOffsetX;
665
int y = yLabel + geo.labelOffsetY;
667
// don't let offset move label out of screen
668
int xmax = view.width - 15;
669
int ymax = view.height - 5;
670
if (x < 5 || x > xmax ) return false;
671
if (y < 15 || y > ymax) return false;
680
* Was the label clicked at? (mouse pointer
681
* location (x,y) in screen coords)
683
public boolean hitLabel(int x, int y) {
684
return labelRectangle.contains(x, y);
687
final void updateStrokes(GeoElement geo) {
689
strokedShape2 = null;
691
if (lineThickness != geo.lineThickness) {
692
lineThickness = geo.lineThickness;
693
lineType = geo.lineType;
695
float width = lineThickness / 2.0f;
696
objStroke = EuclidianView.getStroke(width, lineType);
697
decoStroke = EuclidianView.getStroke(width, EuclidianView.LINE_TYPE_FULL);
699
EuclidianView.getStroke(
700
width + EuclidianView.SELECTION_ADD,
701
EuclidianView.LINE_TYPE_FULL);
702
} else if (lineType != geo.lineType) {
703
lineType = geo.lineType;
705
float width = lineThickness / 2.0f;
706
objStroke = EuclidianView.getStroke(width, lineType);
710
final public static void drawWithValueStrokePure(Shape shape, Graphics2D g2) {
711
Object oldHint = g2.getRenderingHint(RenderingHints.KEY_STROKE_CONTROL);
712
g2.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_PURE);
714
g2.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, oldHint);
717
final public static void fillWithValueStrokePure(Shape shape, Graphics2D g2) {
718
Object oldHint = g2.getRenderingHint(RenderingHints.KEY_STROKE_CONTROL);
719
g2.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_PURE);
721
g2.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, oldHint);
724
//private StringBuffer command = new StringBuffer();
725
private double[] coords = new double[2];
727
public void recordToSpreadsheet(GeoElement geo) {
730
// stop spurious numbers after undo
731
if (view.getKernel().isViewReiniting()) return;
733
// record to spreadsheet tool & trace to spreadsheet
734
Construction cons = view.getKernel().getConstruction();
739
switch (geo.getGeoClassType()) {
741
/* TODO: use this, rather than the code in GeoNumeric.update()
742
case GeoElement.GEO_CLASS_NUMERIC:
744
cons.getApplication().getGuiManager().traceToSpreadsheet(geo);
748
case GeoElement.GEO_CLASS_POINT:
749
//Application.debug("GEO_CLASS_POINT");
750
GeoPoint P = (GeoPoint)geo;
752
boolean polar = P.getMode() == Kernel.COORD_POLAR;
755
P.getPolarCoords(coords);
757
P.getInhomCoords(coords);
760
if (P.getLastTrace1() != coords[0] || P.getLastTrace2() != coords[1]) {
762
cons.getApplication().getGuiManager().traceToSpreadsheet(geo);
764
col = P.getTraceColumn1(); // call before getTraceRow()
765
row = P.getTraceRow();
767
//Application.debug(col+row);
768
cons.getApplication().getGuiManager().setScrollToShow(true);
770
GeoNumeric traceCell = new GeoNumeric(cons,col+row,coords[0]);
771
traceCell.setAuxiliaryObject(true);
773
col = P.getTraceColumn2(); // call before getTraceRow()
774
//Application.debug(col+row);
776
GeoNumeric traceCell2;
778
if (polar) traceCell2 = new GeoAngle(cons,col+row,coords[1]);
779
else traceCell2 = new GeoNumeric(cons,col+row,coords[1]);
781
traceCell2.setAuxiliaryObject(true);
783
cons.getApplication().getGuiManager().setScrollToShow(false);
785
P.setLastTrace1(coords[0]);
786
P.setLastTrace2(coords[1]);
792
case GeoElement.GEO_CLASS_VECTOR:
793
// record to spreadsheet tool
794
GeoVector vector = (GeoVector)geo;
797
vector.getInhomCoords(coords);
799
if (vector.getLastTrace1() != coords[0] || vector.getLastTrace2() != coords[1]) {
801
cons.getApplication().getGuiManager().traceToSpreadsheet(geo);
804
col = vector.getTraceColumn1();
805
row = vector.getTraceRow();
807
cons.getApplication().getGuiManager().setScrollToShow(true);
809
GeoNumeric traceCell = new GeoNumeric(cons,col+row,coords[0]);
810
traceCell.setAuxiliaryObject(true);
811
GeoNumeric traceCell2 = new GeoNumeric(cons,vector.getTraceColumn2()+row,coords[1]);
812
traceCell2.setAuxiliaryObject(true);
814
cons.getApplication().getGuiManager().setScrollToShow(false);
816
vector.setLastTrace1(coords[0]);
817
vector.setLastTrace2(coords[1]);
b'\\ No newline at end of file'
2
GeoGebra - Dynamic Mathematics for Everyone
3
http://www.geogebra.org
5
This file is part of GeoGebra.
7
This program is free software; you can redistribute it and/or modify it
8
under the terms of the GNU General Public License as published by
9
the Free Software Foundation.
16
* Created on 13. Oktober 2001, 17:40
19
package geogebra.euclidian;
21
import geogebra.kernel.Construction;
22
import geogebra.kernel.GeoElement;
23
import geogebra.kernel.GeoText;
24
import geogebra.main.Application;
25
import geogebra.main.MyError;
26
import geogebra.util.Unicode;
28
import java.awt.BasicStroke;
29
import java.awt.Color;
31
import java.awt.Graphics2D;
32
import java.awt.Image;
33
import java.awt.Insets;
34
import java.awt.Point;
35
import java.awt.Rectangle;
36
import java.awt.RenderingHints;
37
import java.awt.Shape;
38
import java.awt.font.FontRenderContext;
39
import java.awt.font.TextLayout;
40
import java.awt.geom.Area;
41
import java.util.ArrayList;
42
import java.util.Iterator;
44
import javax.swing.JLabel;
53
public abstract class Drawable extends DrawableND {
55
private boolean forceNoFill;
58
BasicStroke objStroke = EuclidianView.getDefaultStroke();
59
BasicStroke selStroke = EuclidianView.getDefaultSelectionStroke();
60
BasicStroke decoStroke = EuclidianView.getDefaultStroke();
62
private int lineThickness = -1;
63
public int lineType = -1;
65
protected EuclidianView view;
66
protected int hitThreshold = 3;
67
protected GeoElement geo;
68
public int xLabel, yLabel;
69
/** for Previewables */
71
/** label Description */
72
protected String labelDesc;
73
private String oldLabelDesc;
74
private boolean labelHasIndex = false;
75
/** for label hit testing */
76
Rectangle labelRectangle = new Rectangle();
77
Shape strokedShape, strokedShape2;
83
private int lastFontSize = -1;
86
protected boolean isTracing = false;
88
//boolean createdByDrawList = false;
90
public abstract void update();
91
public abstract void draw(Graphics2D g2);
92
public abstract boolean hit(int x, int y);
93
public abstract boolean isInside(Rectangle rect);
94
public abstract GeoElement getGeoElement();
95
public abstract void setGeoElement(GeoElement geo);
97
public double getxLabel() {
101
public double getyLabel() {
105
void updateFontSize() {
109
* Returns the bounding box of this Drawable in screen coordinates.
110
* @return null when this Drawable is infinite or undefined
112
public Rectangle getBounds() {
116
final protected void drawLabel(Graphics2D g2) {
117
if (labelDesc == null) return;
118
String label = labelDesc;
121
// allow LaTeX caption surrounded by $ $
122
if (label.startsWith("$") && label.endsWith("$")) {
123
boolean serif = true; // nice "x"s
124
if (geo.isGeoText()) serif = ((GeoText)geo).isSerifFont();
125
int offsetY = 10 + view.fontSize; // make sure LaTeX labels don't go off bottom of screen
126
FormulaDimension dim = view.getApplication().getDrawEquation().drawEquation(geo.getKernel().getApplication(), geo, g2, xLabel, yLabel - offsetY, label.substring(1, label.length() - 1), g2.getFont(), serif, g2.getColor(), g2.getBackground(), true);
127
labelRectangle.setBounds(xLabel, yLabel - dim.depth - offsetY, (int)dim.width, (int)dim.height);
131
// label changed: check for bold or italic tags in caption
132
if (oldLabelDesc != labelDesc || labelDesc.startsWith("<")) {
133
boolean italic = false;
135
// support for bold and italic tags in captions
136
// must be whole caption
137
if (label.startsWith("<i>") && label.endsWith("</i>")) {
139
oldFont = g2.getFont();
141
// use Serif font so that we can get a nice curly italic x
142
g2.setFont(view.getApplication().getFont(true, oldFont.getStyle() | Font.ITALIC, oldFont.getSize()));
143
label = label.substring(3, label.length() - 4);
147
if (label.startsWith("<b>") && label.endsWith("</b>")) {
149
oldFont = g2.getFont();
151
g2.setFont(g2.getFont().deriveFont(Font.BOLD + (italic ? Font.ITALIC : 0)));
152
label = label.substring(3, label.length() - 4);
156
// no index in label: draw it fast
157
int fontSize = g2.getFont().getSize();
158
if (oldLabelDesc == labelDesc && !labelHasIndex && lastFontSize == fontSize) {
159
lastFontSize = fontSize;
160
g2.drawString(label, xLabel, yLabel);
161
labelRectangle.setLocation(xLabel, yLabel - fontSize);
163
else { // label with index or label has changed:
164
// do the slower index drawing routine and check for indices
165
oldLabelDesc = labelDesc;
167
Point p = drawIndexedString(view.getApplication(), g2, label, xLabel, yLabel, isSerif());
168
labelHasIndex = p.y > 0;
169
labelRectangle.setBounds(xLabel, yLabel - fontSize, p.x, fontSize + p.y);
177
* Adapts xLabel and yLabel to make sure that the label rectangle fits fully on screen.
179
final public void ensureLabelDrawsOnScreen() {
181
drawLabel(view.getTempGraphics2D(view.getApplication().getPlainFont()));
183
// make sure labelRectangle fits on screen horizontally
187
xLabel = Math.min(xLabel, view.width - labelRectangle.width - 3);
188
if (yLabel < labelRectangle.height)
189
yLabel = labelRectangle.height;
191
yLabel = Math.min(yLabel, view.height - 3);
193
// update label rectangle position
194
labelRectangle.setLocation(xLabel, yLabel - view.fontSize);
197
// Michael Borcherds 2008-06-10
198
final static float textWidth(String str, Font font, FontRenderContext frc)
200
if (str.equals("")) return 0f;
201
TextLayout layout = new TextLayout(str , font, frc);
202
return layout.getAdvance();
208
final void drawMultilineLaTeX(Graphics2D g2, Font font, Color fgColor, Color bgColor) {
210
int fontSize = g2.getFont().getSize();
211
float lineSpread = fontSize * 1.0f;
212
float lineSpace = fontSize * 0.5f;
219
labelDesc=labelDesc.replaceAll("\\$\\$", "\\$"); // replace $$ with $
220
labelDesc=labelDesc.replaceAll("\\\\\\[", "\\$");// replace \[ with $
221
labelDesc=labelDesc.replaceAll("\\\\\\]", "\\$");// replace \] with $
222
labelDesc=labelDesc.replaceAll("\\\\\\(", "\\$");// replace \( with $
223
labelDesc=labelDesc.replaceAll("\\\\\\)", "\\$");// replace \) with $
226
String[] lines=labelDesc.split("\n");
229
for (int k=0 ; k<lines.length ; k++)
232
String[] strings=lines[k].split("\\$");
233
int heights[] = new int[strings.length];
236
if (lines[k].indexOf('$') == -1 && lines.length == 1)
238
latex=true; // just latex
242
// calculate heights of each element
243
for (int j=0 ; j<strings.length ; j++)
246
if (!strings[j].equals(str(" ",strings[j].length()))) // check not empty or just spaces
250
dim = drawEquation(view.getTempGraphics2D(),0,0, strings[j], font, fgColor, bgColor);
251
//dim = sHotEqn.getSizeof(strings[j]);
252
//widths[j] = dim.width;
253
heights[j] = dim.height;
257
heights[j] = (int)lineSpread; //p.y;
265
if (heights[j] > maxHeight) maxHeight=heights[j];
269
if (k!=0) maxHeight += lineSpace;
274
if (lines[k].indexOf('$') == -1 && lines.length == 1)
276
latex=true; // just latex
277
//Application.debug("just latex");
281
for (int j=0 ; j<strings.length ; j++)
284
if (!strings[j].equals(str(" ",strings[j].length()))) // check not empty or just spaces
287
int vOffset = (maxHeight - heights[j] )/2; // vertical centering
292
dim = drawEquation(g2,xLabel + hOffset,(int)(yLabel + height) + vOffset, strings[j], font, fgColor, bgColor);
297
Point p = drawIndexedString(g2, strings[j], xLabel + hOffset, yLabel + height + vOffset + lineSpread);
303
if (hOffset > maxhOffset) maxhOffset = hOffset;
306
labelRectangle.setBounds(xLabel, yLabel, maxhOffset, (int)height);
309
// returns a string consisting of n consecutive "str"s
310
final private String str(String str, int n)
312
if (n == 0) return "";
313
else if (n == 1) return str;
315
StringBuilder ret = new StringBuilder();
317
for (int i=0 ; i<n ; i++) ret.append(str);
318
return ret.toString();
324
* Draw a multiline LaTeX label.
326
* TODO: Improve performance (caching, etc.)
333
public final void drawMultilineLaTeX(Graphics2D g2, Font font, Color fgColor, Color bgColor) {
334
int fontSize = g2.getFont().getSize();
335
int lineSpread = (int)(fontSize * 1.0f);
336
int lineSpace = (int)(fontSize * 0.5f);
338
// latex delimiters \[ \] \( \) $$ -> $
339
labelDesc = labelDesc.replaceAll("(\\$\\$|\\\\\\[|\\\\\\]|\\\\\\(|\\\\\\))", "\\$");
341
// split on $ but not \$
342
String[] elements = labelDesc.split("(?<![\\\\])(\\$)", -1);
344
ArrayList<Integer> lineHeights = new ArrayList<Integer>();
345
lineHeights.add(new Integer(lineSpread + lineSpace));
346
ArrayList<Integer> elementHeights = new ArrayList<Integer>();
347
ArrayList<Integer> elementDepths = new ArrayList<Integer>();
351
// use latex by default just if there is just a single element
352
boolean isLaTeX = (elements.length == 1);
354
// calculate the required space of every element
355
for(int i = 0, currentLine = 0, currentElement = 0; i < elements.length; ++i) {
357
// save the height of this element by drawing it to a temporary buffer
358
FormulaDimension dim = new FormulaDimension();
359
dim = view.getApplication().getDrawEquation().drawEquation(view.app, geo, view.getTempGraphics2D(font), 0, 0, elements[i], font, ((GeoText)geo).isSerifFont(), fgColor, bgColor, false);
361
int height = dim.height;
365
elementHeights.add(new Integer(height));
366
elementDepths.add(new Integer(dim.depth));
368
// check if this element is taller than every else in the line
369
if(height > (lineHeights.get(currentLine)).intValue())
370
lineHeights.set(currentLine, new Integer(height));
374
elements[i] = elements[i].replaceAll("\\\\\\$", "\\$");
375
String[] lines = elements[i].split("\\n", -1);
377
for(int j = 0; j < lines.length; ++j) {
378
elementHeights.add(new Integer(lineSpread));
379
elementDepths.add(new Integer(0));
382
if(j + 1 < lines.length) {
385
lineHeights.add(new Integer(lineSpread + lineSpace));
398
// use latex by default just if there is just a single element
399
isLaTeX = (elements.length == 1);
404
// now draw all elements
405
for(int i = 0, currentLine = 0, currentElement = 0; i < elements.length; ++i) {
407
// calculate the y offset of this element by: (lineHeight - elementHeight) / 2
408
yOffset = (((lineHeights.get(currentLine))).intValue() - ((elementHeights.get(currentElement))).intValue()) / 2;
410
// draw the equation and save the x offset
411
xOffset += view.getApplication().getDrawEquation().drawEquation(view.app, geo, g2, xLabel + xOffset, (yLabel + height) + yOffset + elementDepths.get(currentElement), elements[i], font, ((GeoText)geo).isSerifFont(), fgColor, bgColor, true).width;
415
String[] lines = elements[i].split("\\n", -1);
417
for(int j = 0; j < lines.length; ++j) {
418
// calculate the y offset like done with the element
419
yOffset = (((lineHeights.get(currentLine))).intValue() - ((elementHeights.get(currentElement))).intValue()) / 2;
422
g2.setFont(font); // JLaTeXMath changes g2's fontsize
423
xOffset += drawIndexedString(view.getApplication(), g2, lines[j], xLabel + xOffset, yLabel + height + yOffset + lineSpread, isSerif()).x;
425
// add the height of this line if more lines follow
426
if(j + 1 < lines.length) {
427
height += ((lineHeights.get(currentLine))).intValue();
433
// create a new line if more will follow
434
if(j + 1 < lines.length) {
443
// last element, increase total height and check if this is the most wide element
444
if(i + 1 == elements.length) {
445
height += ((lineHeights.get(currentLine))).intValue();
453
labelRectangle.setBounds(xLabel - 3, yLabel - 3 + depth, width + 6, height + 6 );
456
private static geogebra.gui.hoteqn.sHotEqn eqn;
458
final public static Dimension drawEquationHotEqn(Application app, Graphics2D g2, int x, int y, String text, Font font, Color fgColor, Color bgColor)
462
eqn = new geogebra.gui.hoteqn.sHotEqn(text);
463
//Application.debug(eqn.getSize());
464
eqn.setDoubleBuffered(false);
465
eqn.setEditable(false);
466
eqn.removeMouseListener(eqn);
467
eqn.removeMouseMotionListener(eqn);
469
eqn.setOpaque(false);
473
eqn.setEquation(text);
477
int size = (font.getSize() / 2) * 2;
483
eqn.setFontname(font.getName());
484
eqn.setFontsizes(size, size - 2, size - 4, size - 6);
485
eqn.setFontStyle(font.getStyle());
488
eqn.setForeground(fgColor);
489
eqn.setBackground(bgColor);
492
//eqn.paintComponent(g2Dtemp,0,0);
493
//dim=eqn.getSizeof(text);
494
eqn.paintComponent(g2,x,y);
497
//Application.debug(size);
503
* Adds \\- to positions where the line can
504
* be broken. Now it only breaks at +, -, *
507
* @param latex String
508
* @return The LaTeX string with breaks
510
private static String addPossibleBreaks(String latex){
511
StringBuilder latexTmp=new StringBuilder(latex);
513
boolean no_addition=true;
514
for (int i=0; i<latexTmp.length()-2;i++){
515
char character=latexTmp.charAt(i);
528
if (latexTmp.charAt(i+1) != ';')
531
latexTmp.insert(i+1, "\\?");
535
if (latexTmp.charAt(i+1)!=' ')
541
latexTmp.insert(i+1, "\\?");
548
latexTmp.insert(i+1, "\\-");
553
//no addition happened at depth zero so it can be broken
554
//on * and space too.
556
return latexTmp.toString().replaceAll("\\?", "\\-");
558
return latexTmp.toString().replaceAll("\\?", "");
563
final static Rectangle drawMultiLineText(Application app, String labelDesc, int xLabel, int yLabel, Graphics2D g2, boolean serif) {
565
int fontSize = g2.getFont().getSize();
566
float lineSpread = fontSize * 1.5f;
568
Font font = g2.getFont();
569
font = app.getFontCanDisplay(labelDesc, serif, font.getStyle(), font.getSize());
571
FontRenderContext frc = g2.getFontRenderContext();
574
// draw text line by line
576
int length = labelDesc.length();
577
for (int i=0; i < length-1; i++) {
578
if (labelDesc.charAt(i) == '\n') {
579
//end of line reached: draw this line
580
g2.drawString(labelDesc.substring(lineBegin, i), xLabel, yLabel + lines * lineSpread);
582
int width=(int)textWidth(labelDesc.substring(lineBegin, i), font, frc);
583
if (width > xoffset) xoffset = width;
590
float ypos = yLabel + lines * lineSpread;
591
g2.drawString(labelDesc.substring(lineBegin), xLabel, ypos);
593
int width=(int)textWidth(labelDesc.substring(lineBegin), font, frc);
594
if (width > xoffset) xoffset = width;
596
// Michael Borcherds 2008-06-10
597
// changed setLocation to setBounds (bugfix)
598
// and added final float textWidth()
599
//labelRectangle.setLocation(xLabel, yLabel - fontSize);
600
int height = (int) ( (lines +1)*lineSpread);
602
return new Rectangle(xLabel-3, yLabel - fontSize -3, xoffset+6, height+6);
603
//labelRectangle.setBounds(xLabel, yLabel - fontSize, xoffset, height );
607
final boolean isSerif() {
608
return geo.isGeoText() ? ((GeoText)geo).isSerifFont() : false;
611
final void drawMultilineText(Graphics2D g2) {
613
if (labelDesc == null) return;
617
if (oldLabelDesc == labelDesc && !labelHasIndex) {
619
labelRectangle.setBounds(drawMultiLineText(view.getApplication(), labelDesc, xLabel, yLabel, g2, isSerif()) );
623
int fontSize = g2.getFont().getSize();
624
float lineSpread = fontSize * 1.5f;
626
int xoffset = 0, yoffset = 0;
628
// label description has changed, search for possible indices
629
oldLabelDesc = labelDesc;
631
// draw text line by line
633
int length = labelDesc.length();
636
for (int i=0; i < length-1; i++) {
637
if (labelDesc.charAt(i) == '\n') {
638
//end of line reached: draw this line
639
Point p = drawIndexedString(view.getApplication(), g2, labelDesc.substring(lineBegin, i), xLabel, yLabel + lines * lineSpread, geo.isGeoText() ? ((GeoText)geo).isSerifFont() : false);
640
if (p.x > xoffset) xoffset = p.x;
641
if (p.y > yoffset) yoffset = p.y;
647
float ypos = yLabel + lines * lineSpread;
648
Point p = drawIndexedString(view.getApplication(), g2, labelDesc.substring(lineBegin), xLabel, ypos, isSerif());
649
if (p.x > xoffset) xoffset = p.x;
650
if (p.y > yoffset) yoffset = p.y;
651
labelHasIndex = yoffset > 0;
652
int height = (int) ( (lines +1)*lineSpread);
653
labelRectangle.setBounds(xLabel-3, yLabel - fontSize - 3, xoffset + 6, height + 6 );
658
* Draws a string str with possible indices to g2 at position x, y.
659
* The indices are drawn using the given indexFont.
660
* Examples for strings with indices: "a_1" or "s_{ab}"
663
* @return additional pixel needed to draw str (x-offset, y-offset)
665
public static Point drawIndexedString(Application app, Graphics2D g2, String str, float xPos, float yPos, boolean serif) {
666
Font g2font = g2.getFont();
667
g2font = app.getFontCanDisplay(str, serif, g2font.getStyle(), g2font.getSize());
668
Font indexFont = getIndexFont(g2font);
671
FontRenderContext frc = g2.getFontRenderContext();
673
int indexOffset = indexFont.getSize() / 2;
679
if (str == null) return null;
680
int length = str.length();
682
for (int i=0; i < length; i++) {
683
switch (str.charAt(i)) {
685
// draw everything before _
687
font = (depth == 0) ? g2font : indexFont;
688
y = yPos + depth * indexOffset;
689
if (y > maxY) maxY = y;
690
String tempStr = str.substring(startPos, i);
691
layout = new TextLayout(tempStr, font, frc);
693
g2.drawString(tempStr, x, y);
694
x += layout.getAdvance();
699
// check if next character is a '{' (beginning of index with several chars)
700
if (startPos < length && str.charAt(startPos) != '{') {
701
font = (depth == 0) ? g2font : indexFont;
702
y = yPos + depth * indexOffset;
703
if (y > maxY) maxY = y;
704
String tempStr = str.substring(startPos, startPos+1);
705
layout = new TextLayout(tempStr, font, frc);
707
g2.drawString(tempStr, x, y);
708
x += layout.getAdvance();
715
case '}': // end of index with several characters
718
font = (depth == 0) ? g2font : indexFont;
719
y = yPos + depth * indexOffset;
720
if (y > maxY) maxY = y;
721
String tempStr = str.substring(startPos, i);
722
layout = new TextLayout(tempStr, font, frc);
724
g2.drawString(tempStr, x, y);
725
x += layout.getAdvance();
734
if (startPos < length) {
735
font = (depth == 0) ? g2font : indexFont;
736
y = yPos + depth * indexOffset;
737
if (y > maxY) maxY = y;
738
String tempStr = str.substring(startPos);
739
layout = new TextLayout(tempStr, font, frc);
741
g2.drawString(tempStr, x, y);
742
x += layout.getAdvance();
745
return new Point(Math.round(x - xPos), Math.round(maxY - yPos));
748
private static Font getIndexFont(Font f) {
749
// index font size should be at least 8pt
750
int newSize = Math.max( (int) (f.getSize() * 0.9) , 8);
751
return f.deriveFont(f.getStyle(), newSize);
755
* Adds geo's label offset to xLabel and yLabel.
757
* @return whether something was changed
759
final protected boolean addLabelOffset() {
760
return addLabelOffset(false);
764
* Adds geo's label offset to xLabel and yLabel.
765
* @param ensureLabelOnScreen if true we make sure that the label is drawn on screen
767
* @return whether something was changed
769
final protected boolean addLabelOffset(boolean ensureLabelOnScreen) {
770
if (ensureLabelOnScreen) {
771
// MAKE SURE LABEL STAYS ON SCREEN
772
int xLabelOld = xLabel;
773
int yLabelOld = yLabel;
774
xLabel += geo.labelOffsetX;
775
yLabel += geo.labelOffsetY;
777
// change xLabel and yLabel so that label stays on screen
778
ensureLabelDrawsOnScreen();
780
// something changed?
781
return xLabelOld != xLabel || yLabelOld != yLabel;
784
// STANDARD BEHAVIOUR
785
if (geo.labelOffsetX == 0 && geo.labelOffsetY == 0) return false;
787
int x = xLabel + geo.labelOffsetX;
788
int y = yLabel + geo.labelOffsetY;
790
// don't let offset move label out of screen
791
int xmax = view.width - 15;
792
int ymax = view.height - 5;
793
if (x < 5 || x > xmax ) return false;
794
if (y < 15 || y > ymax) return false;
803
* Was the label clicked at? (mouse pointer
804
* location (x,y) in screen coords)
806
public boolean hitLabel(int x, int y) {
807
return labelRectangle.contains(x, y);
809
private boolean forcedLineType;
812
* Set fixed line type and ignore line type of the geo.
813
* Needed for inequalities.
816
final void forceLineType(int type){
817
forcedLineType = true;
822
* Update strokes (default,selection,deco) accordingly to geo
825
final void updateStrokes(GeoElement geo) {
827
strokedShape2 = null;
829
if (lineThickness != geo.lineThickness) {
830
lineThickness = geo.lineThickness;
832
lineType = geo.lineType;
834
float width = lineThickness / 2.0f;
835
objStroke = EuclidianView.getStroke(width, lineType);
836
decoStroke = EuclidianView.getStroke(width, EuclidianView.LINE_TYPE_FULL);
838
EuclidianView.getStroke(
839
width + EuclidianView.SELECTION_ADD,
840
EuclidianView.LINE_TYPE_FULL);
841
} else if (lineType != geo.lineType) {
843
lineType = geo.lineType;
845
float width = lineThickness / 2.0f;
846
objStroke = EuclidianView.getStroke(width, lineType);
850
final public static void drawWithValueStrokePure(Shape shape, Graphics2D g2) {
851
Object oldHint = g2.getRenderingHint(RenderingHints.KEY_STROKE_CONTROL);
852
g2.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_PURE);
854
g2.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, oldHint);
857
final public static void fillWithValueStrokePure(Shape shape, Graphics2D g2) {
858
Object oldHint = g2.getRenderingHint(RenderingHints.KEY_STROKE_CONTROL);
859
g2.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_PURE);
861
g2.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, oldHint);
864
//private StringBuilder command = new StringBuilder();
865
private double[] coords = new double[2];
869
//=================================================================
870
//G.Sturr 2010-5-14: new recordToSpreadsheet method
873
public void recordToSpreadsheet(GeoElement geo) {
875
// stop spurious numbers after undo
876
if (view.getKernel().isViewReiniting())
878
Construction cons = view.getKernel().getConstruction();
879
if (cons.getApplication().useFullGui())
880
cons.getApplication().getGuiManager().traceToSpreadsheet(geo);
883
protected void fill(Graphics2D g2, Shape shape, boolean usePureStroke) {
886
if (geo.getFillType()==GeoElement.FILL_HATCH) {
888
// use decoStroke as it is always full (not dashed/dotted etc)
889
HatchingHandler.setHatching(g2, decoStroke, geo.getObjectColor(), geo.getBackgroundColor(), geo.getAlphaValue(), geo.getHatchingDistance(), geo.getHatchingAngle());
891
Drawable.fillWithValueStrokePure(shape, g2);
896
else if (geo.getFillType()==GeoElement.FILL_IMAGE)
898
HatchingHandler.setTexture(g2, geo, geo.getAlphaValue());
901
else if (geo.getAlphaValue() > 0.0f)
903
g2.setPaint(geo.getFillColor());
910
* @param forceNoFill the forceNoFill to set
912
public void setForceNoFill(boolean forceNoFill) {
913
this.forceNoFill = forceNoFill;
916
* @return the forceNoFill
918
public boolean isForceNoFill() {
922
* @param shape the shape to set
924
public void setShape(Area shape) {
930
public Area getShape() {