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:
042: package org.netbeans.modules.visualweb.css2;
043:
044: import java.awt.Color;
045: import java.awt.FontMetrics;
046: import java.awt.Graphics;
047: import java.awt.Rectangle;
048:
049: import org.netbeans.modules.visualweb.api.designer.DomProvider.DomPosition;
050: import org.netbeans.modules.visualweb.api.designer.DomProvider.DomPosition.Bias;
051: import org.netbeans.modules.visualweb.designer.WebForm;
052:
053: import org.w3c.dom.Element;
054: import org.w3c.dom.Text;
055:
056: /**
057: * SpaceBox represents a sequence of space characters in
058: * a LineBox.
059: * <p>
060: * See http://www.w3.org/TR/REC-CSS2/visuren.html
061: * <p>
062: * @todo I'm inheriting a lot of crap from CssBox -- margins, padding,
063: * etc. Is there a way I could make an even more primitive box than
064: * CssBox for this?
065:
066: * @author Tor Norbye
067: */
068: public class SpaceBox extends CssBox {
069: private int beginDomOffset = -1;
070: private int endDomOffset = -1;
071: private int beginOffset;
072: private int endOffset;
073: private String xhtml; // Identical to the "contentChars" char array
074: private String jspx; // Original jspx source whose entities were expanded into xhtml.
075: private FontMetrics metrics;
076: private int decoration;
077: // private RaveText node;
078: private Text node;
079: private Color fg;
080:
081: /**
082: * Create a SpaceBox representing the set of spaces running from
083: * position [beginNode,beginOffset] to position
084: * [endNode,endOffset], formatted using the given style.
085: *
086: * @param beginOffset The offset into the contentChars array where the text
087: * for this inline box begins.
088: * @param endOffset The offset into the contentChars array where the text
089: * for this inline box ends.
090: * @param decoration A bit mask indicating whether underline, overline,
091: * strikethrough etc. should be drawn according to the UNDERLINE,
092: * STRIKE etc. constant fields of the class.
093: * @param metrics The font metrics (& font, via metrics.getFont()) to
094: * use to render this text.
095: */
096: public SpaceBox(WebForm webform, Element styleElement, Text node,
097: String xhtml, String jspx, int beginOffset, int endOffset,
098: Color fg, Color bg, int decoration, FontMetrics metrics,
099: boolean hidden) {
100: // Space isn't technically replaced, but it acts like it in many
101: // ways, e.g. it has an intrinsic size, should be treated "atomically"
102: // like an <object> or an <iframe>, etc., and it's that meaning that is
103: // used for the replaced flag
104: super (webform, styleElement, BoxType.SPACE, true, true);
105: this .beginOffset = beginOffset;
106: this .endOffset = endOffset;
107: this .node = node;
108: this .decoration = decoration;
109: this .fg = fg;
110: this .bg = bg;
111: this .metrics = metrics;
112: this .xhtml = xhtml;
113: this .jspx = jspx;
114: this .hidden = hidden;
115:
116: this .contentWidth = this .width = metrics.charWidth(' ');
117: this .contentHeight = this .height = metrics.getHeight();
118: }
119:
120: protected void initialize() {
121: }
122:
123: // Note that I pass in null to super's element reference because
124: // the element corresponds to the parent, not this box, yet
125: // we want to store it as our effective element such that css
126: // lookups on this text box uses this element.
127: protected void initializeDesignBean() {
128: }
129:
130: protected void initializeInvariants() {
131: }
132:
133: /** Can't set widths on text boxes themselves
134: * @todo Can min-width be set on spans etc. ?
135: */
136: protected void initializeHorizontalWidths(FormatContext context) {
137: }
138:
139: // public String toString() {
140: // return "SpaceBox[" + paramString() + "]";
141: // }
142:
143: protected String paramString() {
144: return "space, "
145: + super .paramString()
146: + ", "
147: + (metrics == null ? "" : "font ascent="
148: + metrics.getAscent() + ", descent="
149: + metrics.getDescent() + ", height="
150: + metrics.getHeight() + ", " + "font="
151: + metrics.getFont()) + ", boffset="
152: + beginOffset + ", eoffset=" + endOffset;
153: }
154:
155: protected void paintBackground(Graphics g, int px, int py) {
156: if (hidden) {
157: return;
158: }
159:
160: if (bg != null) {
161: int x = getX() + px;
162: int y = getY() + py;
163: g.setColor(bg);
164: g.fillRect(x, y, width, height);
165: }
166: }
167:
168: public void paint(Graphics g, int px, int py) {
169: if (hidden) {
170: return;
171: }
172:
173: // We deliberately don't want the background painted yet, and
174: // since we have no children to get called there's no reason to
175: // call the super.
176: //super.paint(g, px, py);
177: int x = getX() + px;
178: int y = getY() + py;
179:
180: // No text to paint!
181: g.setColor(fg);
182: g.setFont(metrics.getFont());
183:
184: // determine the y coordinate to render the underline
185: // render underline or strikethrough if set.
186: if ((decoration & TextBox.UNDERLINE) != 0) {
187: int yTmp = (y + metrics.getHeight()) - metrics.getDescent()
188: + 1;
189: g.drawLine(x, yTmp, x + getWidth(), yTmp);
190: }
191:
192: if ((decoration & TextBox.STRIKE) != 0) {
193: int yTmp = (y + metrics.getHeight()) - metrics.getDescent();
194:
195: // move y coordinate above baseline
196: yTmp -= (int) (metrics.getAscent() * 0.4f);
197: g.drawLine(x, yTmp, x + getWidth(), yTmp);
198: }
199:
200: if ((decoration & TextBox.OVERLINE) != 0) {
201: g.drawLine(x, y, x + getWidth(), y);
202: }
203:
204: if (CssBox.paintSpaces) {
205: g.setColor(Color.GREEN);
206: g.drawRect(getAbsoluteX(), getAbsoluteY(), width, height);
207: }
208: }
209:
210: public FontMetrics getMetrics() {
211: return metrics;
212: }
213:
214: // Intended for testsuite
215: public boolean isUnderline() {
216: return (decoration & TextBox.UNDERLINE) != 0;
217: }
218:
219: // Intended for testsuite
220: public boolean isStrikeThrough() {
221: return (decoration & TextBox.STRIKE) != 0;
222: }
223:
224: // Intended for testsuite
225: public boolean isOverline() {
226: return (decoration & TextBox.OVERLINE) != 0;
227: }
228:
229: // Intended for testsuite
230: public Color getTextColor() {
231: return fg;
232: }
233:
234: public int getIntrinsicWidth() {
235: return width;
236: }
237:
238: public int getIntrinsicHeight() {
239: return height;
240: }
241:
242: public int getBaseline() {
243: return metrics.getHeight() - metrics.getDescent();
244: }
245:
246: /** Return the first position in the document of this space node */
247: // public Position getFirstPosition() {
248: public DomPosition getFirstPosition() {
249: if (node != null) {
250: // return new Position(node, getDomStartOffset(), Bias.FORWARD);
251: // return Position.create(node, getDomStartOffset(), Bias.FORWARD);
252: return webform.createDomPosition(node, getDomStartOffset(),
253: Bias.FORWARD);
254: } else {
255: // return Position.NONE;
256: return DomPosition.NONE;
257: }
258: }
259:
260: /** Return the last position in the document of this space node */
261: // public Position getLastPosition() {
262: public DomPosition getLastPosition() {
263: if (node != null) {
264: // return new Position(node, getDomEndOffset(), Bias.BACKWARD);
265: // return Position.create(node, getDomEndOffset(), Bias.BACKWARD);
266: return webform.createDomPosition(node, getDomEndOffset(),
267: Bias.BACKWARD);
268: } else {
269: // return Position.NONE;
270: return DomPosition.NONE;
271: }
272: }
273:
274: /** TODO: rename to computePosition */
275: // public Position computePosition(int px) {
276: public DomPosition computePosition(int px) {
277: if (node == null) {
278: // return Position.NONE;
279: return DomPosition.NONE;
280: }
281:
282: int offset = getDomStartOffset();
283:
284: /* XXX TODO -- look at the width of the space and pick the best fit
285: based on which middle half you're on
286: if ((px-textWidth) > charWidth/2) {
287: offset++;
288: }
289: */
290: // return new Position(node, offset, Bias.FORWARD); // XXX set bias to back if equal to endOffset?
291: // return Position.create(node, offset, Bias.FORWARD); // XXX set bias to back if equal to endOffset?
292: return webform.createDomPosition(node, offset, Bias.FORWARD); // XXX set bias to back if equal to endOffset?
293: }
294:
295: /** Return the bounding box of the character at the given position */
296: // public Rectangle getBoundingBox(Position pos) {
297: public Rectangle getBoundingBox(DomPosition pos) {
298: assert pos.getNode() == node;
299:
300: if (pos.getOffset() == beginOffset) {
301: return new Rectangle(getAbsoluteX(), getAbsoluteY(), width,
302: height);
303: } else {
304: return new Rectangle(getAbsoluteX() + width,
305: getAbsoluteY(), width, height);
306: }
307: }
308:
309: /** Return the node this text box is associated with */
310: public Text getNode() {
311: return node;
312: }
313:
314: /** Return the beginning offset within the text node that this box represents */
315: public int getDomStartOffset() {
316: // XXX Can I have &nbps;'s here?
317: if (beginDomOffset == -1) {
318: if (jspx == xhtml || jspx.indexOf('&') == -1) {
319: beginDomOffset = beginOffset;
320: } else {
321: // <markup_separation>
322: // beginDomOffset = MarkupServiceProvider.getDefault().
323: // getUnexpandedOffset(jspx, beginOffset);
324: // ====
325: // beginDomOffset = InSyncService.getProvider().getUnexpandedOffset(jspx, beginOffset);
326: beginDomOffset = webform.getDomProviderService()
327: .getUnexpandedOffset(jspx, beginOffset);
328: // </markup_separation>
329: }
330: }
331:
332: return beginDomOffset;
333: }
334:
335: /** Return the ending offset within the text node that this box represents */
336: public int getDomEndOffset() {
337: // XXX Can I have &nbps;'s here?
338: if (endDomOffset == -1) {
339: if (jspx == xhtml || jspx.indexOf('&') == -1) {
340: endDomOffset = endOffset;
341: } else {
342: // <markup_separation>
343: // endDomOffset = MarkupServiceProvider.getDefault().
344: // getUnexpandedOffset(jspx, endOffset);
345: // ====
346: // endDomOffset = InSyncService.getProvider().getUnexpandedOffset(jspx, endOffset);
347: endDomOffset = webform.getDomProviderService()
348: .getUnexpandedOffset(jspx, endOffset);
349: // </markup_separation>
350: }
351: }
352:
353: return endDomOffset;
354: }
355:
356: /**
357: * Given the position, return the previous position within this textbox if available
358: * or Position.NONE if not.
359: */
360: // public Position getPrev(Position pos) {
361: public DomPosition getPrev(DomPosition pos) {
362: if (node == null) {
363: // return Position.NONE;
364: return DomPosition.NONE;
365: }
366:
367: if (pos.getOffset() > getDomStartOffset()) {
368: // Jump over all spaces in one big jump
369: int offset = getDomStartOffset();
370:
371: // return new Position(node, offset, Bias.BACKWARD);
372: // return Position.create(node, offset, Bias.BACKWARD);
373: return webform.createDomPosition(node, offset,
374: Bias.BACKWARD);
375: } else {
376: // return Position.NONE;
377: return DomPosition.NONE;
378: }
379: }
380:
381: /**
382: * Given the position, return the next position within this textbox if available
383: * or Position.NONE if not.
384: */
385: // public Position getNext(Position pos) {
386: public DomPosition getNext(DomPosition pos) {
387: if (node == null) {
388: // return Position.NONE;
389: return DomPosition.NONE;
390: }
391:
392: if (pos.getOffset() < getDomEndOffset()) {
393: // Jump over all spaces in one big jump
394: int offset = getDomEndOffset();
395:
396: // return Position.create(node, offset, Bias.FORWARD);
397: return webform
398: .createDomPosition(node, offset, Bias.FORWARD);
399: } else {
400: // return Position.NONE;
401: return DomPosition.NONE;
402: }
403: }
404: }
|