intial code check in and renaming
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/.classpath b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/.classpath
new file mode 100644
index 0000000..d00d96b
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/.classpath
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+ <classpathentry kind="src" path="src"/>
+ <classpathentry exported="true" kind="lib" path="lib/flute.jar"/>
+ <classpathentry exported="true" kind="lib" path="lib/sac.jar"/>
+ <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
+ <classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>
+ <classpathentry kind="output" path="bin"/>
+</classpath>
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/.cvsignore b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/.cvsignore
new file mode 100644
index 0000000..fc08203
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/.cvsignore
@@ -0,0 +1,3 @@
+bin
+net.sf.vex.toolkit_1.0.0.jar
+vex-toolkit.jar
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/.project b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/.project
new file mode 100644
index 0000000..8670aa3
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/.project
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+ <name>org.eclipse.wst.xml.vex.core</name>
+ <comment></comment>
+ <projects>
+ </projects>
+ <buildSpec>
+ <buildCommand>
+ <name>org.eclipse.jdt.core.javabuilder</name>
+ <arguments>
+ </arguments>
+ </buildCommand>
+ <buildCommand>
+ <name>org.eclipse.pde.ManifestBuilder</name>
+ <arguments>
+ </arguments>
+ </buildCommand>
+ <buildCommand>
+ <name>org.eclipse.pde.SchemaBuilder</name>
+ <arguments>
+ </arguments>
+ </buildCommand>
+ <buildCommand>
+ <name>net.sourceforge.metrics.builder</name>
+ <arguments>
+ </arguments>
+ </buildCommand>
+ </buildSpec>
+ <natures>
+ <nature>org.eclipse.pde.PluginNature</nature>
+ <nature>org.eclipse.jdt.core.javanature</nature>
+ <nature>net.sourceforge.metrics.nature</nature>
+ </natures>
+</projectDescription>
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/.settings/org.eclipse.jdt.core.prefs b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/.settings/org.eclipse.jdt.core.prefs
new file mode 100644
index 0000000..310db2d
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/.settings/org.eclipse.jdt.core.prefs
@@ -0,0 +1,7 @@
+#Tue Jul 17 18:36:56 CEST 2007
+eclipse.preferences.version=1
+org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=disabled
+org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve
+org.eclipse.jdt.core.compiler.debug.lineNumber=generate
+org.eclipse.jdt.core.compiler.debug.localVariable=generate
+org.eclipse.jdt.core.compiler.debug.sourceFile=generate
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/.template b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/.template
new file mode 100644
index 0000000..d65e0f4
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/.template
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<form>
+ <p/><p><b>Tips on working with this plug-in project</b></p><li>For the view of the new plug-in at a glance, go to the <img href="pageImage"/><a href="OverviewPage">Overview</a>.</li><li>You can test the contributions of this plug-in by launching another instance of the workbench. On the <b>Run</b> menu, click <b>Run As</b> and choose <img href="runTimeWorkbenchImage"/><a href="action.run">Run-time Workbench</a> from the available choices.</li><li>You can add more functionality to this plug-in by adding extensions using the <a href="action.newExtension">New Extension Wizard</a>.</li><li>The plug-in project contains Java code that you can debug. Place breakpoints in Java classes. On the <b>Run</b> menu, select <b>Debug As</b> and choose <img href="runTimeWorkbenchImage"/><a href="action.debug">Run-time Workbench</a> from the available choices.</li>
+</form>
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/META-INF/MANIFEST.MF b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/META-INF/MANIFEST.MF
new file mode 100644
index 0000000..adc1898
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/META-INF/MANIFEST.MF
@@ -0,0 +1,32 @@
+Manifest-Version: 1.0
+Bundle-ManifestVersion: 2
+Bundle-Name: %plugin.name
+Bundle-Version: 0.5.0.qualifier
+Bundle-Vendor: Eclipse
+Bundle-SymbolicName: org.eclipse.wst.xml.vex.core;singleton:=true
+Require-Bundle: org.eclipse.core.runtime,
+ org.eclipse.ui,
+ org.junit
+Export-Package: com.wutka.dtd,
+ org.eclipse.wst.xml.vex.core.internal;x-internal:=true,
+ org.eclipse.wst.xml.vex.core.internal.action;x-internal:=true,
+ org.eclipse.wst.xml.vex.core.internal.core;x-internal:=true,
+ org.eclipse.wst.xml.vex.core.internal.css;x-internal:=true,
+ org.eclipse.wst.xml.vex.core.internal.dom;x-internal:=true,
+ org.eclipse.wst.xml.vex.core.internal.layout;x-internal:=true,
+ org.eclipse.wst.xml.vex.core.internal.swing;x-internal:=true,
+ org.eclipse.wst.xml.vex.core.internal.swt;x-internal:=true,
+ org.eclipse.wst.xml.vex.core.internal.undo;x-internal:=true,
+ org.eclipse.wst.xml.vex.core.internal.widget;x-internal:=true,
+ org.w3c.css.sac,
+ org.w3c.css.sac.helpers,
+ org.w3c.flute.parser,
+ org.w3c.flute.parser.selectors,
+ org.w3c.flute.util
+Bundle-Activator: org.eclipse.wst.xml.vex.core.internal.VEXCorePlugin
+Eclipse-LazyStart: true
+Bundle-Localization: plugin
+Bundle-ClassPath: lib/dtdparser120.jar,
+ lib/sac.jar,
+ lib/flute.jar,
+ .
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/build.properties b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/build.properties
new file mode 100644
index 0000000..e390377
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/build.properties
@@ -0,0 +1,19 @@
+bin.includes = plugin.xml,\
+ vex-toolkit.jar,\
+ lgpl.txt,\
+ plugin.properties,\
+ META-INF/,\
+ .,\
+ lib/,\
+ lib/dtdparser120.jar,\
+ lib/flute.jar,\
+ lib/sac.jar,\
+ bin/
+output.vex-toolkit.jar = bin/
+source.vex-toolkit.jar = src/
+src.includes = lgpl.txt,\
+ lib/,\
+ src/
+jars.compile.order = vex-toolkit.jar,\
+ .
+source.. = src/
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/lib/.cvsignore b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/lib/.cvsignore
new file mode 100644
index 0000000..8f7e8a6
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/lib/.cvsignore
@@ -0,0 +1 @@
+dtdparser120.jar
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/lib/flute.jar b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/lib/flute.jar
new file mode 100644
index 0000000..39a55d4
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/lib/flute.jar
Binary files differ
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/lib/sac.jar b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/lib/sac.jar
new file mode 100644
index 0000000..6ff0b70
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/lib/sac.jar
Binary files differ
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/plugin.properties b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/plugin.properties
new file mode 100644
index 0000000..d7d3e64
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/plugin.properties
@@ -0,0 +1 @@
+plugin.name=Vex Toolkit
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/VEXCorePlugin.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/VEXCorePlugin.java
new file mode 100644
index 0000000..4680695
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/VEXCorePlugin.java
@@ -0,0 +1,39 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * John Krasnay - initial API and implementation
+ * David Carver (STAR) - initial renaming
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal;
+
+import org.eclipse.ui.plugin.AbstractUIPlugin;
+import org.osgi.framework.BundleContext;
+
+public class VEXCorePlugin extends AbstractUIPlugin {
+
+ private static VEXCorePlugin instance;
+
+ public VEXCorePlugin() {
+ instance = this;
+ }
+
+ /**
+ * Returns the shared instance.
+ */
+ public static VEXCorePlugin getInstance() {
+ return instance;
+ }
+
+ public void start(BundleContext bundleContext) throws Exception {
+ super.start(bundleContext);
+ }
+
+ public void stop(BundleContext context) throws Exception {
+ super.stop(context);
+ }
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/action/AbstractVexAction.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/action/AbstractVexAction.java
new file mode 100644
index 0000000..da0e86e
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/action/AbstractVexAction.java
@@ -0,0 +1,34 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.action;
+
+import org.eclipse.wst.xml.vex.core.internal.widget.IVexWidget;
+
+/**
+ * Abstract Vex action. This class provides default implementations for
+ * all methods in IVexAction except for <code>run</code>.
+ */
+public abstract class AbstractVexAction implements IVexAction {
+
+ /**
+ * Class constructor.
+ */
+ public AbstractVexAction() {
+ }
+
+ /**
+ * Returns <code>true</code>.
+ */
+ public boolean isEnabled(IVexWidget vexWidget) {
+ return true;
+ }
+
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/action/ActionUtils.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/action/ActionUtils.java
new file mode 100644
index 0000000..d757ef2
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/action/ActionUtils.java
@@ -0,0 +1,494 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.action;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.eclipse.wst.xml.vex.core.internal.core.IntRange;
+import org.eclipse.wst.xml.vex.core.internal.css.CSS;
+import org.eclipse.wst.xml.vex.core.internal.css.StyleSheet;
+import org.eclipse.wst.xml.vex.core.internal.dom.Document;
+import org.eclipse.wst.xml.vex.core.internal.dom.Element;
+import org.eclipse.wst.xml.vex.core.internal.dom.Node;
+import org.eclipse.wst.xml.vex.core.internal.layout.BlockBox;
+import org.eclipse.wst.xml.vex.core.internal.layout.Box;
+import org.eclipse.wst.xml.vex.core.internal.layout.ElementOrRangeCallback;
+import org.eclipse.wst.xml.vex.core.internal.layout.LayoutUtils;
+import org.eclipse.wst.xml.vex.core.internal.layout.TableRowBox;
+import org.eclipse.wst.xml.vex.core.internal.widget.IBoxFilter;
+import org.eclipse.wst.xml.vex.core.internal.widget.IVexWidget;
+
+
+/**
+ * Static helper methods used across actions.
+ */
+public class ActionUtils {
+
+ public static class RowColumnInfo {
+ public Object row;
+ public Object cell;
+ public int rowIndex;
+ public int cellIndex;
+ public int rowCount;
+ public int columnCount;
+ public int maxColumnCount;
+ }
+
+ /**
+ * Clone the table cells from the given TableRowBox to the current offset in vexWidget.
+ * @param vexWidget IVexWidget to modify.
+ * @param tr TableRowBox whose cells are to be cloned.
+ * @param moveToFirstCell TODO
+ */
+ public static void cloneTableCells(final IVexWidget vexWidget, final TableRowBox tr, final boolean moveToFirstCell) {
+ vexWidget.doWork(new Runnable() {
+ public void run() {
+
+ int offset = vexWidget.getCaretOffset();
+
+ boolean firstCellIsAnonymous = false;
+ Box[] cells = tr.getChildren();
+ for (int i = 0; i < cells.length; i++) {
+ if (cells[i].isAnonymous()) {
+ vexWidget.insertText(" ");
+ if (i == 0) {
+ firstCellIsAnonymous = true;
+ }
+ } else {
+ vexWidget.insertElement((Element) cells[i].getElement().clone());
+ vexWidget.moveBy(+1);
+ }
+ }
+
+ if (moveToFirstCell) {
+ vexWidget.moveTo(offset + 1);
+ if (firstCellIsAnonymous) {
+ vexWidget.moveBy(-1, true);
+ }
+ }
+ }
+ });
+ }
+
+ /**
+ * Duplicate the given table row, inserting a new empty one below it. The new
+ * row contains empty children corresponding to the given row's children.
+ * @param vexWidget IVexWidget with which we're working
+ * @param tr TableRowBox to be duplicated.
+ */
+ public static void duplicateTableRow(final IVexWidget vexWidget, final TableRowBox tr) {
+ vexWidget.doWork(new Runnable() {
+ public void run() {
+
+ vexWidget.moveTo(tr.getEndOffset());
+
+ if (!tr.isAnonymous()) {
+ vexWidget.moveBy(+1); // Move past sentinel in current row
+ vexWidget.insertElement((Element) tr.getElement().clone());
+ }
+
+ cloneTableCells(vexWidget, tr, true);
+ }
+ });
+ }
+
+ /**
+ * Returns true if the given element or range is at least partially selected.
+ *
+ * @param vexWidget IVexWidget being tested.
+ * @param elementOrRange Element or IntRange being tested.
+ */
+ public static boolean elementOrRangeIsPartiallySelected(IVexWidget vexWidget, Object elementOrRange) {
+ IntRange range = getInnerRange(elementOrRange);
+ return range.getEnd() >= vexWidget.getSelectionStart()
+ && range.getStart() <= vexWidget.getSelectionEnd();
+ }
+
+ /**
+ * Returns the zero-based index of the table column containing the
+ * current offset. Returns -1 if we are not inside a table.
+ */
+ public static int getCurrentColumnIndex(IVexWidget vexWidget) {
+
+ Element row = getCurrentTableRow(vexWidget);
+
+ if (row == null) {
+ return -1;
+ }
+
+ final int offset = vexWidget.getCaretOffset();
+ final int[] column = new int[] { -1 };
+ LayoutUtils.iterateTableCells(vexWidget.getStyleSheet(), row, new ElementOrRangeCallback() {
+ private int i = 0;
+ public void onElement(Element child, String displayStyle) {
+ if (offset > child.getStartOffset() && offset <= child.getEndOffset()) {
+ column[0] = i;
+ }
+ i++;
+ }
+ public void onRange(Element parent, int startOffset, int endOffset) {
+ i++;
+ }
+ });
+
+ return column[0];
+ }
+
+ /**
+ * Returns the innermost Element with style table-row containing the caret,
+ * or null if no such element exists.
+ * @param vexWidget IVexWidget to use.
+ */
+ public static Element getCurrentTableRow(IVexWidget vexWidget) {
+
+ StyleSheet ss = vexWidget.getStyleSheet();
+ Element element = vexWidget.getCurrentElement();
+
+ while (element != null) {
+ if (ss.getStyles(element).getDisplay().equals(CSS.TABLE_ROW)) {
+ return element;
+ }
+ element = element.getParent();
+ }
+
+ return null;
+ }
+
+ /**
+ * Returns the start offset of the next sibling of the parent element.
+ * Returns -1 if there is no previous sibling in the parent.
+ * @param vexWidget VexWidget to use.
+ */
+ public static int getPreviousSiblingStart(IVexWidget vexWidget) {
+ int startOffset;
+
+ if (vexWidget.hasSelection()) {
+ startOffset = vexWidget.getSelectionStart();
+ } else {
+ Box box = vexWidget.findInnermostBox(new IBoxFilter() {
+ public boolean matches(Box box) {
+ return box instanceof BlockBox
+ && box.getElement() != null;
+ }
+ });
+
+ if (box.getElement() == vexWidget.getDocument().getRootElement()) {
+ return -1;
+ }
+
+ startOffset = box.getElement().getStartOffset();
+ }
+
+ int previousSiblingStart = -1;
+ Element parent = vexWidget.getDocument().getElementAt(startOffset);
+ Node[] children = parent.getChildNodes();
+ for (int i = 0; i < children.length; i++) {
+ Node child = children[i];
+ if (startOffset == child.getStartOffset()) {
+ break;
+ }
+ previousSiblingStart = child.getStartOffset();
+ }
+ return previousSiblingStart;
+ }
+
+ /**
+ * Returns an array of the selected block boxes. Text nodes between boxes
+ * are not returned. If the selection does not enclose any block boxes,
+ * returns an empty array.
+ * @param vexWidget VexWidget to use.
+ */
+ public static BlockBox[] getSelectedBlockBoxes(final IVexWidget vexWidget) {
+
+ if (!vexWidget.hasSelection()) {
+ return new BlockBox[0];
+ }
+
+ Box parent = vexWidget.findInnermostBox(new IBoxFilter() {
+ public boolean matches(Box box) {
+ System.out.println("Matching " + box);
+ return box instanceof BlockBox
+ && box.getStartOffset() <= vexWidget.getSelectionStart()
+ && box.getEndOffset() >= vexWidget.getSelectionEnd();
+ }
+ });
+
+ System.out.println("Matched " + parent);
+
+ List blockList = new ArrayList();
+
+ Box[] children = parent.getChildren();
+ System.out.println("Parent has " + children.length + " children");
+ for (int i = 0; i < children.length; i++) {
+ Box child = children[i];
+ if (child instanceof BlockBox
+ && child.getStartOffset() >= vexWidget.getSelectionStart()
+ && child.getEndOffset() <= vexWidget.getSelectionEnd()) {
+ System.out.println(" adding " + child);
+ blockList.add(child);
+ } else {
+ System.out.println(" skipping " + child);
+ }
+
+ }
+
+ return (BlockBox[]) blockList.toArray(new BlockBox[blockList.size()]);
+ }
+
+ /**
+ * Returns the currently selected table rows, or the current row if
+ * ther is no selection. If no row can be found, returns an empty array.
+ * @param vexWidget IVexWidget to use.
+ */
+ public static SelectedRows getSelectedTableRows(final IVexWidget vexWidget) {
+
+ final SelectedRows selected = new SelectedRows();
+
+ ActionUtils.iterateTableCells(vexWidget, new TableCellCallback() {
+ public void startRow(Object row, int rowIndex) {
+ if (ActionUtils.elementOrRangeIsPartiallySelected(vexWidget, row)) {
+ if (selected.rows == null) {
+ selected.rows = new ArrayList();
+ }
+ selected.rows.add(row);
+ } else {
+ if (selected.rows == null) {
+ selected.rowBefore = row;
+ } else {
+ if (selected.rowAfter == null) {
+ selected.rowAfter = row;
+ }
+ }
+ }
+ }
+ public void onCell(Object row, Object cell, int rowIndex, int cellIndex) {
+ }
+ public void endRow(Object row, int rowIndex) {
+ }
+ });
+
+ return selected;
+ }
+
+ public static void iterateTableCells(IVexWidget vexWidget, final TableCellCallback callback) {
+
+ final StyleSheet ss = vexWidget.getStyleSheet();
+
+ iterateTableRows(vexWidget, new ElementOrRangeCallback() {
+
+ final private int[] rowIndex = { 0 };
+
+ public void onElement(final Element row, String displayStyle) {
+
+ callback.startRow(row, rowIndex[0]);
+
+ LayoutUtils.iterateTableCells(ss, row, new ElementOrRangeCallback() {
+ private int cellIndex = 0;
+ public void onElement(Element cell, String displayStyle) {
+ callback.onCell(row, cell, rowIndex[0], cellIndex);
+ cellIndex++;
+ }
+ public void onRange(Element parent, int startOffset, int endOffset) {
+ callback.onCell(row, new IntRange(startOffset, endOffset), rowIndex[0], cellIndex);
+ cellIndex++;
+ }
+ });
+
+ callback.endRow(row, rowIndex[0]);
+
+ rowIndex[0]++;
+ }
+
+ public void onRange(Element parent, final int startOffset, final int endOffset) {
+
+ final IntRange row = new IntRange(startOffset, endOffset);
+ callback.startRow(row, rowIndex[0]);
+
+ LayoutUtils.iterateTableCells(ss, parent, startOffset, endOffset, new ElementOrRangeCallback() {
+ private int cellIndex = 0;
+ public void onElement(Element cell, String displayStyle) {
+ callback.onCell(row, cell, rowIndex[0], cellIndex);
+ cellIndex++;
+ }
+ public void onRange(Element parent, int startOffset, int endOffset) {
+ callback.onCell(row, new IntRange(startOffset, endOffset), rowIndex[0], cellIndex);
+ cellIndex++;
+ }
+ });
+
+ callback.endRow(row, rowIndex[0]);
+
+ rowIndex[0]++;
+ }
+ });
+ }
+
+ /**
+ * Returns a RowColumnInfo structure containing information about the table
+ * containing the caret. Returns null if the caret is not currently inside
+ * a table.
+ *
+ * @param vexWidget IVexWidget to inspect.
+ */
+ public static RowColumnInfo getRowColumnInfo(IVexWidget vexWidget) {
+
+ final boolean[] found = new boolean[1];
+ final RowColumnInfo[] rcInfo = new RowColumnInfo[] { new RowColumnInfo() };
+ final int offset = vexWidget.getCaretOffset();
+
+ rcInfo[0].cellIndex = -1;
+ rcInfo[0].rowIndex = -1;
+
+ iterateTableCells(vexWidget, new TableCellCallback() {
+
+ int rowColumnCount;
+
+ public void startRow(Object row, int rowIndex) {
+ rowColumnCount = 0;
+ }
+
+ public void onCell(Object row, Object cell, int rowIndex, int cellIndex) {
+ found[0] = true;
+ if (LayoutUtils.elementOrRangeContains(row, offset)) {
+ rcInfo[0].row = row;
+ rcInfo[0].rowIndex = rowIndex;
+ rcInfo[0].columnCount++;
+
+ if (LayoutUtils.elementOrRangeContains(cell, offset)) {
+ rcInfo[0].cell = cell;
+ rcInfo[0].cellIndex = cellIndex;
+ }
+ }
+
+ rowColumnCount++;
+ }
+
+ public void endRow(Object row, int rowIndex) {
+ rcInfo[0].rowCount++;
+ rcInfo[0].maxColumnCount = Math.max(rcInfo[0].maxColumnCount, rowColumnCount);
+ }
+ });
+
+ if (found[0]) {
+ return rcInfo[0];
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Iterate over all rows in the table containing the caret.
+ *
+ * @param vexWidget IVexWidget to iterate over.
+ * @param callback Caller-provided callback that this method calls for
+ * each row in the current table.
+ */
+ public static void iterateTableRows(IVexWidget vexWidget, ElementOrRangeCallback callback) {
+
+ final StyleSheet ss = vexWidget.getStyleSheet();
+ final Document doc = vexWidget.getDocument();
+ final int offset = vexWidget.getCaretOffset();
+
+ // This may or may not be a table
+ // In any case, it's the element that contains the top-level table children
+ Element table = doc.getElementAt(offset);
+
+ while (table != null && !LayoutUtils.isTableChild(ss, table)) {
+ table = table.getParent();
+ }
+
+ while (table != null && LayoutUtils.isTableChild(ss, table)) {
+ table = table.getParent();
+ }
+
+ if (table == null || table.getParent() == null) {
+ return;
+ }
+
+ final List tableChildren = new ArrayList();
+ final boolean[] found = new boolean[] { false };
+ LayoutUtils.iterateChildrenByDisplayStyle(ss, LayoutUtils.TABLE_CHILD_STYLES, table, new ElementOrRangeCallback() {
+ public void onElement(Element child, String displayStyle) {
+ if (offset >= child.getStartOffset() && offset <= child.getEndOffset()) {
+ found[0] = true;
+ }
+ tableChildren.add(child);
+ }
+ public void onRange(Element parent, int startOffset, int endOffset) {
+ if (!found[0]) {
+ tableChildren.clear();
+ }
+ }
+ });
+
+ if (!found[0]) {
+ return;
+ }
+
+ int startOffset = ((Element) tableChildren.get(0)).getStartOffset();
+ int endOffset = ((Element) tableChildren.get(tableChildren.size() - 1)).getEndOffset() + 1;
+ LayoutUtils.iterateTableRows(ss, table, startOffset, endOffset, callback);
+ }
+
+ /**
+ * Returns an IntRange representing the offsets inside the given Element or
+ * IntRange. If an Element is passed, returns the offsets inside the
+ * sentinels. If an IntRange is passed it is returned directly.
+ *
+ * @param elementOrRange Element or IntRange to be inspected.
+ */
+ public static IntRange getInnerRange(Object elementOrRange) {
+ if (elementOrRange instanceof Element) {
+ Element element = (Element) elementOrRange;
+ return new IntRange(element.getStartOffset() + 1, element.getEndOffset());
+ } else {
+ return (IntRange) elementOrRange;
+ }
+ }
+
+ /**
+ * Returns an IntRange representing the offsets outside the given Element or
+ * IntRange. If an Element is passed, returns the offsets outside the
+ * sentinels. If an IntRange is passed it is returned directly.
+ *
+ * @param elementOrRange Element or IntRange to be inspected.
+ */
+ public static IntRange getOuterRange(Object elementOrRange) {
+ if (elementOrRange instanceof Element) {
+ Element element = (Element) elementOrRange;
+ return new IntRange(element.getStartOffset(), element.getEndOffset() + 1);
+ } else {
+ return (IntRange) elementOrRange;
+ }
+ }
+
+
+ public static class SelectedRows {
+
+ private SelectedRows() {
+ }
+
+ public List getRows() {
+ return this.rows;
+ }
+ public Object getRowBefore() {
+ return this.rowBefore;
+ }
+ public Object getRowAfter() {
+ return this.rowAfter;
+ }
+
+ private List rows;
+ private Object rowBefore;
+ private Object rowAfter;
+ }
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/action/DeleteColumnAction.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/action/DeleteColumnAction.java
new file mode 100644
index 0000000..802eb34
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/action/DeleteColumnAction.java
@@ -0,0 +1,68 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.action;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.eclipse.wst.xml.vex.core.internal.core.IntRange;
+import org.eclipse.wst.xml.vex.core.internal.widget.IVexWidget;
+
+
+/**
+ * Delete the table column containing the caret.
+ */
+public class DeleteColumnAction extends AbstractVexAction {
+
+ public void run(final IVexWidget vexWidget) {
+
+ vexWidget.doWork(new Runnable() {
+ public void run() {
+
+ final ActionUtils.RowColumnInfo rcInfo = ActionUtils.getRowColumnInfo(vexWidget);
+
+ if (rcInfo == null) {
+ return;
+ }
+
+ final List cellsToDelete = new ArrayList();
+ ActionUtils.iterateTableCells(vexWidget, new TableCellCallback() {
+ public void startRow(Object row, int rowIndex) {
+ }
+ public void onCell(Object row, Object cell, int rowIndex, int cellIndex) {
+ if (cellIndex == rcInfo.cellIndex) {
+ cellsToDelete.add(cell);
+ }
+ }
+ public void endRow(Object row, int rowIndex) {
+ }
+ });
+
+ // Iterate the deletions in reverse, so that we don't mess up
+ // offsets that are in anonymous cells, which are not stored
+ // as Positions.
+ for (int i = cellsToDelete.size() - 1; i >= 0; i--) {
+ Object cell = cellsToDelete.get(i);
+ IntRange range = ActionUtils.getOuterRange(cell);
+ vexWidget.moveTo(range.getStart());
+ vexWidget.moveTo(range.getEnd(), true);
+ vexWidget.deleteSelection();
+ }
+ }
+ });
+
+ }
+
+ public boolean isEnabled(IVexWidget vexWidget) {
+ return ActionUtils.getCurrentColumnIndex(vexWidget) != -1;
+ }
+
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/action/DeleteRowAction.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/action/DeleteRowAction.java
new file mode 100644
index 0000000..48d50c4
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/action/DeleteRowAction.java
@@ -0,0 +1,48 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.action;
+
+import java.util.List;
+
+import org.eclipse.wst.xml.vex.core.internal.widget.IVexWidget;
+
+
+/**
+ * Delete selected table rows.
+ */
+public class DeleteRowAction extends AbstractVexAction {
+
+ public void run(final IVexWidget vexWidget) {
+
+ final List rows = ActionUtils.getSelectedTableRows(vexWidget).getRows();
+
+ if (rows == null) {
+ return;
+ }
+
+ vexWidget.doWork(new Runnable() {
+ public void run() {
+ int startOffset = ActionUtils.getOuterRange(rows.get(0)).getStart();
+ int endOffset = ActionUtils.getOuterRange(rows.get(rows.size() - 1)).getEnd();
+
+ vexWidget.moveTo(startOffset);
+ vexWidget.moveTo(endOffset, true);
+ vexWidget.deleteSelection();
+ }
+ });
+
+ }
+
+ public boolean isEnabled(IVexWidget vexWidget) {
+ return ActionUtils.getSelectedTableRows(vexWidget).getRows() != null;
+ }
+
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/action/DuplicateSelectionAction.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/action/DuplicateSelectionAction.java
new file mode 100644
index 0000000..7f41b93
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/action/DuplicateSelectionAction.java
@@ -0,0 +1,47 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.action;
+
+import org.eclipse.wst.xml.vex.core.internal.dom.Element;
+import org.eclipse.wst.xml.vex.core.internal.widget.IVexWidget;
+
+
+/**
+ * Duplicates current selection or element.
+ */
+public class DuplicateSelectionAction extends AbstractVexAction {
+
+ public void run(final IVexWidget vexWidget) {
+
+ vexWidget.doWork(new Runnable() {
+ public void run() {
+ if (!vexWidget.hasSelection()) {
+ Element element = vexWidget.getCurrentElement();
+ if (element.getParent() == null) {
+ // Can't dup the root element
+ return;
+ }
+ vexWidget.moveTo(element.getStartOffset());
+ vexWidget.moveTo(element.getEndOffset() + 1, true);
+ }
+
+ vexWidget.copySelection();
+ int startOffset = vexWidget.getSelectionEnd();
+ vexWidget.moveTo(startOffset);
+ vexWidget.paste();
+ int endOffset = vexWidget.getCaretOffset();
+ vexWidget.moveTo(startOffset);
+ vexWidget.moveTo(endOffset, true);
+ }
+ });
+ }
+
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/action/IVexAction.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/action/IVexAction.java
new file mode 100644
index 0000000..aa9ecfd
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/action/IVexAction.java
@@ -0,0 +1,31 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.action;
+
+import org.eclipse.wst.xml.vex.core.internal.widget.IVexWidget;
+
+/**
+ * Interface implemented by command objects that can act on a VexWidget.
+ */
+public interface IVexAction {
+
+ /**
+ * Performs the action on the VexWidget.
+ * @param vexWidget IVexWidget on which the action is to be performed.
+ */
+ public void run(IVexWidget vexWidget);
+
+ /**
+ * Returns true if the action is valid for the given VexWidget.
+ * @param vexWidget IVexWidget against which to test validity.
+ */
+ public boolean isEnabled(IVexWidget vexWidget);
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/action/InsertColumnAfterAction.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/action/InsertColumnAfterAction.java
new file mode 100644
index 0000000..5fd253d
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/action/InsertColumnAfterAction.java
@@ -0,0 +1,69 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.action;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+import org.eclipse.wst.xml.vex.core.internal.dom.Element;
+import org.eclipse.wst.xml.vex.core.internal.widget.IVexWidget;
+
+
+/**
+ * Inserts a single table column after the current one.
+ */
+public class InsertColumnAfterAction extends AbstractVexAction {
+
+ public void run(final IVexWidget vexWidget) {
+
+ final int indexToDup = ActionUtils.getCurrentColumnIndex(vexWidget);
+ if (indexToDup == -1) {
+ return;
+ }
+
+ vexWidget.doWork(new Runnable() {
+ public void run() {
+
+ final List cellsToDup = new ArrayList();
+ ActionUtils.iterateTableCells(vexWidget, new TableCellCallback() {
+ public void startRow(Object row, int rowIndex) {
+ }
+ public void onCell(Object row, Object cell, int rowIndex, int cellIndex) {
+ if (cellIndex == indexToDup && cell instanceof Element) {
+ cellsToDup.add(cell);
+ }
+ }
+ public void endRow(Object row, int rowIndex) {
+ }
+ });
+
+ int finalOffset = -1;
+ for (Iterator it = cellsToDup.iterator(); it.hasNext();) {
+ Element element = (Element) it.next();
+ if (finalOffset == -1) {
+ finalOffset = element.getStartOffset() + 1;
+ }
+ vexWidget.moveTo(element.getEndOffset() + 1);
+ vexWidget.insertElement((Element) element.clone());
+ }
+
+ if (finalOffset != -1) {
+ vexWidget.moveTo(finalOffset);
+ }
+ }
+ });
+ }
+
+ public boolean isEnabled(IVexWidget vexWidget) {
+ return ActionUtils.getCurrentColumnIndex(vexWidget) != -1;
+ }
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/action/InsertColumnBeforeAction.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/action/InsertColumnBeforeAction.java
new file mode 100644
index 0000000..f8f1e89
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/action/InsertColumnBeforeAction.java
@@ -0,0 +1,72 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.action;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+import org.eclipse.wst.xml.vex.core.internal.dom.Element;
+import org.eclipse.wst.xml.vex.core.internal.widget.IVexWidget;
+
+
+/**
+ * Inserts a single table column before the current one.
+ */
+public class InsertColumnBeforeAction extends AbstractVexAction {
+
+ public void run(final IVexWidget vexWidget) {
+
+ vexWidget.doWork(new Runnable() {
+ public void run() {
+
+ final int indexToDup = ActionUtils.getCurrentColumnIndex(vexWidget);
+ if (indexToDup == -1) {
+ return;
+ }
+
+ final List cellsToDup = new ArrayList();
+ ActionUtils.iterateTableCells(vexWidget, new TableCellCallback() {
+ public void startRow(Object row, int rowIndex) {
+ }
+ public void onCell(Object row, Object cell, int rowIndex, int cellIndex) {
+ if (cellIndex == indexToDup && cell instanceof Element) {
+ cellsToDup.add(cell);
+ }
+ }
+ public void endRow(Object row, int rowIndex) {
+ }
+ });
+
+ int finalOffset = -1;
+ for (Iterator it = cellsToDup.iterator(); it.hasNext();) {
+ Element element = (Element) it.next();
+ if (finalOffset == -1) {
+ finalOffset = element.getStartOffset() + 1;
+ }
+ vexWidget.moveTo(element.getStartOffset());
+ vexWidget.insertElement((Element) element.clone());
+ }
+
+ if (finalOffset != -1) {
+ vexWidget.moveTo(finalOffset);
+ }
+
+ }
+ });
+
+ }
+
+ public boolean isEnabled(IVexWidget vexWidget) {
+ return ActionUtils.getCurrentColumnIndex(vexWidget) != -1;
+ }
+
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/action/InsertRowAboveAction.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/action/InsertRowAboveAction.java
new file mode 100644
index 0000000..00b227a
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/action/InsertRowAboveAction.java
@@ -0,0 +1,22 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.action;
+
+
+/**
+ * Inserts one or more table rows above the currently selected one(s).
+ */
+public class InsertRowAboveAction extends InsertRowAction {
+
+ public InsertRowAboveAction() {
+ super(true);
+ }
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/action/InsertRowAction.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/action/InsertRowAction.java
new file mode 100644
index 0000000..7e44ef9
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/action/InsertRowAction.java
@@ -0,0 +1,144 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.action;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.eclipse.wst.xml.vex.core.internal.dom.Element;
+import org.eclipse.wst.xml.vex.core.internal.widget.IVexWidget;
+
+
+/**
+ * Inserts one or more table rows either above or below the currently selected one(s).
+ * This class is meant as a base class for InsertRowAboveAction and
+ * InsertRowBelowAction.
+ */
+public class InsertRowAction extends AbstractVexAction {
+
+ /**
+ * Class constructor.
+ * @param above If true, the new rows are inserted above the currently
+ * selected ones, else they are inserted below.
+ */
+ public InsertRowAction(boolean above) {
+ this.above = above;
+ }
+
+ public void run(final IVexWidget vexWidget) {
+
+ vexWidget.doWork(new Runnable() {
+ public void run() {
+
+ final List rowsToInsert = new ArrayList();
+ final List rowCellsToInsert = new ArrayList();
+
+ ActionUtils.iterateTableCells(vexWidget, new TableCellCallback() {
+
+ boolean rowSelected;
+ List cellsToInsert;
+
+ public void startRow(Object row, int rowIndex) {
+ rowSelected = ActionUtils.elementOrRangeIsPartiallySelected(vexWidget, row);
+
+ if (rowSelected) {
+ cellsToInsert = new ArrayList();
+ }
+ }
+
+ public void onCell(Object row, Object cell, int rowIndex, int cellIndex) {
+ if (rowSelected) {
+ cellsToInsert.add(cell);
+ }
+ }
+
+ public void endRow(Object row, int rowIndex) {
+ if (rowSelected) {
+ rowsToInsert.add(row);
+ rowCellsToInsert.add(cellsToInsert);
+ }
+ }
+
+ });
+
+ if (rowsToInsert.size() == 0) {
+ return;
+ }
+
+
+ //
+ // save the caret offset so that we return just inside the first
+ // table cell
+ //
+ // (innerOffset - outerOffset) represents the final offset of
+ // the caret, relative to the insertion point of the new rows
+ //
+ int outerOffset = ActionUtils.getOuterRange(rowsToInsert.get(0)).getStart();
+ int innerOffset;
+ List firstCells = (List) rowCellsToInsert.get(0);
+ if (firstCells.size() == 0) {
+ innerOffset = ActionUtils.getInnerRange(rowsToInsert.get(0)).getStart();
+ } else {
+ innerOffset = ActionUtils.getInnerRange(firstCells.get(0)).getStart();
+ }
+
+ int insertOffset;
+ if (above) {
+ insertOffset = ActionUtils.getOuterRange(rowsToInsert.get(0)).getStart();
+ } else {
+ Object lastRow = rowsToInsert.get(rowsToInsert.size() - 1);
+ insertOffset = ActionUtils.getOuterRange(lastRow).getEnd();
+ }
+
+ int finalOffset = insertOffset + (innerOffset - outerOffset);
+
+ vexWidget.moveTo(insertOffset);
+
+ for (int i = 0; i < rowsToInsert.size(); i++) {
+
+ Object row = rowsToInsert.get(i);
+
+ if (row instanceof Element) {
+ vexWidget.insertElement((Element) ((Element) row).clone());
+ }
+
+ List cellsToInsert = (List) rowCellsToInsert.get(i);
+
+ for (int j = 0; j < cellsToInsert.size(); j++) {
+ Object cell = cellsToInsert.get(j);
+ if (cell instanceof Element) {
+ vexWidget.insertElement((Element) ((Element) cell).clone());
+ vexWidget.moveBy(+1);
+ } else {
+ vexWidget.insertText(" ");
+ }
+ }
+
+ if (row instanceof Element) {
+ vexWidget.moveBy(+1);
+ }
+
+ }
+
+ vexWidget.moveTo(finalOffset);
+ }
+ });
+
+ }
+
+ public boolean isEnabled(IVexWidget vexWidget) {
+ // TODO only enable (a) if rows are selected, and (b) if not inserting
+ // adjacent anonymous rows
+ return true;
+ }
+
+ private boolean above;
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/action/InsertRowBelowAction.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/action/InsertRowBelowAction.java
new file mode 100644
index 0000000..9ce6ac3
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/action/InsertRowBelowAction.java
@@ -0,0 +1,22 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.action;
+
+
+/**
+ * Inserts one or more table rows below the currently selected one(s).
+ */
+public class InsertRowBelowAction extends InsertRowAction {
+
+ public InsertRowBelowAction() {
+ super(false);
+ }
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/action/MoveColumnLeftAction.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/action/MoveColumnLeftAction.java
new file mode 100644
index 0000000..946ea6c
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/action/MoveColumnLeftAction.java
@@ -0,0 +1,91 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.action;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.eclipse.wst.xml.vex.core.internal.core.IntRange;
+import org.eclipse.wst.xml.vex.core.internal.widget.IVexWidget;
+
+
+/**
+ * Moves the current table column to the left.
+ */
+public class MoveColumnLeftAction extends AbstractVexAction {
+
+ public void run(final IVexWidget vexWidget) {
+
+ final ActionUtils.RowColumnInfo rcInfo = ActionUtils.getRowColumnInfo(vexWidget);
+
+ if (rcInfo == null || rcInfo.cellIndex < 1) {
+ return;
+ }
+
+ vexWidget.doWork(true, new Runnable() {
+ public void run() {
+
+ // Cells to the left of the current column
+ final List sourceCells = new ArrayList();
+
+ // Cells in the current column
+ final List destCells = new ArrayList();
+
+ ActionUtils.iterateTableCells(vexWidget, new TableCellCallback() {
+ Object prevCell = null;
+ public void startRow(Object row, int rowIndex) {
+ }
+ public void onCell(Object row, Object cell, int rowIndex, int cellIndex) {
+ if (cellIndex == rcInfo.cellIndex) {
+ sourceCells.add(this.prevCell);
+ destCells.add(cell);
+ } else if (cellIndex == rcInfo.cellIndex - 1) {
+ this.prevCell = cell;
+ }
+ }
+ public void endRow(Object row, int rowIndex) {
+ }
+ });
+
+ // Iterate the deletions in reverse, so that we don't mess up
+ // offsets that are in anonymous cells, which are not stored
+ // as Positions.
+ //
+ // Also, to preserve the current caret position, we don't cut
+ // and paste the current column. Instead, we cut the column
+ // to the left of the current column and paste it on the right.
+ for (int i = sourceCells.size() - 1; i >= 0; i--) {
+
+ Object source = sourceCells.get(i);
+ final IntRange sourceRange = ActionUtils.getOuterRange(source);
+
+ Object dest = destCells.get(i);
+ vexWidget.moveTo(ActionUtils.getOuterRange(dest).getEnd());
+
+ vexWidget.savePosition(new Runnable() {
+ public void run() {
+ vexWidget.moveTo(sourceRange.getStart());
+ vexWidget.moveTo(sourceRange.getEnd(), true);
+ vexWidget.cutSelection();
+ }
+ });
+
+ vexWidget.paste();
+ }
+ }
+ });
+ }
+
+ public boolean isEnabled(IVexWidget vexWidget) {
+ ActionUtils.RowColumnInfo rcInfo = ActionUtils.getRowColumnInfo(vexWidget);
+ return rcInfo != null && rcInfo.cellIndex > 0;
+ }
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/action/MoveColumnRightAction.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/action/MoveColumnRightAction.java
new file mode 100644
index 0000000..784c00e
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/action/MoveColumnRightAction.java
@@ -0,0 +1,91 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.action;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.eclipse.wst.xml.vex.core.internal.core.IntRange;
+import org.eclipse.wst.xml.vex.core.internal.widget.IVexWidget;
+
+
+/**
+ * Moves the current table column to the right.
+ */
+public class MoveColumnRightAction extends AbstractVexAction {
+
+ public void run(final IVexWidget vexWidget) {
+
+ final ActionUtils.RowColumnInfo rcInfo = ActionUtils.getRowColumnInfo(vexWidget);
+
+ if (rcInfo == null || rcInfo.cellIndex >= rcInfo.maxColumnCount - 1) {
+ return;
+ }
+
+ vexWidget.doWork(true, new Runnable() {
+ public void run() {
+
+ // Cells to the right of the current column
+ final List sourceCells = new ArrayList();
+
+ // Cells in the current column
+ final List destCells = new ArrayList();
+
+ ActionUtils.iterateTableCells(vexWidget, new TableCellCallback() {
+ Object thisCell = null;
+ public void startRow(Object row, int rowIndex) {
+ }
+ public void onCell(Object row, Object cell, int rowIndex, int cellIndex) {
+ if (cellIndex == rcInfo.cellIndex) {
+ this.thisCell = cell;
+ } else if (cellIndex == rcInfo.cellIndex + 1) {
+ sourceCells.add(cell);
+ destCells.add(this.thisCell);
+ }
+ }
+ public void endRow(Object row, int rowIndex) {
+ }
+ });
+
+ // Iterate the deletions in reverse, so that we don't mess up
+ // offsets that are in anonymous cells, which are not stored
+ // as Positions.
+ //
+ // Also, to preserve the current caret position, we don't cut
+ // and paste the current column. Instead, we cut the column
+ // to the right of the current column and paste it on the left.
+ for (int i = sourceCells.size() - 1; i >= 0; i--) {
+
+ Object source = sourceCells.get(i);
+ final IntRange sourceRange = ActionUtils.getOuterRange(source);
+
+ Object dest = destCells.get(i);
+ vexWidget.moveTo(ActionUtils.getOuterRange(dest).getStart());
+
+ vexWidget.savePosition(new Runnable() {
+ public void run() {
+ vexWidget.moveTo(sourceRange.getStart());
+ vexWidget.moveTo(sourceRange.getEnd(), true);
+ vexWidget.cutSelection();
+ }
+ });
+
+ vexWidget.paste();
+ }
+ }
+ });
+ }
+
+ public boolean isEnabled(IVexWidget vexWidget) {
+ ActionUtils.RowColumnInfo rcInfo = ActionUtils.getRowColumnInfo(vexWidget);
+ return rcInfo != null && rcInfo.cellIndex < rcInfo.maxColumnCount - 1;
+ }
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/action/MoveRowDownAction.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/action/MoveRowDownAction.java
new file mode 100644
index 0000000..f4b9df0
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/action/MoveRowDownAction.java
@@ -0,0 +1,48 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.action;
+
+import org.eclipse.wst.xml.vex.core.internal.core.IntRange;
+import org.eclipse.wst.xml.vex.core.internal.widget.IVexWidget;
+
+
+/**
+ * Moves the current table row down below its next sibling.
+ */
+public class MoveRowDownAction extends AbstractVexAction {
+
+ public void run(final IVexWidget vexWidget) {
+
+ final ActionUtils.SelectedRows selected = ActionUtils.getSelectedTableRows(vexWidget);
+
+ if (selected.getRows() == null || selected.getRowAfter() == null) {
+ return;
+ }
+
+ vexWidget.doWork(true, new Runnable() {
+ public void run() {
+ IntRange range = ActionUtils.getOuterRange(selected.getRowAfter());
+ vexWidget.moveTo(range.getStart());
+ vexWidget.moveTo(range.getEnd(), true);
+ vexWidget.cutSelection();
+
+ Object firstRow = selected.getRows().get(0);
+ vexWidget.moveTo(ActionUtils.getOuterRange(firstRow).getStart());
+ vexWidget.paste();
+ }
+ });
+ }
+
+ public boolean isEnabled(IVexWidget vexWidget) {
+ ActionUtils.SelectedRows selected = ActionUtils.getSelectedTableRows(vexWidget);
+ return selected.getRows() != null && selected.getRowAfter() != null;
+ }
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/action/MoveRowUpAction.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/action/MoveRowUpAction.java
new file mode 100644
index 0000000..2da3806
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/action/MoveRowUpAction.java
@@ -0,0 +1,52 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.action;
+
+import java.util.List;
+
+import org.eclipse.wst.xml.vex.core.internal.core.IntRange;
+import org.eclipse.wst.xml.vex.core.internal.widget.IVexWidget;
+
+
+/**
+ * Moves the current table row up above its previous sibling.
+ */
+public class MoveRowUpAction extends AbstractVexAction {
+
+ public void run(final IVexWidget vexWidget) {
+
+ final ActionUtils.SelectedRows selected = ActionUtils.getSelectedTableRows(vexWidget);
+
+ if (selected.getRows() == null || selected.getRowBefore() == null) {
+ return;
+ }
+
+ vexWidget.doWork(true, new Runnable() {
+ public void run() {
+ IntRange range = ActionUtils.getOuterRange(selected.getRowBefore());
+ vexWidget.moveTo(range.getStart());
+ vexWidget.moveTo(range.getEnd(), true);
+ vexWidget.cutSelection();
+
+ List rows = selected.getRows();
+ Object lastRow = rows.get(rows.size() - 1);
+ vexWidget.moveTo(ActionUtils.getOuterRange(lastRow).getEnd());
+ vexWidget.paste();
+ }
+ });
+ }
+
+ public boolean isEnabled(IVexWidget vexWidget) {
+ ActionUtils.SelectedRows selected = ActionUtils.getSelectedTableRows(vexWidget);
+ return selected.getRows() != null && selected.getRowBefore() != null;
+ }
+
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/action/MoveSelectionUpAction.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/action/MoveSelectionUpAction.java
new file mode 100644
index 0000000..1139873
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/action/MoveSelectionUpAction.java
@@ -0,0 +1,80 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.action;
+
+import org.eclipse.wst.xml.vex.core.internal.dom.Element;
+import org.eclipse.wst.xml.vex.core.internal.layout.BlockBox;
+import org.eclipse.wst.xml.vex.core.internal.layout.Box;
+import org.eclipse.wst.xml.vex.core.internal.widget.IBoxFilter;
+import org.eclipse.wst.xml.vex.core.internal.widget.IVexWidget;
+
+
+/**
+ * Moves the current selection or block element above the previous sibling.
+ * WORK IN PROGRESS.
+ */
+public class MoveSelectionUpAction extends AbstractVexAction {
+
+ public void run(final IVexWidget vexWidget) {
+
+ // First we determine whether we should expand the selection
+ // to contain an entire block box.
+
+ // Find the lowest block box that completely contains the
+ // selection
+ Box box = vexWidget.findInnermostBox(new IBoxFilter() {
+ public boolean matches(Box box) {
+ return box instanceof BlockBox
+ && box.getElement() != null
+ && box.getStartOffset() <= vexWidget.getSelectionStart()
+ && box.getEndOffset() >= vexWidget.getSelectionEnd();
+ }
+ });
+
+ Box[] children = box.getChildren();
+ if (children.length > 0 && children[0] instanceof BlockBox) {
+ // The found box contains other block children, so we
+ // do NOT have to expand the selection
+ } else {
+ // Expand selection to the containing box
+
+ // (Note: This "if" is caused by the fact that getStartOffset is treated
+ // differently between elements and boxes. Boxes own their startOffset,
+ // while elements don't own theirs. Perhaps we should fix this by having
+ // box.getStartOffset() return box.getStartPosition() + 1, but this would
+ // be a VERY large change.)
+ System.out.println("Box is " + box);
+ Element element = box.getElement();
+ if (element != null) {
+ vexWidget.moveTo(element.getEndOffset());
+ vexWidget.moveTo(element.getStartOffset(), true);
+
+ } else {
+ vexWidget.moveTo(box.getEndOffset());
+ vexWidget.moveTo(box.getStartOffset(), true);
+ }
+ }
+
+// final int previousSiblingStart = ActionUtils.getPreviousSiblingStart(vexWidget);
+//
+// vexWidget.doWork(new IRunnable() {
+// public void run() throws Exception {
+// vexWidget.cutSelection();
+// vexWidget.moveTo(previousSiblingStart);
+// vexWidget.paste();
+// vexWidget.moveTo(previousSiblingStart, true);
+// }
+// });
+
+
+ }
+
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/action/NextTableCellAction.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/action/NextTableCellAction.java
new file mode 100644
index 0000000..2b4b29e
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/action/NextTableCellAction.java
@@ -0,0 +1,72 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.action;
+
+import org.eclipse.wst.xml.vex.core.internal.layout.Box;
+import org.eclipse.wst.xml.vex.core.internal.layout.TableRowBox;
+import org.eclipse.wst.xml.vex.core.internal.widget.IBoxFilter;
+import org.eclipse.wst.xml.vex.core.internal.widget.IVexWidget;
+
+
+/**
+ * Moves the caret to the next table cell. The contents of the cell
+ * are selected. If the current cell is the last cell in the table,
+ * the current row is duplicated.
+ */
+public class NextTableCellAction extends AbstractVexAction {
+
+ public void run(final IVexWidget vexWidget) {
+
+ final TableRowBox tr = (TableRowBox) vexWidget.findInnermostBox(new IBoxFilter() {
+ public boolean matches(Box box) {
+ return box instanceof TableRowBox;
+ }
+ });
+
+ if (tr == null) {
+ // not in a table row
+ return;
+ }
+
+ int offset = vexWidget.getCaretOffset();
+
+ Box[] cells = tr.getChildren();
+ for (int i = 0; i < cells.length; i++) {
+ if (cells[i].getStartOffset() > offset) {
+ vexWidget.moveTo(cells[i].getStartOffset());
+ vexWidget.moveTo(cells[i].getEndOffset(), true);
+ return;
+ }
+ }
+
+ // No next cell found in this row
+ // Find the next row
+ Box[] rows = tr.getParent().getChildren();
+ for (int i = 0; i < rows.length; i++) {
+ if (rows[i].getStartOffset() > offset) {
+ cells = rows[i].getChildren();
+ if (cells.length > 0) {
+ Box cell = cells[0];
+ vexWidget.moveTo(cell.getStartOffset());
+ vexWidget.moveTo(cell.getEndOffset(), true);
+ } else {
+ System.out.println("TODO - dup row into new empty row");
+ }
+ return;
+ }
+ }
+
+
+ // We didn't find a "next row", so let's dup the current one
+ ActionUtils.duplicateTableRow(vexWidget, tr);
+
+ }
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/action/PasteTextAction.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/action/PasteTextAction.java
new file mode 100644
index 0000000..38dca68
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/action/PasteTextAction.java
@@ -0,0 +1,28 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.action;
+
+import org.eclipse.wst.xml.vex.core.internal.widget.IVexWidget;
+
+/**
+ * Paste the clipboard contents into the document as plain text, ignoring
+ * any markup.
+ */
+public class PasteTextAction extends AbstractVexAction {
+
+ public void run(IVexWidget vexWidget) {
+ throw new UnsupportedOperationException("PasteTextAction is not yet implemented.");
+ }
+
+ public boolean isEnabled(IVexWidget vexWidget) {
+ return false;
+ }
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/action/PreviousTableCellAction.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/action/PreviousTableCellAction.java
new file mode 100644
index 0000000..045f922
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/action/PreviousTableCellAction.java
@@ -0,0 +1,66 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.action;
+
+import org.eclipse.wst.xml.vex.core.internal.layout.Box;
+import org.eclipse.wst.xml.vex.core.internal.layout.TableRowBox;
+import org.eclipse.wst.xml.vex.core.internal.widget.IBoxFilter;
+import org.eclipse.wst.xml.vex.core.internal.widget.IVexWidget;
+
+
+/**
+ * Moves the caret to the next table cell. The contents of the cell
+ * are selected. If the current cell is the last cell in the table,
+ * the current row is duplicated.
+ */
+public class PreviousTableCellAction extends AbstractVexAction {
+
+ public void run(final IVexWidget vexWidget) {
+
+ final TableRowBox tr = (TableRowBox) vexWidget.findInnermostBox(new IBoxFilter() {
+ public boolean matches(Box box) {
+ return box instanceof TableRowBox;
+ }
+ });
+
+ if (tr == null) {
+ // not in a table row
+ return;
+ }
+
+ int offset = vexWidget.getCaretOffset();
+
+ Box[] cells = tr.getChildren();
+ for (int i = cells.length - 1; i >= 0; i--) {
+ if (cells[i].getEndOffset() < offset) {
+ vexWidget.moveTo(cells[i].getStartOffset());
+ vexWidget.moveTo(cells[i].getEndOffset(), true);
+ return;
+ }
+ }
+
+ // No next cell found in this row
+ // Find the previous row
+ Box[] rows = tr.getParent().getChildren();
+ for (int i = rows.length - 1; i >= 0; i--) {
+ if (rows[i].getEndOffset() < offset) {
+ cells = rows[i].getChildren();
+ if (cells.length > 0) {
+ Box cell = cells[cells.length - 1];
+ vexWidget.moveTo(cell.getStartOffset());
+ vexWidget.moveTo(cell.getEndOffset(), true);
+ }
+ return;
+ }
+ }
+
+ }
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/action/RemoveElementAction.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/action/RemoveElementAction.java
new file mode 100644
index 0000000..6cc3a94
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/action/RemoveElementAction.java
@@ -0,0 +1,40 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.action;
+
+import org.eclipse.wst.xml.vex.core.internal.dom.DocumentFragment;
+import org.eclipse.wst.xml.vex.core.internal.dom.Element;
+import org.eclipse.wst.xml.vex.core.internal.widget.IVexWidget;
+
+
+/**
+ * Removes the current element, adding its content to the parent element.
+ */
+public class RemoveElementAction extends AbstractVexAction {
+
+ public void run(final IVexWidget vexWidget) {
+ vexWidget.doWork(new Runnable() {
+ public void run() {
+ Element element =
+ vexWidget.getDocument().getElementAt(vexWidget.getCaretOffset());
+ vexWidget.moveTo(element.getStartOffset() + 1, false);
+ vexWidget.moveTo(element.getEndOffset(), true);
+ DocumentFragment frag = vexWidget.getSelectedFragment();
+ vexWidget.deleteSelection();
+ vexWidget.moveBy(-1, false);
+ vexWidget.moveBy(2, true);
+ vexWidget.deleteSelection();
+ vexWidget.insertFragment(frag);
+ }
+ });
+ }
+
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/action/RestoreLastSelectionAction.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/action/RestoreLastSelectionAction.java
new file mode 100644
index 0000000..1956978
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/action/RestoreLastSelectionAction.java
@@ -0,0 +1,27 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.action;
+
+import org.eclipse.wst.xml.vex.core.internal.widget.IVexWidget;
+
+/**
+ * Restore the selection to what it was before it last changed.
+ */
+public class RestoreLastSelectionAction extends AbstractVexAction {
+
+ public void run(IVexWidget vexWidget) {
+ throw new UnsupportedOperationException("RestoreLastSelectionAction is not yet implemented.");
+ }
+
+ public boolean isEnabled(IVexWidget vexWidget) {
+ return false;
+ }
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/action/SplitAction.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/action/SplitAction.java
new file mode 100644
index 0000000..d41448c
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/action/SplitAction.java
@@ -0,0 +1,104 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.action;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.eclipse.wst.xml.vex.core.internal.VEXCorePlugin;
+import org.eclipse.wst.xml.vex.core.internal.css.CSS;
+import org.eclipse.wst.xml.vex.core.internal.css.Styles;
+import org.eclipse.wst.xml.vex.core.internal.dom.Document;
+import org.eclipse.wst.xml.vex.core.internal.dom.DocumentFragment;
+import org.eclipse.wst.xml.vex.core.internal.dom.Element;
+import org.eclipse.wst.xml.vex.core.internal.widget.IVexWidget;
+
+
+/**
+ * Splits the current block element.
+ */
+public class SplitAction extends AbstractVexAction {
+
+ public void run(final IVexWidget vexWidget) {
+ Element element = vexWidget.getCurrentElement();
+ Styles styles = vexWidget.getStyleSheet().getStyles(element);
+ while (!styles.isBlock()) {
+ element = element.getParent();
+ styles = vexWidget.getStyleSheet().getStyles(element);
+ }
+ splitElement(vexWidget, element);
+ }
+
+
+ /**
+ * Splits the given element.
+ * @param vexWidget IVexWidget containing the document.
+ * @param element Element to be split.
+ */
+ public static void splitElement(final IVexWidget vexWidget, final Element element) {
+
+ vexWidget.doWork(new Runnable() {
+ public void run() {
+
+ long start = 0;
+ if (VEXCorePlugin.getInstance().isDebugging()) {
+ start = System.currentTimeMillis();
+ }
+
+ Styles styles = vexWidget.getStyleSheet().getStyles(element);
+
+ if (styles.getWhiteSpace().equals(CSS.PRE)) {
+ // can't call vexWidget.insertText() or we'll get an infinite loop
+ Document doc = vexWidget.getDocument();
+ int offset = vexWidget.getCaretOffset();
+ doc.insertText(offset, "\n");
+ vexWidget.moveTo(offset + 1);
+ } else {
+
+ // There may be a number of child elements below the given
+ // element. We cut out the tails of each of these elements
+ // and put them in a list of fragments to be reconstructed when
+ // we clone the element.
+ List children = new ArrayList();
+ List frags = new ArrayList();
+ Element child = vexWidget.getCurrentElement();
+ while (true) {
+ children.add(child);
+ vexWidget.moveTo(child.getEndOffset(), true);
+ frags.add(vexWidget.getSelectedFragment());
+ vexWidget.deleteSelection();
+ vexWidget.moveTo(child.getEndOffset() + 1);
+ if (child == element) {
+ break;
+ }
+ child = child.getParent();
+ }
+
+ for (int i = children.size() - 1; i >= 0; i--) {
+ child = (Element) children.get(i);
+ DocumentFragment frag = (DocumentFragment) frags.get(i);
+ vexWidget.insertElement((Element) child.clone());
+ int offset = vexWidget.getCaretOffset();
+ if (frag != null) {
+ vexWidget.insertFragment(frag);
+ }
+ vexWidget.moveTo(offset);
+ }
+ }
+
+ if (VEXCorePlugin.getInstance().isDebugging()) {
+ long end = System.currentTimeMillis();
+ System.out.println("split() took " + (end - start) + "ms");
+ }
+ }
+ });
+ }
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/action/SplitItemAction.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/action/SplitItemAction.java
new file mode 100644
index 0000000..1c328fb
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/action/SplitItemAction.java
@@ -0,0 +1,55 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.action;
+
+import org.eclipse.wst.xml.vex.core.internal.css.CSS;
+import org.eclipse.wst.xml.vex.core.internal.css.StyleSheet;
+import org.eclipse.wst.xml.vex.core.internal.dom.Element;
+import org.eclipse.wst.xml.vex.core.internal.layout.Box;
+import org.eclipse.wst.xml.vex.core.internal.layout.TableRowBox;
+import org.eclipse.wst.xml.vex.core.internal.widget.IBoxFilter;
+import org.eclipse.wst.xml.vex.core.internal.widget.IVexWidget;
+
+
+/**
+ * Splits the nearest enclosing table row or list item. If a table row is being
+ * split, empty versions of the current row's cells are created.
+ */
+public class SplitItemAction extends AbstractVexAction {
+
+ public void run(IVexWidget vexWidget) {
+
+ final StyleSheet ss = vexWidget.getStyleSheet();
+
+ // Item is either a TableRowBox or a BlockElementBox representing
+ // a list item
+ Box item = vexWidget.findInnermostBox(new IBoxFilter() {
+ public boolean matches(Box box) {
+ if (box instanceof TableRowBox) {
+ return true;
+ } else {
+ Element element = box.getElement();
+ return element != null && ss.getStyles(element).getDisplay().equals(CSS.LIST_ITEM);
+ }
+ }
+ });
+
+ if (item instanceof TableRowBox) {
+ insertRowBelowAction.run(vexWidget);
+ //ActionUtils.duplicateTableRow(vexWidget, (TableRowBox) item);
+ } else if (item != null) {
+ SplitAction.splitElement(vexWidget, item.getElement());
+ }
+ }
+
+ private static InsertRowBelowAction insertRowBelowAction = new InsertRowBelowAction();
+
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/action/TableCellCallback.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/action/TableCellCallback.java
new file mode 100644
index 0000000..907bc30
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/action/TableCellCallback.java
@@ -0,0 +1,44 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.action;
+
+/**
+ * Callback interface called from LayoutUtils.iterateTableCells.
+ */
+public interface TableCellCallback {
+
+ /**
+ * Called before the first cell in a row is visited.
+ *
+ * @param row Element or IntRange representing the row.
+ * @param rowIndex Zero-based index of the row.
+ */
+ public void startRow(Object row, int rowIndex);
+
+ /**
+ * Called when a cell is visited.
+ *
+ * @param row Element or IntRange representing the row.
+ * @param cell Element or IntRange representing the cell.
+ * @param rowIndex Zero-based index of the current row.
+ * @param cellIndex Zero-based index of the current cell.
+ */
+ public void onCell(Object row, Object cell, int rowIndex, int cellIndex);
+
+ /**
+ * Called after the last cell in a row is visited.
+ *
+ * @param row Element or IntRange representing the row.
+ * @param rowIndex Zero-based index of the row.
+ */
+ public void endRow(Object row, int rowIndex);
+
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/core/Caret.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/core/Caret.java
new file mode 100644
index 0000000..1078ebc
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/core/Caret.java
@@ -0,0 +1,67 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.core;
+
+/**
+ * Represents the caret, a line that indicates an insertion point in the
+ * document.
+ */
+public abstract class Caret {
+
+ private int x;
+ private int y;
+
+ /**
+ * Class constructor
+ * @param x x-coordinate of the top left corner of the caret
+ * @param y y-coordinate of the top left corner of the caret
+ */
+ public Caret(int x, int y) {
+ this.x = x;
+ this.y = y;
+ }
+
+ /**
+ * Draws the caret in the given Graphics context.
+ * @param g Graphics within which the caret should be drawn.
+ * @param color Color with which the caret should be drawn.
+ */
+ public abstract void draw(Graphics g, Color color);
+
+ /**
+ * Returns the smallest rectangle that completely encloses the caret.
+ */
+ public abstract Rectangle getBounds();
+
+ /**
+ * Returns the x-coordinate of the top left corner of the caret
+ */
+ public int getX() {
+ return this.x;
+ }
+
+ /**
+ * Returns the y-coordinate of the top left corner of the caret
+ */
+ public int getY() {
+ return this.y;
+ }
+
+ /**
+ * Moves the caret by the given x and y distance.
+ * @param x amount by which to move the caret to the right
+ * @param y amount by which to move the caret down
+ */
+ public void translate(int x, int y) {
+ this.x += x;
+ this.y += y;
+ }
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/core/Color.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/core/Color.java
new file mode 100644
index 0000000..420774a
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/core/Color.java
@@ -0,0 +1,84 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.core;
+
+/**
+ * Toolkit-independent representation of a color. Colors consist of three
+ * integers in the range 0..255 representing red, green, and blue components.
+ * Objects of this class are immutable.
+ */
+public class Color {
+
+ public static final Color BLACK = new Color(0, 0, 0);
+
+ private int red;
+ private int green;
+ private int blue;
+
+ /**
+ * Class constructor.
+ *
+ * @param red red value, 0..255
+ * @param green green value, 0..255
+ * @param blue blue value, 0..255
+ */
+ public Color(int red, int green, int blue) {
+ this.red = red;
+ this.green = green;
+ this.blue = blue;
+ }
+
+ /**
+ * Returns the blue component of the color, in the range 0..255
+ */
+ public int getBlue() {
+ return blue;
+ }
+
+ /**
+ * Returns the green component of the color, in the range 0..255
+ */
+ public int getGreen() {
+ return green;
+ }
+
+ /**
+ * Returns the red component of the color, in the range 0..255
+ */
+ public int getRed() {
+ return red;
+ }
+
+ public boolean equals(Object o) {
+ if (o.getClass() != this.getClass()) {
+ return false;
+ }
+ Color c = (Color) o;
+ return this.red == c.red && this.green == c.green && this.blue == c.blue;
+ }
+
+ public int hashCode() {
+ return this.red + this.green << 16 + this.blue << 24;
+ }
+
+ public String toString() {
+ StringBuffer sb = new StringBuffer(20);
+ sb.append("Color[r=");
+ sb.append(this.red);
+ sb.append(",g=");
+ sb.append(this.green);
+ sb.append(",b=");
+ sb.append(this.blue);
+ sb.append("]");
+ return sb.toString();
+ }
+
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/core/ColorResource.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/core/ColorResource.java
new file mode 100644
index 0000000..94ad77f
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/core/ColorResource.java
@@ -0,0 +1,25 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.core;
+
+/**
+ * Wrapper for a toolkit-defined color. Color objects are system resources.
+ * They should be retrieved with the Graphics.createColor method and should
+ * be disposed when no longer needed.
+ */
+public interface ColorResource {
+
+ public static final int SELECTION_BACKGROUND = 0;
+ public static final int SELECTION_FOREGROUND = 1;
+
+ public void dispose();
+
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/core/DisplayDevice.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/core/DisplayDevice.java
new file mode 100644
index 0000000..f4527fd
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/core/DisplayDevice.java
@@ -0,0 +1,55 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.core;
+
+/**
+ * Represents a device that can display graphics. This class is subclassed
+ * for each target system.
+ */
+public abstract class DisplayDevice {
+
+ /**
+ * Class constructor.
+ */
+ public DisplayDevice() {
+ }
+
+ /**
+ * Returns the current display device.
+ */
+ public static DisplayDevice getCurrent() {
+ return current;
+ }
+ /**
+ * Returns the horizontal resolution of the device, in pixels-per-inch.
+ */
+ public abstract int getHorizontalPPI();
+
+
+ /**
+ * Returns the horizontal resolution of the device, in pixels-per-inch.
+ */
+ public abstract int getVerticalPPI();
+
+
+ /**
+ * Sets the current display device. This is typically called by the
+ * platform-specific widget;
+ * @param current The device to use as the current device.
+ */
+ public static void setCurrent(DisplayDevice current) {
+ DisplayDevice.current = current;
+ }
+
+ //======================================================= PRIVATE
+
+ private static DisplayDevice current;
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/core/Drawable.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/core/Drawable.java
new file mode 100644
index 0000000..d393279
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/core/Drawable.java
@@ -0,0 +1,32 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.core;
+
+/**
+ * An object that can be drawn into a Graphics.
+ */
+public interface Drawable {
+
+ /**
+ * Draw the object.
+ * @param g Graphics into which to draw the object.
+ * @param x x-coordinate where the object should be drawn
+ * @param y y-coordinate where the object should be drawn
+ */
+ public void draw(Graphics g, int x, int y);
+
+ /**
+ * Returns the smallest rectangle that completely encloses the
+ * drawn shape.
+ */
+ public Rectangle getBounds();
+
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/core/FontMetrics.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/core/FontMetrics.java
new file mode 100644
index 0000000..b758735
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/core/FontMetrics.java
@@ -0,0 +1,21 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.core;
+
+/**
+ * Toolkit-independent font metrics.
+ */
+public interface FontMetrics {
+ public int getAscent();
+ public int getDescent();
+ public int getHeight();
+ public int getLeading();
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/core/FontResource.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/core/FontResource.java
new file mode 100644
index 0000000..58019d6
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/core/FontResource.java
@@ -0,0 +1,21 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.core;
+
+/**
+ * Wrapper for a toolkit-defined font. Fonts are system-defined resources.
+ * They must be retrieved from the Graphics.createFont method, and must be
+ * disposed when no longer needed.
+ */
+public interface FontResource {
+
+ public void dispose();
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/core/FontSpec.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/core/FontSpec.java
new file mode 100644
index 0000000..1aec35d
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/core/FontSpec.java
@@ -0,0 +1,76 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * John Krasnay - initial API and implementation
+ * Dave Holroyd - Implement text decoration
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.core;
+
+/**
+ * Toolkit-independent specifier of a font. This class does not encapsulate
+ * an actual font, but simply the information needed
+ * for the toolkit to find an actual font.
+ *
+ * <p>An array of font family names may be specified. If more than one name
+ * is specified, the toolkit should select the first name that matches an
+ * actual font on the platform.</p>
+ */
+public class FontSpec {
+
+ public static final int PLAIN = 0x0;
+ public static final int BOLD = 1<<0;
+ public static final int ITALIC = 1<<1;
+ public static final int UNDERLINE = 1<<2;
+ public static final int OVERLINE = 1<<3;
+ public static final int LINE_THROUGH = 1<<4;
+
+ private String[] names;
+ private float size;
+ private int style;
+
+ /**
+ * Class constructor.
+ * @param names Array of names of the font family.
+ * @param style Bitwise-OR of the applicable style flages, e.g. BOLD | ITALIC
+ * @param size Size of the font, in points.
+ */
+ public FontSpec(String[] names, int style, float size) {
+ this.names = names;
+ this.style = style;
+ this.size = size;
+ }
+
+ /**
+ * Returns the names of the font families that match the font.
+ */
+ public String[] getNames() {
+ return names;
+ }
+
+ /**
+ * Returns the size of the font in points.
+ */
+ public float getSize() {
+ return size;
+ }
+
+ /**
+ * Returns a bitwise-OR of the style flags. The following sample checks if
+ * the font is bold.
+ *
+ * <pre>
+ * if (font.getStyle | VexFont.BOLD) {
+ * // do something bold...
+ * }
+ * </pre>
+ */
+ public int getStyle() {
+ return style;
+ }
+
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/core/Graphics.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/core/Graphics.java
new file mode 100644
index 0000000..1d71711
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/core/Graphics.java
@@ -0,0 +1,59 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.core;
+
+
+/**
+ * Interface through which Vex performs graphics operations. Implemented
+ * by adapters to the java.awt.Graphics and org.eclipse.swt.graphics.GC
+ * classes.
+ */
+public interface Graphics {
+
+ public static final int LINE_SOLID = 0;
+ public static final int LINE_DASH = 1;
+ public static final int LINE_DOT = 2;
+
+ public int charsWidth(char[] data, int offset, int length);
+ public ColorResource createColor(Color rgb);
+ public FontResource createFont(FontSpec fontSpec);
+ public void dispose();
+ public void drawChars(char[] chars, int offset, int length, int x, int y);
+ public void drawLine(int x1, int y1, int x2, int y2);
+
+ /**
+ * Draw the given string at the given point using the current font.
+ * @param s string to draw
+ * @param x x-coordinate of the top left corner of the text box
+ * @param y y-coordinate of the top left corner of the text box
+ */
+ public void drawString(String s, int x, int y);
+ public void drawOval(int x, int y, int width, int height);
+ public void drawRect(int x, int y, int width, int height);
+ public void fillOval(int x, int y, int width, int height);
+ public void fillRect(int x, int y, int width, int height);
+ public Rectangle getClipBounds();
+ public ColorResource getColor();
+ public FontResource getFont();
+ public int getLineStyle();
+ public int getLineWidth();
+ public ColorResource getSystemColor(int id);
+ public FontMetrics getFontMetrics();
+ public boolean isAntiAliased();
+ public void setAntiAliased(boolean antiAliased);
+ public ColorResource setColor(ColorResource color);
+ public FontResource setFont(FontResource font);
+ public void setLineStyle(int style);
+ public void setLineWidth(int width);
+
+ public int stringWidth(String s);
+
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/core/Insets.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/core/Insets.java
new file mode 100644
index 0000000..9d63ffa
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/core/Insets.java
@@ -0,0 +1,84 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.core;
+
+/**
+ * Toolkit-independent insets.
+ */
+public class Insets {
+
+ private int top;
+ private int left;
+ private int bottom;
+ private int right;
+
+ /** Zero insets */
+ public static final Insets ZERO_INSETS = new Insets(0, 0, 0, 0);
+
+ /**
+ * Class constructor.
+ *
+ * @param top Top inset.
+ * @param left Left inset.
+ * @param bottom Bottom inset.
+ * @param right Right inset.
+ */
+ public Insets(int top, int left, int bottom, int right) {
+ this.top = top;
+ this.left = left;
+ this.bottom = bottom;
+ this.right = right;
+ }
+
+ /**
+ * @return Returns the top.
+ */
+ public int getTop() {
+ return top;
+ }
+
+ /**
+ * @return Returns the left.
+ */
+ public int getLeft() {
+ return left;
+ }
+
+ /**
+ * @return Returns the bottom.
+ */
+ public int getBottom() {
+ return bottom;
+ }
+
+ /**
+ * Returns the right inset.
+ */
+ public int getRight() {
+ return right;
+ }
+
+ public String toString() {
+ StringBuffer sb = new StringBuffer(80);
+ sb.append(Insets.class.getName());
+ sb.append("[top=");
+ sb.append(this.getTop());
+ sb.append(",left=");
+ sb.append(this.getLeft());
+ sb.append(",bottom=");
+ sb.append(this.getBottom());
+ sb.append(",right=");
+ sb.append(this.getRight());
+ sb.append("]");
+ return sb.toString();
+ }
+
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/core/IntRange.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/core/IntRange.java
new file mode 100644
index 0000000..cdb66b2
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/core/IntRange.java
@@ -0,0 +1,100 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.core;
+
+/**
+ * Represents a range of integers. Zero-length ranges (i.e. ranges where
+ * start == end) are permitted. This class is immutable.
+ */
+public class IntRange {
+
+ /**
+ * Class constuctor.
+ * @param start Start of the range.
+ * @param end End of the range. Must be >= start.
+ */
+ public IntRange(int start, int end) {
+ if (start > end) {
+ throw new IllegalArgumentException("start (" + start + ") is greater than end (" + end + ")");
+ }
+ this.start = start;
+ this.end = end;
+ }
+
+ /**
+ * Returns the start of the range.
+ */
+ public int getStart() {
+ return this.start;
+ }
+
+ /**
+ * Returns the end of the range.
+ */
+ public int getEnd() {
+ return this.end;
+ }
+
+ /**
+ * Returns the range that represents the intersection of this range
+ * and the given range. If the ranges do not intersect, returns null.
+ * May return an empty range.
+ * @param range Range with which to perform an intersection.
+ */
+ public IntRange intersection(IntRange range) {
+ if (this.intersects(range)) {
+ return new IntRange(Math.max(this.start, range.start), Math.min(this.end, range.end));
+ } else {
+ return null;
+ }
+
+ }
+
+ /**
+ * Returns true if this range intersects the given range, even if the
+ * result would be an empty range.
+ * @param range Range with which to intersect.
+ */
+ public boolean intersects(IntRange range) {
+ return this.start <= range.end && this.end >= range.start;
+ }
+
+ /**
+ * Returns true if start and end are equal.
+ */
+ public boolean isEmpty() {
+ return start == end;
+ }
+
+ /**
+ * Returns a range that is the union of this range and the given range.
+ * If the ranges are disjoint, the gap between the ranges is included
+ * in the result.
+ * @param range Rnage with which to perform the union
+ */
+ public IntRange union(IntRange range) {
+ return new IntRange(Math.min(this.start, range.start), Math.min(this.end, range.end));
+ }
+
+ public String toString() {
+ StringBuffer sb = new StringBuffer();
+ sb.append("IntRange(");
+ sb.append(start);
+ sb.append(",");
+ sb.append(end);
+ sb.append(")");
+ return sb.toString();
+ }
+ //============================================================= PRIVATE
+
+ private int start;
+ private int end;
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/core/ListenerList.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/core/ListenerList.java
new file mode 100644
index 0000000..18a4e67
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/core/ListenerList.java
@@ -0,0 +1,113 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.core;
+
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.EventObject;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+
+/**
+ * A collection of listener objects. The main point of this class is the
+ * fireEvent method, which takes care of the
+ * tedium of iterating over the collection and catching exceptions generated
+ * by listeners.
+ */
+public class ListenerList {
+
+ /**
+ * Class constructor.
+ * @param listenerClass Class of the listener interface.
+ * @param eventClass Class of the event objects passed to methods in the
+ * listener interface.
+ */
+ public ListenerList(Class listenerClass, Class eventClass) {
+ this.listenerClass = listenerClass;
+ this.methodParams = new Class[] { eventClass };
+ }
+
+ /**
+ * Adds a listener to the list. Rejects listeners that are not subclasses
+ * of the listener class passed to the constructor.
+ * @param listener Listener to be added.
+ */
+ public void add(Object listener) {
+ if (!listenerClass.isInstance(listener)) {
+ this.handleException(new IllegalArgumentException("" + listener + " is not an instance of " + listenerClass));
+ }
+ this.listeners.add(listener);
+ }
+
+ /**
+ * Calls the given method on each registered listener. Any exception
+ * thrown from one of the called methods is passed to handleException, as
+ * is any introspection error, e.g. if the given method doesn't exist.
+ *
+ * @param methodName Listener method to call.
+ * @param event Event to be passed to each call.
+ */
+ public void fireEvent(String methodName, EventObject event) {
+
+ Method method = (Method) this.methods.get(methodName);
+ if (method == null) {
+ try {
+ method = listenerClass.getMethod(methodName, methodParams);
+ this.methods.put(methodName, method);
+ } catch (Exception e) {
+ this.handleException(e);
+ return;
+ }
+ }
+
+ Object[] args = new Object[] { event };
+ for (Iterator it = this.listeners.iterator(); it.hasNext();) {
+ Object listener = it.next();
+ try {
+ method.invoke(listener, args);
+ } catch (Exception ex) {
+ this.handleException(ex);
+ }
+ }
+
+ }
+
+ /**
+ * Called from fireEvent whenever a called listener method throws an
+ * exception, or if there is a problem looking up the listener method
+ * by reflection. By default, simply prints the stack trace to stdout.
+ * Clients may override this method to provide a more suitable
+ * implementation.
+ * @param ex Exception thrown by the listener method.
+ */
+ public void handleException(Exception ex) {
+ ex.printStackTrace();
+ }
+
+ /**
+ * Removes a listener from the list.
+ * @param listener Listener to remove.
+ */
+ public void remove(Object listener) {
+ this.listeners.remove(listener);
+ }
+
+ //====================================================== PRIVATE
+
+ private Class listenerClass;
+ private Class[] methodParams;
+ private Collection listeners = new ArrayList();
+
+ // map methodName => Method object
+ private Map methods = new HashMap();
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/core/Point.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/core/Point.java
new file mode 100644
index 0000000..e3614e4
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/core/Point.java
@@ -0,0 +1,56 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.core;
+
+/**
+ * Toolkit-independent point.
+ */
+public class Point {
+
+ private int x;
+ private int y;
+
+ /**
+ * Class constructor.
+ * @param x X-coordinate.
+ * @param y Y-coordinate.
+ */
+ public Point(int x, int y) {
+ this.x = x;
+ this.y = y;
+ }
+
+ public String toString() {
+ StringBuffer sb = new StringBuffer(80);
+ sb.append(Point.class.getName());
+ sb.append("[x=");
+ sb.append(this.getX());
+ sb.append(",y=");
+ sb.append(this.getY());
+ sb.append("]");
+ return sb.toString();
+ }
+
+ /**
+ * Returns the x-coordinate.
+ */
+ public int getX() {
+ return x;
+ }
+
+ /**
+ * Returns the y-coordinate.
+ */
+ public int getY() {
+ return y;
+ }
+
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/core/Rectangle.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/core/Rectangle.java
new file mode 100644
index 0000000..3c26055
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/core/Rectangle.java
@@ -0,0 +1,91 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.core;
+
+/**
+ * Toolkit-independent rectangle.
+ */
+public class Rectangle {
+
+ private int x;
+ private int y;
+ private int width;
+ private int height;
+
+ public Rectangle(int x, int y, int width, int height) {
+ this.x = x;
+ this.y = y;
+ this.width = width;
+ this.height = height;
+ }
+
+ public boolean intersects(Rectangle rect) {
+ return rect.x < this.x + this.width
+ && rect.x + rect.width > this.x
+ && rect.y < this.y + this.height
+ && rect.y + rect.height > this.y;
+ }
+
+ public String toString() {
+ StringBuffer sb = new StringBuffer(80);
+ sb.append(Rectangle.class.getName());
+ sb.append("[x=");
+ sb.append(this.getX());
+ sb.append(",y=");
+ sb.append(this.getY());
+ sb.append(",width=");
+ sb.append(this.getWidth());
+ sb.append(",height=");
+ sb.append(this.getHeight());
+ sb.append("]");
+ return sb.toString();
+ }
+
+ /**
+ * @return Returns the x.
+ */
+ public int getX() {
+ return x;
+ }
+
+ /**
+ * @return Returns the y.
+ */
+ public int getY() {
+ return y;
+ }
+
+ /**
+ * @return Returns the width.
+ */
+ public int getWidth() {
+ return width;
+ }
+
+ /**
+ * @return Returns the height.
+ */
+ public int getHeight() {
+ return height;
+ }
+
+ /**
+ * Returns a Rectangle that is the union of this rectangle with another.
+ * @param rect Rectangle with which to union this one.
+ */
+ public Rectangle union(Rectangle rect) {
+ int left = Math.min(this.x, rect.x);
+ int top = Math.min(this.y, rect.y);
+ int right = Math.max(this.x + this.width, rect.x + rect.width);
+ int bottom = Math.max(this.y + this.height, rect.y + rect.height);
+ return new Rectangle(left, top, right - left, bottom - top);
+ }
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/css/AbstractProperty.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/css/AbstractProperty.java
new file mode 100644
index 0000000..e217370
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/css/AbstractProperty.java
@@ -0,0 +1,139 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.css;
+
+import org.w3c.css.sac.LexicalUnit;
+
+
+/**
+ * Abstract base class for property classes. Implements the <code>name</code>
+ * property but leaves the implementation of <code>calculate</code> to the
+ * subclass.
+ */
+public abstract class AbstractProperty implements IProperty {
+
+ /**
+ * Class constructor.
+ * @param name Name of the property.
+ */
+ public AbstractProperty(String name) {
+ this.name = name;
+ }
+
+ /**
+ * Returns true if the given lexical unit represents the token "inherit".
+ */
+ public static boolean isInherit(LexicalUnit lu) {
+ return lu != null
+ && lu.getLexicalUnitType() == LexicalUnit.SAC_INHERIT;
+ }
+
+ public String getName() {
+ return this.name;
+ }
+
+ public static boolean isPercentage(LexicalUnit lu) {
+ return lu != null &&
+ lu.getLexicalUnitType() == LexicalUnit.SAC_PERCENTAGE;
+ }
+
+
+
+ public static boolean isLength(LexicalUnit lu) {
+ if (lu == null) {
+ return false;
+ }
+
+ short type = lu.getLexicalUnitType();
+
+ if (type == LexicalUnit.SAC_INTEGER &&
+ lu.getIntegerValue() == 0) {
+ return true;
+ }
+
+ return type == LexicalUnit.SAC_CENTIMETER
+ || type == LexicalUnit.SAC_DIMENSION
+ || type == LexicalUnit.SAC_EM
+ || type == LexicalUnit.SAC_EX
+ || type == LexicalUnit.SAC_INCH
+ || type == LexicalUnit.SAC_MILLIMETER
+ || type == LexicalUnit.SAC_PICA
+ || type == LexicalUnit.SAC_PIXEL
+ || type == LexicalUnit.SAC_POINT;
+ }
+
+ public static int getIntLength(LexicalUnit lu, float fontSize, int ppi) {
+ return Math.round(getFloatLength(lu, fontSize, ppi));
+ }
+
+ public static float getFloatLength(LexicalUnit lu, float fontSize, int ppi) {
+
+ float value = 0f;
+
+ switch (lu.getLexicalUnitType()) {
+
+ case LexicalUnit.SAC_CENTIMETER:
+ value = lu.getFloatValue() * ppi / 2.54f;
+ break;
+ case LexicalUnit.SAC_EM:
+ value = lu.getFloatValue() * fontSize;
+ break;
+ case LexicalUnit.SAC_EX:
+ value = lu.getFloatValue() * fontSize * EX_FACTOR;
+ break;
+ case LexicalUnit.SAC_INCH:
+ value = lu.getFloatValue() * ppi;
+ break;
+ case LexicalUnit.SAC_INTEGER:
+ value = 0; // 0 is the only valid length w/o a dimension
+ break;
+ case LexicalUnit.SAC_MILLIMETER:
+ value = lu.getFloatValue() * ppi / 25.4f;
+ break;
+ case LexicalUnit.SAC_PICA:
+ value = lu.getFloatValue() * ppi / 6;
+ break;
+ case LexicalUnit.SAC_PIXEL:
+ value = lu.getFloatValue();
+ break;
+ case LexicalUnit.SAC_POINT:
+ value = lu.getFloatValue() * ppi / 72;
+ break;
+ }
+ return value;
+ }
+
+
+ //============================================================== PRIVATE
+
+ public static boolean isNumber(LexicalUnit lu) {
+ return lu != null &&
+ (lu.getLexicalUnitType() == LexicalUnit.SAC_INTEGER
+ || lu.getLexicalUnitType() == LexicalUnit.SAC_REAL);
+ }
+
+
+ public static float getNumber(LexicalUnit lu) {
+ if (lu.getLexicalUnitType() == LexicalUnit.SAC_INTEGER) {
+ return lu.getIntegerValue();
+ } else if (lu.getLexicalUnitType() == LexicalUnit.SAC_REAL) {
+ return lu.getFloatValue();
+ } else {
+ throw new RuntimeException("LexicalUnit type " + lu.getLexicalUnitType() + " is not a numeric type.");
+ }
+ }
+
+
+ private String name;
+
+ private static final float EX_FACTOR = 0.6f;
+
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/css/BorderSpacingProperty.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/css/BorderSpacingProperty.java
new file mode 100644
index 0000000..1c096ec
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/css/BorderSpacingProperty.java
@@ -0,0 +1,86 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.css;
+
+
+import org.eclipse.wst.xml.vex.core.internal.core.DisplayDevice;
+import org.w3c.css.sac.LexicalUnit;
+
+/**
+ * The CSS 'border-spacing' property.
+ */
+public class BorderSpacingProperty extends AbstractProperty {
+
+ /**
+ * Represents the computed value of border-spacing, which is a pair
+ * of values representing vertical and horizontal spacing.
+ */
+ public static class Value {
+
+ private int horizontal;
+ private int vertical;
+
+ public static final Value ZERO = new Value(0, 0);
+
+ public Value(int horizontal, int vertical) {
+ this.horizontal = horizontal;
+ this.vertical = vertical;
+ }
+
+ /**
+ * Returns the horizontal spacing, in pixels.
+ */
+ public int getHorizontal() {
+ return this.horizontal;
+ }
+
+ /**
+ * Returns the vertical spacing, in pixels.
+ */
+ public int getVertical() {
+ return this.vertical;
+ }
+ }
+
+ /**
+ * Class constructor.
+ */
+ public BorderSpacingProperty() {
+ super(CSS.BORDER_SPACING);
+ }
+
+ public Object calculate(LexicalUnit lu, Styles parentStyles, Styles styles) {
+
+ int horizontal = 0;
+ int vertical = 0;
+
+ DisplayDevice device = DisplayDevice.getCurrent();
+
+ if (isLength(lu)) {
+ horizontal = getIntLength(lu, styles.getFontSize(), device.getHorizontalPPI());
+ lu = lu.getNextLexicalUnit();
+ if (isLength(lu)) {
+ vertical = getIntLength(lu, styles.getFontSize(), device.getVerticalPPI());
+ } else {
+ vertical = horizontal;
+ }
+ return new Value(horizontal, vertical);
+ } else {
+ // 'inherit' or an invalid value
+ if (parentStyles == null) {
+ return Value.ZERO;
+ } else {
+ return parentStyles.getBorderSpacing();
+ }
+ }
+ }
+
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/css/BorderStyleProperty.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/css/BorderStyleProperty.java
new file mode 100644
index 0000000..44b9f22
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/css/BorderStyleProperty.java
@@ -0,0 +1,63 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.css;
+
+import org.w3c.css.sac.LexicalUnit;
+
+/**
+ * The border-XXX-style CSS property.
+ */
+public class BorderStyleProperty extends AbstractProperty {
+
+ /**
+ * Class constructor.
+ * @param name Name of the property.
+ */
+ public BorderStyleProperty(String name) {
+ super(name);
+ }
+
+ /**
+ * Returns true if the given lexical unit represents a border style.
+ *
+ * @param lu LexicalUnit to check.
+ */
+ public static boolean isBorderStyle(LexicalUnit lu) {
+ if (lu == null) {
+ return false;
+ } else if (lu.getLexicalUnitType() == LexicalUnit.SAC_IDENT) {
+ String s = lu.getStringValue();
+ return s.equals(CSS.NONE)
+ || s.equals(CSS.HIDDEN)
+ || s.equals(CSS.DOTTED)
+ || s.equals(CSS.DASHED)
+ || s.equals(CSS.SOLID)
+ || s.equals(CSS.DOUBLE)
+ || s.equals(CSS.GROOVE)
+ || s.equals(CSS.RIDGE)
+ || s.equals(CSS.INSET)
+ || s.equals(CSS.OUTSET);
+ }
+
+ return false;
+ }
+
+ public Object calculate(LexicalUnit lu, Styles parentStyles, Styles styles) {
+ if (isBorderStyle(lu)) {
+ return lu.getStringValue();
+ } else if (isInherit(lu) && parentStyles != null) {
+ return parentStyles.get(this.getName());
+ } else {
+ return CSS.NONE;
+ }
+ }
+
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/css/BorderWidthProperty.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/css/BorderWidthProperty.java
new file mode 100644
index 0000000..bb256dd
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/css/BorderWidthProperty.java
@@ -0,0 +1,118 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.css;
+
+
+import org.eclipse.wst.xml.vex.core.internal.core.DisplayDevice;
+import org.w3c.css.sac.LexicalUnit;
+
+/**
+ * The border-XXX-width CSS property. Since the value of this property
+ * depends on the corresponding border-XXX-style property, the style
+ * property must be calculated first and placed in the styles, and its
+ * name given to the constructor of this class.
+ */
+public class BorderWidthProperty extends AbstractProperty {
+
+
+ /**
+ * Class constructor.
+ * @param name Name of the property.
+ * @param borderStyleName Name of the corresponding border style
+ * property. For example, if name is CSS.BORDER_TOP_WIDTH, then
+ * borderStyleName should be CSS.BORDER_TOP_STYLE.
+ * @param axis AXIS_HORIZONTAL (for left and right borders) or
+ * AXIS_VERTICAL (for top and bottom borders).
+ */
+ public BorderWidthProperty(String name, String borderStyleName, byte axis) {
+ super(name);
+ this.borderStyleName = borderStyleName;
+ this.axis = axis;
+ }
+
+ /**
+ * Returns true if the given lexical unit represents a border width.
+ *
+ * @param lu LexicalUnit to check.
+ */
+ public static boolean isBorderWidth(LexicalUnit lu) {
+ if (lu == null) {
+ return false;
+ } else if (isLength(lu)) {
+ return true;
+ } else if (lu.getLexicalUnitType() == LexicalUnit.SAC_IDENT) {
+ String s = lu.getStringValue();
+ return s.equals(CSS.THIN)
+ || s.equals(CSS.MEDIUM)
+ || s.equals(CSS.THICK);
+ } else {
+ return false;
+ }
+ }
+
+ public Object calculate(LexicalUnit lu, Styles parentStyles, Styles styles) {
+ return new Integer(this.calculateInternal(lu, parentStyles, styles));
+ }
+
+ private int calculateInternal(LexicalUnit lu, Styles parentStyles, Styles styles) {
+
+ DisplayDevice device = DisplayDevice.getCurrent();
+ int ppi = this.axis == AXIS_HORIZONTAL ? device.getHorizontalPPI() : device.getVerticalPPI();
+
+ String borderStyle = (String) styles.get(this.borderStyleName);
+
+ if (borderStyle.equals(CSS.NONE) || borderStyle.equals(CSS.HIDDEN)) {
+ return 0;
+ } else if (isBorderWidth(lu)) {
+ return getBorderWidth(lu, styles.getFontSize(), ppi);
+ } else if (isInherit(lu) && parentStyles != null) {
+ return ((Integer) parentStyles.get(this.getName())).intValue();
+ } else {
+ // not specified, "none", or other unknown value
+ return BORDER_WIDTH_MEDIUM;
+ }
+ }
+
+ //=================================================== PRIVATE
+
+ // Name of the corresponding border style property
+ private String borderStyleName;
+
+ // Axis along which the border width is measured.
+ private byte axis;
+
+ // named border widths
+ private static final int BORDER_WIDTH_THIN = 1;
+ private static final int BORDER_WIDTH_MEDIUM = 3;
+ private static final int BORDER_WIDTH_THICK = 5;
+
+
+ private static int getBorderWidth(LexicalUnit lu, float fontSize, int ppi) {
+ if (isLength(lu)) {
+ return getIntLength(lu, fontSize, ppi);
+ } else if (lu.getLexicalUnitType() == LexicalUnit.SAC_IDENT) {
+ String s = lu.getStringValue();
+ if (s.equals(CSS.THIN)) {
+ return BORDER_WIDTH_THIN;
+ } else if (s.equals(CSS.MEDIUM)) {
+ return BORDER_WIDTH_MEDIUM;
+ } else if (s.equals(CSS.THICK)) {
+ return BORDER_WIDTH_THICK;
+ } else {
+ return 0;
+ }
+ } else {
+ return 0;
+ }
+ }
+
+
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/css/CSS.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/css/CSS.java
new file mode 100644
index 0000000..1adcf06
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/css/CSS.java
@@ -0,0 +1,233 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * John Krasnay - initial API and implementation
+ * Dave Holroyd - Implement text decoration
+ * John Austin - More complete CSS constants. Add the colour "orange".
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.css;
+
+/**
+ * CSS constants.
+ */
+public interface CSS {
+
+ // property names
+ public static final String AZIMUTH = "azimuth";
+ public static final String BACKGROUND = "background";
+ public static final String BACKGROUND_ATTACHMENT = "background-attachment";
+ public static final String BACKGROUND_COLOR = "background-color";
+ public static final String BACKGROUND_IMAGE = "background-image";
+ public static final String BACKGROUND_POSITION = "background-position";
+ public static final String BACKGROUND_REPEAT = "background-repeat";
+ public static final String BORDER = "border";
+ public static final String BORDER_BOTTOM = "border-bottom";
+ public static final String BORDER_BOTTOM_COLOR = "border-bottom-color";
+ public static final String BORDER_BOTTOM_STYLE = "border-bottom-style";
+ public static final String BORDER_BOTTOM_WIDTH = "border-bottom-width";
+ public static final String BORDER_COLOR = "border-color";
+ public static final String BORDER_LEFT = "border-left";
+ public static final String BORDER_LEFT_COLOR = "border-left-color";
+ public static final String BORDER_LEFT_STYLE = "border-left-style";
+ public static final String BORDER_LEFT_WIDTH = "border-left-width";
+ public static final String BORDER_RIGHT = "border-right";
+ public static final String BORDER_RIGHT_COLOR = "border-right-color";
+ public static final String BORDER_RIGHT_STYLE = "border-right-style";
+ public static final String BORDER_RIGHT_WIDTH = "border-right-width";
+ public static final String BORDER_SPACING = "border-spacing";
+ public static final String BORDER_STYLE = "border-style";
+ public static final String BORDER_TOP = "border-top";
+ public static final String BORDER_TOP_COLOR = "border-top-color";
+ public static final String BORDER_TOP_STYLE = "border-top-style";
+ public static final String BORDER_TOP_WIDTH = "border-top-width";
+ public static final String BORDER_WIDTH = "border-width";
+ public static final String BOTTOM = "bottom";
+ public static final String CAPTION_SIDE = "caption-side";
+ public static final String CLEAR = "clear";
+ public static final String CLIP= "clip";
+ public static final String COLOR = "color";
+ public static final String CONTENT = "content";
+ public static final String COUNTER_INCREMENT = "counter-increment";
+ public static final String COUNTER_RESET= "counter-reset";
+ public static final String CUE = "cue";
+ public static final String CUE_AFTER = "cue-after";
+ public static final String CUE_BEFORE = "cue-before";
+ public static final String CURSOR = "cursor";
+ public static final String DIRECTION = "direction";
+ public static final String DISPLAY = "display";
+ public static final String ELEVATION = "elevation";
+ public static final String EMPTY_CELLS = "empty-cells";
+ public static final String FLOAT = "float";
+ public static final String FONT = "font";
+ public static final String FONT_FAMILY = "font-family";
+ public static final String FONT_SIZE = "font-size";
+ public static final String FONT_STYLE = "font-style";
+ public static final String FONT_VARIANT = "font-variant";
+ public static final String FONT_WEIGHT = "font-weight";
+ public static final String HEIGHT = "height";
+ public static final String HIDDEN = "hidden";
+ public static final String LETTER_SPACING = "letter-spacing";
+ public static final String LINE_HEIGHT = "line-height";
+ public static final String LIST_STYLE = "list-style";
+ public static final String LIST_STYLE_IMAGE = "list-style-image";
+ public static final String LIST_STYLE_POSITION = "list-style-position";
+ public static final String LIST_STYLE_TYPE = "list-style-type";
+ public static final String MARGIN = "margin";
+ public static final String MARGIN_BOTTOM = "margin-bottom";
+ public static final String MARGIN_LEFT = "margin-left";
+ public static final String MARGIN_RIGHT = "margin-right";
+ public static final String MARGIN_TOP = "margin-top";
+ public static final String MAX_HEIGHT = "max-height";
+ public static final String MAX_WIDTH = "max-width";
+ public static final String MIN_HEIGHT = "min-height";
+ public static final String MIN_WIDTH = "min-width";
+ public static final String ORPHANS = "orphans";
+ public static final String OUTLINE = "outline";
+ public static final String OVERFLOW = "overflow";
+ public static final String OUTLINE_COLOR = "outline-color";
+ public static final String OUTLINE_STYLE = "outline-style";
+ public static final String OUTLINE_WIDTH = "outline-width";
+ public static final String PADDING = "padding";
+ public static final String PADDING_BOTTOM = "padding-bottom";
+ public static final String PADDING_LEFT = "padding-left";
+ public static final String PADDING_RIGHT = "padding-right";
+ public static final String PADDING_TOP = "padding-top";
+ public static final String PAUSE = "pause";
+ public static final String PAUSE_AFTER = "pause-after";
+ public static final String PAUSE_BEFORE = "pause-before";
+ public static final String PITCH = "pitch";
+ public static final String PITCH_RANGE = "pitch-range";
+ public static final String PLAY_DURING = "play-during";
+ public static final String POSITION = "position";
+ public static final String QUOTES = "quotes";
+ public static final String RICHNESS = "richness";
+ public static final String SPEAK = "speak";
+ public static final String SPEAK_HEADER = "speak-header";
+ public static final String SPEAK_NUMERAL = "speak-numeral";
+ public static final String SPEAK_PUNCTUATION= "speak-punctuation";
+ public static final String SPEECH_RATE = "speech-rate";
+ public static final String STRESS = "stress";
+ public static final String TABLE_LAYOUT = "table-layout";
+ public static final String TEXT_ALIGN = "text-align";
+ public static final String TEXT_DECORATION = "text-decoration";
+ public static final String TEXT_INDENT = "text-indent";
+ public static final String TEXT_TRANSFORM = "text-transform";
+ public static final String UNICODE_BIDI = "unicode-bidi";
+ public static final String VERTICAL_ALIGN= "vertical-align";
+ public static final String VISIBILITY = "visibility";
+ public static final String VOICE_FAMILY = "voice-family";
+ public static final String VOLUME = "volume";
+ public static final String WHITE_SPACE = "white-space";
+ public static final String WIDOWS = "widows";
+ public static final String WIDTH = "width";
+ public static final String WORD_SPACING = "word-spacing";
+ public static final String Z_SPACING = "z-spacing";
+
+ // suffixes to BORDER_XXX
+ public static final String COLOR_SUFFIX = "-color";
+ public static final String STYLE_SUFFIX = "-style";
+ public static final String WIDTH_SUFFIX = "-width";
+
+ // color values
+ public static final String AQUA = "aqua";
+ public static final String BLACK = "black";
+ public static final String BLUE = "blue";
+ public static final String FUCHSIA = "fuchsia";
+ public static final String GRAY = "gray";
+ public static final String GREEN = "green";
+ public static final String LIME = "lime";
+ public static final String MAROON = "maroon";
+ public static final String NAVY = "navy";
+ public static final String OLIVE = "olive";
+ public static final String ORANGE = "orange";
+ public static final String PURPLE = "purple";
+ public static final String RED = "red";
+ public static final String SILVER = "silver";
+ public static final String TEAL = "teal";
+ public static final String WHITE = "white";
+ public static final String YELLOW = "yellow";
+
+ // list-style values
+ public static final String ARMENIAN = "armenian";
+ public static final String CIRCLE = "circle";
+ public static final String CJK_IDEOGRAPHIC = "cjk-ideographic";
+ public static final String DECIMAL = "decimal";
+ public static final String DECIMAL_LEADING_ZERO = "decimal-leading-zero";
+ public static final String DISC = "disc";
+ public static final String GEORGIAN = "georgian";
+ public static final String HEBREW = "hebrew";
+ public static final String HIRAGANA = "hiragana";
+ public static final String HIRAGANA_IROHA = "hiragana-iroha";
+ public static final String KATAKANA = "katakana";
+ public static final String KATAKANA_IROHA = "katakana-iroha";
+ public static final String LOWER_ALPHA = "lower-alpha";
+ public static final String LOWER_GREEK = "lower-greek";
+ public static final String LOWER_LATIN = "lower-latin";
+ public static final String LOWER_ROMAN = "lower-roman";
+ public static final String SQUARE = "square";
+ public static final String UPPER_ALPHA = "upper-alpha";
+ public static final String UPPER_LATIN = "upper-latin";
+ public static final String UPPER_ROMAN = "upper-roman";
+
+
+ // other values
+ public static final String BLINK = "blink";
+ public static final String BLOCK = "block";
+ public static final String BOLD = "bold";
+ public static final String BOLDER = "bolder";
+ public static final String CENTER = "center";
+ public static final String DASHED = "dashed";
+ public static final String DOTTED = "dotted";
+ public static final String DOUBLE = "double";
+ public static final String GROOVE = "groove";
+ public static final String INLINE = "inline";
+ public static final String INLINE_BLOCK = "inline-block";
+ public static final String INLINE_TABLE = "inline-table";
+ public static final String INSET = "inset";
+ public static final String ITALIC = "italic";
+ public static final String JUSTIFY = "justify";
+ public static final String LARGE = "large";
+ public static final String LARGER = "larger";
+ public static final String LEFT = "left";
+ public static final String LIGHTER = "lighter";
+ public static final String LINE_THROUGH = "line-through";
+ public static final String LIST_ITEM = "list-item";
+ public static final String MEDIUM = "medium";
+ public static final String NONE = "none";
+ public static final String NORMAL = "normal";
+ public static final String NOWRAP = "nowrap";
+ public static final String OBLIQUE = "oblique";
+ public static final String OUTSET = "outset";
+ public static final String OVERLINE = "overline";
+ public static final String PRE = "pre";
+ public static final String RIDGE = "ridge";
+ public static final String RIGHT = "right";
+ public static final String RUN_IN = "run-in";
+ public static final String SOLID = "solid";
+ public static final String SMALL = "small";
+ public static final String SMALL_CAPS = "small-caps";
+ public static final String SMALLER = "smaller";
+ public static final String TABLE = "table";
+ public static final String TABLE_CAPTION = "table-caption";
+ public static final String TABLE_CELL = "table-cell";
+ public static final String TABLE_COLUMN = "table-column";
+ public static final String TABLE_COLUMN_GROUP = "table-column-group";
+ public static final String TABLE_FOOTER_GROUP = "table-footer-group";
+ public static final String TABLE_HEADER_GROUP = "table-header-group";
+ public static final String TABLE_ROW = "table-row";
+ public static final String TABLE_ROW_GROUP = "table-row-group";
+ public static final String THICK = "thick";
+ public static final String THIN = "thin";
+ public static final String UNDERLINE = "underline";
+ public static final String X_LARGE = "x-large";
+ public static final String X_SMALL = "x-small";
+ public static final String XX_LARGE = "xx-large";
+ public static final String XX_SMALL = "xx-small";
+
+}
+
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/css/ColorProperty.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/css/ColorProperty.java
new file mode 100644
index 0000000..1dfd0b8
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/css/ColorProperty.java
@@ -0,0 +1,152 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.css;
+
+import java.util.HashMap;
+import java.util.Map;
+
+
+import org.eclipse.wst.xml.vex.core.internal.core.Color;
+import org.w3c.css.sac.LexicalUnit;
+
+/**
+ * Color-valued properties.
+ */
+public class ColorProperty extends AbstractProperty {
+
+ private static Map colorNames = new HashMap();
+
+ static {
+ colorNames.put(CSS.AQUA, new Color(0, 255, 255));
+ colorNames.put(CSS.BLACK, new Color(0, 0, 0));
+ colorNames.put(CSS.BLUE, new Color(0, 0, 255));
+ colorNames.put(CSS.FUCHSIA, new Color(255, 0, 255));
+ colorNames.put(CSS.GRAY, new Color(128, 128, 128));
+ colorNames.put(CSS.GREEN, new Color(0, 128, 0));
+ colorNames.put(CSS.LIME, new Color(0, 255, 0));
+ colorNames.put(CSS.MAROON, new Color(128, 0, 0));
+ colorNames.put(CSS.NAVY, new Color(0, 0, 128));
+ colorNames.put(CSS.OLIVE, new Color(128, 128, 0));
+ colorNames.put(CSS.ORANGE, new Color( 255, 165, 0));
+ colorNames.put(CSS.PURPLE, new Color(128, 0, 128));
+ colorNames.put(CSS.RED, new Color(255, 0, 0));
+ colorNames.put(CSS.SILVER, new Color(192, 192, 192));
+ colorNames.put(CSS.TEAL, new Color(0, 128, 128));
+ colorNames.put(CSS.WHITE, new Color(255, 255, 255));
+ colorNames.put(CSS.YELLOW, new Color(255, 255, 0));
+ }
+
+ /**
+ * Class constructor. The names CSS.COLOR and
+ * CSS.BACKGROUND_COLOR are treated specially, as follows.
+ *
+ * <ul>
+ * <li>If name is CSS.COLOR, it is inherited and defaults to black.</li>
+ * <li>If name is CSS.BACKGROUND_COLOR, it is not inherited and defaults
+ * to transparent (null).</li>
+ * <li>Otherwise, it is not inherited and defaults to the current color.</li>
+ * </ul>
+ *
+ * <p>Because of the default in the third case, the ColorProperty
+ * for CSS.COLOR must be processed before any others.</p>
+ * @param name Name of the element.
+ */
+ public ColorProperty(String name) {
+ super(name);
+ }
+
+ public Object calculate(LexicalUnit lu, Styles parentStyles, Styles styles) {
+
+ boolean inherit = isInherit(lu) || this.getName().equals(CSS.COLOR);
+
+ if (isColor(lu)) {
+ return getColor(lu);
+ } else if (inherit && parentStyles != null) {
+ return parentStyles.get(this.getName());
+ } else {
+ if (this.getName().equals(CSS.COLOR)) {
+ return Color.BLACK;
+ } else if (this.getName().equals(CSS.BACKGROUND_COLOR)) {
+ return null; // transparent
+ } else {
+ return styles.getColor();
+ }
+ }
+ }
+
+ /**
+ * Returns true if the given lexical unit represents a color.
+ *
+ * @param lu LexicalUnit to check.
+ */
+ public static boolean isColor(LexicalUnit lu) {
+ if (lu == null) {
+ return false;
+ } else if (lu.getLexicalUnitType() == LexicalUnit.SAC_IDENT &&
+ colorNames.containsKey(lu.getStringValue())) {
+ return true;
+ } else if (lu.getLexicalUnitType() == LexicalUnit.SAC_RGBCOLOR) {
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ //========================================================== PRIVATE
+
+ private static Color getColor(LexicalUnit lu) {
+ if (lu.getLexicalUnitType() == LexicalUnit.SAC_IDENT) {
+ String s = lu.getStringValue();
+ if (colorNames.containsKey(s)) {
+ return (Color) colorNames.get(s);
+ } else {
+ return null;
+ }
+ } else if (lu.getLexicalUnitType() == LexicalUnit.SAC_RGBCOLOR) {
+ lu = lu.getParameters();
+ int red = getColorPart(lu);
+ lu = lu.getNextLexicalUnit(); // gobble comma
+ lu = lu.getNextLexicalUnit();
+ int green = getColorPart(lu);
+ lu = lu.getNextLexicalUnit(); // gobble comma
+ lu = lu.getNextLexicalUnit();
+ int blue = getColorPart(lu);
+
+ if (red == -1 || green == -1 || blue == -1) {
+ return null;
+ } else {
+ return new Color(red, green, blue);
+ }
+ } else {
+ System.out.println("WARNING: unsupported color type: " + lu);
+ return null;
+ }
+ }
+
+ /**
+ * Converts one of the color channels into an int from 0-255, or -1
+ * if there's an error.
+ */
+ private static int getColorPart(LexicalUnit lu) {
+ int value;
+ if (lu.getLexicalUnitType() == LexicalUnit.SAC_INTEGER) {
+ value = lu.getIntegerValue();
+ } else if (lu.getLexicalUnitType() == LexicalUnit.SAC_PERCENTAGE) {
+ value = Math.round(lu.getFloatValue() * 255);
+ } else {
+ System.out.println("WARNING: unsupported color part: " + lu);
+ return -1;
+ }
+ return Math.max(0, Math.min(255, value));
+ }
+
+
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/css/DisplayProperty.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/css/DisplayProperty.java
new file mode 100644
index 0000000..919a17c
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/css/DisplayProperty.java
@@ -0,0 +1,73 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.css;
+
+import org.w3c.css.sac.LexicalUnit;
+
+/**
+ * The CSS 'display' property.
+ */
+public class DisplayProperty extends AbstractProperty {
+
+ /**
+ * Class constructor.
+ */
+ public DisplayProperty() {
+ super(CSS.DISPLAY);
+ }
+
+ public Object calculate(LexicalUnit lu, Styles parentStyles,
+ Styles styles) {
+
+ if (isDisplay(lu)) {
+ return lu.getStringValue();
+ } else if (isInherit(lu) && parentStyles != null) {
+ return parentStyles.getDisplay();
+ } else {
+ // not specified or other unknown value
+ return CSS.INLINE;
+ }
+ }
+
+ //======================================================== PRIVATE
+
+ /**
+ * Returns true if the value of the given LexicalUnit represents
+ * a valid value for this property.
+ * @param lu LexicalUnit to inspect.
+ */
+ private static boolean isDisplay(LexicalUnit lu) {
+ if (lu == null) {
+ return false;
+ } else if (lu.getLexicalUnitType() == LexicalUnit.SAC_IDENT) {
+ String s = lu.getStringValue();
+ return s.equals(CSS.BLOCK)
+ || s.equals(CSS.INLINE)
+ || s.equals(CSS.INLINE_BLOCK)
+ || s.equals(CSS.INLINE_TABLE)
+ || s.equals(CSS.LIST_ITEM)
+ || s.equals(CSS.NONE)
+ || s.equals(CSS.RUN_IN)
+ || s.equals(CSS.TABLE)
+ || s.equals(CSS.TABLE_CAPTION)
+ || s.equals(CSS.TABLE_CELL)
+ || s.equals(CSS.TABLE_COLUMN)
+ || s.equals(CSS.TABLE_COLUMN_GROUP)
+ || s.equals(CSS.TABLE_FOOTER_GROUP)
+ || s.equals(CSS.TABLE_HEADER_GROUP)
+ || s.equals(CSS.TABLE_ROW)
+ || s.equals(CSS.TABLE_ROW_GROUP);
+ } else {
+ return false;
+ }
+ }
+
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/css/FontFamilyProperty.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/css/FontFamilyProperty.java
new file mode 100644
index 0000000..ca857b3
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/css/FontFamilyProperty.java
@@ -0,0 +1,71 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.css;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.w3c.css.sac.LexicalUnit;
+
+/**
+ * The CSS 'font-family' property.
+ */
+public class FontFamilyProperty extends AbstractProperty {
+
+ /**
+ * Class constructor.
+ */
+ public FontFamilyProperty() {
+ super(CSS.FONT_FAMILY);
+ }
+
+ /**
+ *
+ */
+
+ public Object calculate(LexicalUnit lu, Styles parentStyles,
+ Styles styles) {
+ if (isFontFamily(lu)) {
+ return getFontFamilies(lu);
+ } else {
+ // not specified, "inherit", or some other value
+ if (parentStyles != null) {
+ return parentStyles.getFontFamilies();
+ } else {
+ return DEFAULT_FONT_FAMILY;
+ }
+ }
+ }
+
+ //================================================= PRIVATE
+
+ private static final String[] DEFAULT_FONT_FAMILY = new String[] { "sans-serif" };
+
+ private static boolean isFontFamily(LexicalUnit lu) {
+ return lu != null
+ && (lu.getLexicalUnitType() == LexicalUnit.SAC_STRING_VALUE
+ || lu.getLexicalUnitType() == LexicalUnit.SAC_IDENT);
+ }
+
+ private static String[] getFontFamilies(LexicalUnit lu) {
+ List list = new ArrayList();
+ while (lu != null) {
+ if (lu.getLexicalUnitType() == LexicalUnit.SAC_STRING_VALUE
+ || lu.getLexicalUnitType() == LexicalUnit.SAC_IDENT) {
+
+ list.add(lu.getStringValue());
+ }
+ lu = lu.getNextLexicalUnit();
+ }
+ return (String[]) list.toArray(new String[list.size()]);
+ }
+
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/css/FontSizeProperty.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/css/FontSizeProperty.java
new file mode 100644
index 0000000..8f30991
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/css/FontSizeProperty.java
@@ -0,0 +1,147 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.css;
+
+
+import org.eclipse.wst.xml.vex.core.internal.core.DisplayDevice;
+import org.w3c.css.sac.LexicalUnit;
+
+/**
+ * The CSS font-size property. Note that other lengths depend on the
+ * computed value of this property, so this should be evaluated early
+ * on in the stylesheet, before any other lengths.
+ */
+public class FontSizeProperty extends AbstractProperty {
+
+ /**
+ * Class constructor,
+ */
+ public FontSizeProperty() {
+ super(CSS.FONT_SIZE);
+ }
+
+ public Object calculate(LexicalUnit lu, Styles parentStyles, Styles styles) {
+ return new Float(this.calculateInternal(lu, parentStyles, styles));
+ }
+
+ /**
+ * Returns true if the given lexical unit represents a font size.
+ *
+ * @param lu LexicalUnit to check.
+ */
+ public static boolean isFontSize(LexicalUnit lu) {
+ if (lu == null) {
+ return false;
+ } else if (isLength(lu)) {
+ return true;
+ } else if (isPercentage(lu)) {
+ return true;
+ } else if (lu.getLexicalUnitType() == LexicalUnit.SAC_IDENT) {
+ String s = lu.getStringValue();
+ return s.equals(CSS.XX_SMALL)
+ || s.equals(CSS.X_SMALL)
+ || s.equals(CSS.SMALL)
+ || s.equals(CSS.MEDIUM)
+ || s.equals(CSS.LARGE)
+ || s.equals(CSS.X_LARGE)
+ || s.equals(CSS.XX_LARGE)
+ || s.equals(CSS.SMALLER)
+ || s.equals(CSS.LARGER);
+ } else {
+ return false;
+ }
+ }
+
+ //======================================================== PRIVATE
+
+ private float calculateInternal(LexicalUnit lu, Styles parentStyles, Styles styles) {
+
+ DisplayDevice device = DisplayDevice.getCurrent();
+ float baseFontSize = DEFAULT_FONT_SIZE_POINTS * device.getVerticalPPI() / 72;
+
+ if (parentStyles != null) {
+ baseFontSize = parentStyles.getFontSize();
+ }
+
+ if (lu == null) {
+ return baseFontSize;
+ } else if (isLength(lu)) {
+ return getFloatLength(lu, baseFontSize, device.getVerticalPPI());
+ } else if (isPercentage(lu)) {
+ return baseFontSize * lu.getFloatValue() / 100;
+ } else if (lu.getLexicalUnitType() == LexicalUnit.SAC_IDENT) {
+ String s = lu.getStringValue();
+
+ if (s.equals(CSS.XX_SMALL)) {
+ return baseFontSize * FONT_FACTOR_XX_SMALL;
+
+ } else if (s.equals(CSS.X_SMALL)) {
+ return baseFontSize * FONT_FACTOR_X_SMALL;
+
+ } else if (s.equals(CSS.SMALL)) {
+ return baseFontSize * FONT_FACTOR_SMALL;
+
+ } else if (s.equals(CSS.MEDIUM)) {
+ return baseFontSize * FONT_FACTOR_MEDIUM;
+
+ } else if (s.equals(CSS.LARGE)) {
+ return baseFontSize * FONT_FACTOR_LARGE;
+
+ } else if (s.equals(CSS.X_LARGE)) {
+ return baseFontSize * FONT_FACTOR_X_LARGE;
+
+ } else if (s.equals(CSS.XX_LARGE)) {
+ return baseFontSize * FONT_FACTOR_XX_LARGE;
+
+ } else if (s.equals(CSS.SMALLER)) {
+ return baseFontSize / FONT_SIZE_FACTOR;
+
+ } else if (s.equals(CSS.LARGER)) {
+ return baseFontSize * FONT_SIZE_FACTOR;
+
+ } else {
+ return baseFontSize;
+ }
+ } else {
+ return baseFontSize;
+ }
+
+
+ }
+
+ private static final float DEFAULT_FONT_SIZE_POINTS = 12;
+
+ // relative size of adjacent font size names
+ private static final float FONT_SIZE_FACTOR = 1.2f;
+
+ // Sizes of named font sizes, relative to "medium"
+ private static final float FONT_FACTOR_MEDIUM = 1.0f;
+
+ private static final float FONT_FACTOR_SMALL =
+ FONT_FACTOR_MEDIUM / FONT_SIZE_FACTOR;
+
+ private static final float FONT_FACTOR_X_SMALL =
+ FONT_FACTOR_SMALL / FONT_SIZE_FACTOR;
+
+ private static final float FONT_FACTOR_XX_SMALL =
+ FONT_FACTOR_X_SMALL / FONT_SIZE_FACTOR;
+
+ private static final float FONT_FACTOR_LARGE =
+ FONT_FACTOR_MEDIUM * FONT_SIZE_FACTOR;
+
+ private static final float FONT_FACTOR_X_LARGE =
+ FONT_FACTOR_LARGE * FONT_SIZE_FACTOR;
+
+ private static final float FONT_FACTOR_XX_LARGE =
+ FONT_FACTOR_X_LARGE * FONT_SIZE_FACTOR;
+
+
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/css/FontStyleProperty.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/css/FontStyleProperty.java
new file mode 100644
index 0000000..ebf432d
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/css/FontStyleProperty.java
@@ -0,0 +1,59 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.css;
+
+import org.w3c.css.sac.LexicalUnit;
+
+/**
+ * The CSS font-style property.
+ */
+public class FontStyleProperty extends AbstractProperty {
+
+ /**
+ * Class constructor.
+ */
+ public FontStyleProperty() {
+ super(CSS.FONT_STYLE);
+ }
+
+ public Object calculate(LexicalUnit lu, Styles parentStyles, Styles styles) {
+ if (isFontStyle(lu)) {
+ return lu.getStringValue();
+ } else {
+ // not specified, "inherit", or some other value
+ if (parentStyles != null) {
+ return parentStyles.getFontStyle();
+ } else {
+ return CSS.NORMAL;
+ }
+ }
+
+ }
+
+ /**
+ * Returns true if the given lexical unit represents a font style.
+ *
+ * @param lu LexicalUnit to check.
+ */
+ public static boolean isFontStyle(LexicalUnit lu) {
+ if (lu == null) {
+ return false;
+ } else if (lu.getLexicalUnitType() == LexicalUnit.SAC_IDENT) {
+ String s = lu.getStringValue();
+ return s.equals(CSS.NORMAL)
+ || s.equals(CSS.ITALIC)
+ || s.equals(CSS.OBLIQUE);
+ } else {
+ return false;
+ }
+ }
+
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/css/FontVariantProperty.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/css/FontVariantProperty.java
new file mode 100644
index 0000000..e6b57ca
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/css/FontVariantProperty.java
@@ -0,0 +1,58 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.css;
+
+import org.w3c.css.sac.LexicalUnit;
+
+/**
+ * The CSS font-variant property.
+ */
+public class FontVariantProperty extends AbstractProperty {
+
+ /**
+ * Class constructor.
+ */
+ public FontVariantProperty() {
+ super(CSS.FONT_VARIANT);
+ }
+
+ public Object calculate(LexicalUnit lu, Styles parentStyles, Styles styles) {
+ if (isFontVariant(lu)) {
+ return lu.getStringValue();
+ } else {
+ // not specified, "inherit", or some other value
+ if (parentStyles != null) {
+ return parentStyles.getFontStyle();
+ } else {
+ return CSS.NORMAL;
+ }
+ }
+
+ }
+
+ /**
+ * Returns true if the given lexical unit represents a font variant.
+ *
+ * @param lu LexicalUnit to check.
+ */
+ public static boolean isFontVariant(LexicalUnit lu) {
+ if (lu == null) {
+ return false;
+ } else if (lu.getLexicalUnitType() == LexicalUnit.SAC_IDENT) {
+ String s = lu.getStringValue();
+ return s.equals(CSS.NORMAL)
+ || s.equals(CSS.SMALL_CAPS);
+ } else {
+ return false;
+ }
+ }
+
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/css/FontWeightProperty.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/css/FontWeightProperty.java
new file mode 100644
index 0000000..9336790
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/css/FontWeightProperty.java
@@ -0,0 +1,100 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.css;
+
+import org.w3c.css.sac.LexicalUnit;
+
+/**
+ * The CSS font-weight property.
+ */
+public class FontWeightProperty extends AbstractProperty {
+
+ private static final int FONT_WEIGHT_NORMAL = 400;
+ private static final int FONT_WEIGHT_BOLD = 700;
+
+ /**
+ * Class constructor.
+ */
+ public FontWeightProperty() {
+ super(CSS.FONT_WEIGHT);
+ }
+
+ public Object calculate(LexicalUnit lu, Styles parentStyles, Styles styles) {
+ return new Integer(this.calculateInternal(lu, parentStyles, styles));
+ }
+
+ public int calculateInternal(LexicalUnit lu, Styles parentStyles, Styles styles) {
+ if (isFontWeight(lu)) {
+ return getFontWeight(lu, parentStyles);
+ } else {
+ // not specified, "inherit", or some other value
+ if (parentStyles != null) {
+ return parentStyles.getFontWeight();
+ } else {
+ return FONT_WEIGHT_NORMAL;
+ }
+ }
+
+ }
+
+ /**
+ * Returns true if the given lexical unit represents a font weight.
+ *
+ * @param lu LexicalUnit to check.
+ */
+ public static boolean isFontWeight(LexicalUnit lu) {
+ if (lu == null) {
+ return false;
+ } else if (lu.getLexicalUnitType() == LexicalUnit.SAC_INTEGER) {
+ return true;
+ } else if (lu.getLexicalUnitType() == LexicalUnit.SAC_IDENT) {
+ String s = lu.getStringValue();
+ return s.equals(CSS.NORMAL)
+ || s.equals(CSS.BOLD)
+ || s.equals(CSS.BOLDER)
+ || s.equals(CSS.LIGHTER);
+ } else {
+ return false;
+ }
+ }
+
+ private static int getFontWeight(LexicalUnit lu, Styles parentStyles) {
+ if (lu == null) {
+ return FONT_WEIGHT_NORMAL;
+ } else if (lu.getLexicalUnitType() == LexicalUnit.SAC_INTEGER) {
+ return lu.getIntegerValue();
+ } else if (lu.getLexicalUnitType() == LexicalUnit.SAC_IDENT) {
+ String s = lu.getStringValue();
+ if (s.equals(CSS.NORMAL)) {
+ return FONT_WEIGHT_NORMAL;
+ } else if (s.equals(CSS.BOLD)) {
+ return FONT_WEIGHT_BOLD;
+ } else if (s.equals(CSS.BOLDER)) {
+ if (parentStyles != null) {
+ return parentStyles.getFontWeight() + 151;
+ } else {
+ return FONT_WEIGHT_BOLD;
+ }
+ } else if (s.equals(CSS.LIGHTER)) {
+ if (parentStyles != null) {
+ return parentStyles.getFontWeight() - 151;
+ } else {
+ return FONT_WEIGHT_NORMAL;
+ }
+ } else {
+ return FONT_WEIGHT_NORMAL;
+ }
+ } else {
+ return FONT_WEIGHT_NORMAL;
+ }
+ }
+
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/css/IProperty.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/css/IProperty.java
new file mode 100644
index 0000000..3190b35
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/css/IProperty.java
@@ -0,0 +1,43 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.css;
+
+import org.w3c.css.sac.LexicalUnit;
+
+/**
+ * Represents a CSS property.
+ */
+public interface IProperty {
+
+ /** Constant indicating the length is along the horizontal axis. */
+ public static final byte AXIS_HORIZONTAL = 0;
+
+ /** Constant indicating the length is along the vertical axis. */
+ public static final byte AXIS_VERTICAL = 1;
+
+
+ /**
+ * Returns the name of the property.
+ */
+ public String getName();
+
+ /**
+ * Calculates the value of a property given a LexicalUnit.
+ * @param lu LexicalUnit to interpret.
+ * @param parentStyles Styles of the parent element. These are used
+ * when the property inherits a value.
+ * @param styles Styles currently in effect. Often, the calculated
+ * value depends on previously calculated styles such as font size
+ * and color.
+ */
+ public Object calculate(LexicalUnit lu, Styles parentStyles, Styles styles);
+
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/css/LengthProperty.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/css/LengthProperty.java
new file mode 100644
index 0000000..fc6c74e
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/css/LengthProperty.java
@@ -0,0 +1,49 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.css;
+
+
+import org.eclipse.wst.xml.vex.core.internal.core.DisplayDevice;
+import org.w3c.css.sac.LexicalUnit;
+
+/**
+ * A property that represents lengths, such as a margin or padding.
+ */
+public class LengthProperty extends AbstractProperty {
+
+ public LengthProperty(String name, byte axis) {
+ super(name);
+ this.axis = axis;
+ }
+
+ public Object calculate(LexicalUnit lu, Styles parentStyles, Styles styles) {
+
+ DisplayDevice device = DisplayDevice.getCurrent();
+ int ppi = this.axis == AXIS_HORIZONTAL ? device.getHorizontalPPI() : device.getVerticalPPI();
+
+ if (isLength(lu)) {
+ int length = getIntLength(lu, styles.getFontSize(), ppi);
+ return RelativeLength.createAbsolute(length);
+ } else if (isPercentage(lu)) {
+ return RelativeLength.createRelative(lu.getFloatValue() / 100);
+ } else if (isInherit(lu) && parentStyles != null) {
+ return parentStyles.get(this.getName());
+ } else {
+ // not specified, "auto", or other unknown value
+ return RelativeLength.createAbsolute(0);
+ }
+ }
+
+
+ //============================================================ PRIVATE
+
+ private int axis;
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/css/LineHeightProperty.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/css/LineHeightProperty.java
new file mode 100644
index 0000000..8ef5f2f
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/css/LineHeightProperty.java
@@ -0,0 +1,63 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.css;
+
+
+import org.eclipse.wst.xml.vex.core.internal.core.DisplayDevice;
+import org.w3c.css.sac.LexicalUnit;
+
+/**
+ * The CSS line-height property.
+ */
+public class LineHeightProperty extends AbstractProperty {
+
+ private static final float LINE_HEIGHT_NORMAL = 1.2f;
+
+ /**
+ * Class constructor.
+ */
+ public LineHeightProperty() {
+ super(CSS.LINE_HEIGHT);
+ }
+
+ /**
+ * Calculates the value of the property given a LexicalUnit. Returns
+ * a RelativeLength that is relative to the current font size.
+ */
+ public Object calculate(LexicalUnit lu, Styles parentStyles, Styles styles) {
+
+ int ppi = DisplayDevice.getCurrent().getVerticalPPI();
+
+ if (isLength(lu)) {
+ return RelativeLength.createAbsolute(Math.round(getIntLength(lu, styles.getFontSize(), ppi) / styles.getFontSize()));
+ } else if (isNumber(lu)) {
+ if (getNumber(lu) <= 0) {
+ return RelativeLength.createRelative(LINE_HEIGHT_NORMAL);
+ } else {
+ return RelativeLength.createRelative(getNumber(lu));
+ }
+ } else if (isPercentage(lu)) {
+ if (lu.getFloatValue() <= 0) {
+ return RelativeLength.createRelative(LINE_HEIGHT_NORMAL);
+ } else {
+ return RelativeLength.createRelative(lu.getFloatValue() / 100);
+ }
+ } else {
+ // not specified, "inherit", or other unknown value
+ if (parentStyles == null) {
+ return RelativeLength.createRelative(LINE_HEIGHT_NORMAL);
+ } else {
+ return (RelativeLength) parentStyles.get(CSS.LINE_HEIGHT);
+ }
+ }
+ }
+
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/css/ListStyleTypeProperty.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/css/ListStyleTypeProperty.java
new file mode 100644
index 0000000..efad554
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/css/ListStyleTypeProperty.java
@@ -0,0 +1,67 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.css;
+
+import org.w3c.css.sac.LexicalUnit;
+
+/**
+ * The CSS list-style-type property.
+ */
+public class ListStyleTypeProperty extends AbstractProperty {
+
+ public ListStyleTypeProperty() {
+ super(CSS.LIST_STYLE_TYPE);
+ }
+
+ public Object calculate(LexicalUnit lu, Styles parentStyles, Styles styles) {
+ if (isListStyleType(lu)) {
+ return lu.getStringValue();
+ } else {
+ if (parentStyles == null) {
+ return CSS.DISC;
+ } else {
+ return parentStyles.getListStyleType();
+ }
+ }
+
+ }
+
+ private static boolean isListStyleType(LexicalUnit lu) {
+
+ if (lu == null || lu.getLexicalUnitType() != LexicalUnit.SAC_IDENT) {
+ return false;
+ }
+
+ String s = lu.getStringValue();
+ return s.equals(CSS.ARMENIAN)
+ || s.equals(CSS.CIRCLE)
+ || s.equals(CSS.CJK_IDEOGRAPHIC)
+ || s.equals(CSS.DECIMAL)
+ || s.equals(CSS.DECIMAL_LEADING_ZERO)
+ || s.equals(CSS.DISC)
+ || s.equals(CSS.GEORGIAN)
+ || s.equals(CSS.HEBREW)
+ || s.equals(CSS.HIRAGANA)
+ || s.equals(CSS.HIRAGANA_IROHA)
+ || s.equals(CSS.KATAKANA)
+ || s.equals(CSS.KATAKANA_IROHA)
+ || s.equals(CSS.LOWER_ALPHA)
+ || s.equals(CSS.LOWER_GREEK)
+ || s.equals(CSS.LOWER_LATIN)
+ || s.equals(CSS.LOWER_ROMAN)
+ || s.equals(CSS.NONE)
+ || s.equals(CSS.SQUARE)
+ || s.equals(CSS.UPPER_ALPHA)
+ || s.equals(CSS.UPPER_LATIN)
+ || s.equals(CSS.UPPER_ROMAN);
+ }
+
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/css/PropertyDecl.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/css/PropertyDecl.java
new file mode 100644
index 0000000..76d21a3
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/css/PropertyDecl.java
@@ -0,0 +1,121 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.css;
+
+import java.io.Serializable;
+
+import org.w3c.css.sac.LexicalUnit;
+
+/**
+ * Represents a particular CSS property declaration.
+ */
+public class PropertyDecl implements Comparable, Serializable {
+
+ private static final long serialVersionUID = 1L;
+
+ public static final byte SOURCE_DEFAULT = 0;
+ public static final byte SOURCE_AUTHOR = 1;
+ public static final byte SOURCE_USER = 2;
+
+ private Rule rule;
+ private String property;
+ private LexicalUnit value;
+ private boolean important;
+
+ /**
+ * Class constructor.
+ */
+ public PropertyDecl(Rule rule,
+ String property,
+ LexicalUnit value,
+ boolean important) {
+ this.rule = rule;
+ this.property = property;
+ this.value = value;
+ this.important = important;
+ }
+
+ /**
+ * Implementation of <code>Comparable.compareTo(Object)</code>
+ * that implements CSS cascade ordering.
+ */
+ public int compareTo(Object o) {
+ PropertyDecl other = (PropertyDecl) o;
+ int thisWeight = this.getWeight();
+ int otherWeight = other.getWeight();
+ if (thisWeight != otherWeight) {
+ return thisWeight - otherWeight;
+ }
+
+ int thisSpec = this.getRule().getSpecificity();
+ int otherSpec = other.getRule().getSpecificity();
+
+ return thisSpec - otherSpec;
+ }
+
+ /**
+ * Return the value of the <code>important</code> property.
+ */
+ public boolean isImportant() {
+ return this.important;
+ }
+
+ /**
+ * Return the value of the <code>property</code> property.
+ */
+ public String getProperty() {
+ return this.property;
+ }
+
+ /**
+ * Return the value of the <code>rule</code> property.
+ */
+ public Rule getRule() {
+ return this.rule;
+ }
+
+ /**
+ * Return the value of the <code>value</code> property.
+ */
+ public LexicalUnit getValue() {
+ return this.value;
+ }
+
+
+ //===================================================== PRIVATE
+
+ /**
+ * Returns the weight of this declaration, as follows...
+ *
+ * <pre>
+ * 4 => user stylesheet, important decl
+ * 3 => author stylesheet, important decl
+ * 2 => author stylesheet, not important
+ * 1 => user stylesheet, not important
+ * 0 => default stylesheet
+ * </pre>
+ */
+ private int getWeight() {
+ int source = this.getRule().getSource();
+ if (this.isImportant() && source == StyleSheet.SOURCE_USER) {
+ return 4;
+ } else if (this.isImportant() && source == StyleSheet.SOURCE_AUTHOR) {
+ return 3;
+ } else if (!this.isImportant() && source == StyleSheet.SOURCE_AUTHOR) {
+ return 2;
+ } else if (!this.isImportant() && source == StyleSheet.SOURCE_USER) {
+ return 1;
+ } else {
+ return 0;
+ }
+ }
+}
+
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/css/PseudoElement.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/css/PseudoElement.java
new file mode 100644
index 0000000..bb8fbda
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/css/PseudoElement.java
@@ -0,0 +1,51 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.css;
+
+import org.eclipse.wst.xml.vex.core.internal.dom.Element;
+
+/**
+ * Represents a :before or :after pseudo-element.
+ */
+public class PseudoElement extends Element {
+
+ public static final String AFTER = "after";
+ public static final String BEFORE = "before";
+
+ /**
+ * Class constructor.
+ * @param parent Parent element to this pseudo-element.
+ * @param name Name of this pseudo-element, e.g. PseudoElement.BEFORE.
+ */
+ public PseudoElement(Element parent, String name) {
+ super(name);
+ this.setParent(parent);
+ }
+
+ /**
+ * @see java.lang.Object#equals(java.lang.Object)
+ */
+ public boolean equals(Object o) {
+ if (o == null || o.getClass() != this.getClass()) {
+ return false;
+ }
+ PseudoElement other = (PseudoElement) o;
+ return this.getParent() == other.getParent()
+ && this.getName().equals(other.getName());
+ }
+
+ /**
+ * @see java.lang.Object#hashCode()
+ */
+ public int hashCode() {
+ return this.getParent().hashCode() + this.getName().hashCode();
+ }
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/css/RelativeLength.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/css/RelativeLength.java
new file mode 100644
index 0000000..86eed18
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/css/RelativeLength.java
@@ -0,0 +1,72 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.css;
+
+import java.io.Serializable;
+
+/**
+ * A length that may be expressed as an absolute or relative value.
+ */
+public class RelativeLength implements Serializable {
+
+ private static final long serialVersionUID = 1L;
+
+ private float percentage;
+ private int absolute;
+ boolean isAbsolute;
+
+ private static RelativeLength ZERO = new RelativeLength(0, 0, true);
+
+ /**
+ * Create a relative length representing an absolute value.
+ * @return the new RelativeLength value.
+ */
+ public static RelativeLength createAbsolute(int value) {
+ if (value == 0) {
+ return ZERO;
+ } else {
+ return new RelativeLength(0, value, true);
+ }
+ }
+
+ /**
+ * Create a relative length representing a relative value.
+ * @return the new RelativeLength value.
+ */
+ public static RelativeLength createRelative(float percentage) {
+ return new RelativeLength(percentage, 0, false);
+ }
+
+ /**
+ * Return the value of the length given a reference value. If this
+ * object represents an absolute value, that value is simply returned.
+ * Otherwise, returns the given reference length multiplied by the given
+ * percentage and rounded to the nearest integer.
+ * @param referenceLength reference length by which percentage lengths
+ * will by multiplied.
+ * @return the actual value
+ */
+ public int get(int referenceLength) {
+ if (this.isAbsolute) {
+ return this.absolute;
+ } else {
+ return Math.round(this.percentage * referenceLength);
+ }
+ }
+
+ //==================================================== PRIVATE
+
+ private RelativeLength(float percentage, int absolute, boolean isAbsolute) {
+ this.percentage = percentage;
+ this.absolute = absolute;
+ this.isAbsolute = isAbsolute;
+ }
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/css/Rule.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/css/Rule.java
new file mode 100644
index 0000000..bc4e1e4
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/css/Rule.java
@@ -0,0 +1,342 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * John Krasnay - initial API and implementation
+ * Dave Holroyd - Proper specificity for wildcard selector
+ * John Austin - Implement sibling selectors
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.css;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.StringTokenizer;
+
+
+import org.eclipse.wst.xml.vex.core.internal.dom.Element;
+import org.w3c.css.sac.AttributeCondition;
+import org.w3c.css.sac.CombinatorCondition;
+import org.w3c.css.sac.Condition;
+import org.w3c.css.sac.ConditionalSelector;
+import org.w3c.css.sac.DescendantSelector;
+import org.w3c.css.sac.ElementSelector;
+import org.w3c.css.sac.NegativeSelector;
+import org.w3c.css.sac.Selector;
+import org.w3c.css.sac.SiblingSelector;
+
+/**
+ * Represents a pairing of a selector with a list of styles. This does
+ * not exactly correspond to a rule in a style sheet; there is only
+ * one selector associated with an instance of this class, whereas
+ * multiple selectors may be associated with a style sheet rule.
+ *
+ * Note: <code>Rule</code> implements the <code>Comparable</code>
+ * interface in order to be sorted by "specificity" as defined by the
+ * CSS spec. However, this ordering is <em>not</em> consistent with
+ * <code>equals</code> (rules with the same specificity may not be
+ * equal). Therefore, <code>Rule</code> objects should not be used
+ * with sorted collections or maps in the <code>java.util</code>
+ * package, unless a suitable <code>Comparator</code> is also used.
+ */
+public class Rule implements Serializable {
+
+ private static final long serialVersionUID = 1L;
+
+ private byte source;
+ private Selector selector;
+ private List propertyDecls = new ArrayList();
+
+ /**
+ * Class constructor.
+ *
+ * @param source Source of the rule.
+ * @param selector Selector for the rule.
+ */
+ public Rule(byte source, Selector selector) {
+ this.source = source;
+ this.selector = selector;
+ }
+
+ /**
+ * Adds a property declaration to the rule.
+ *
+ * @param decl new property declaration to add
+ */
+ public void add(PropertyDecl decl) {
+ propertyDecls.add(decl);
+ }
+
+ /**
+ * Returns the selector for the rule.
+ */
+ public Selector getSelector() {
+ return this.selector;
+ }
+
+ /**
+ * Returns the source of this rule.
+ * @return one of StyleSheet.SOURCE_DEFAULT, StyleSheet.SOURCE_AUTHOR, or
+ * StyleSheet.SOURCE_USER.
+ */
+ public byte getSource() {
+ return this.source;
+ }
+
+ /**
+ * Returns an array of the property declarations in this rule.
+ */
+ public PropertyDecl[] getPropertyDecls() {
+ return (PropertyDecl[])
+ this.propertyDecls.toArray(new PropertyDecl[propertyDecls.size()]);
+ }
+
+ /**
+ * Calculates the specificity for the selector associated with
+ * this rule. The specificity is represented as an integer whose
+ * base-10 representation, xxxyyyzzz, can be decomposed into the
+ * number of "id" selectors (xxx), "class" selectors (yyy), and
+ * "element" selectors (zzz). Composite selectors result in a
+ * recursive call.
+ */
+ public int getSpecificity() {
+ return specificity(this.getSelector());
+ }
+
+ /**
+ * Returns true if the given element matches this rule's selector.
+ *
+ * @param element Element to check.
+ */
+ public boolean matches(Element element) {
+ return matches(this.selector, element);
+ }
+
+ //==================================================== PRIVATE
+
+ /**
+ * Returns true if the given element matches the given selector.
+ */
+ private static boolean matches(Selector selector, Element element) {
+
+ if (element == null) {
+ // This can happen when, e.g., with the rule "foo > *".
+ // Since the root element matches the "*", we check if
+ // its parent matches "foo", but of course its parent
+ // is null
+ return false;
+ }
+
+ String elementName = element.getName();
+ int selectorType = selector.getSelectorType();
+
+ switch (selectorType) {
+
+ case Selector.SAC_ANY_NODE_SELECTOR:
+ // You'd think we land here if we have a * rule, but instead
+ // it appears we get a SAC_ELEMENT_NODE_SELECTOR with localName==null
+ return true;
+
+ case Selector.SAC_CONDITIONAL_SELECTOR:
+ // This little wart is the product of a mismatch btn the CSS
+ // spec an the Flute parser. CSS treats pseudo-elements as elements
+ // attached to their parents, while Flute treats them like attributes
+ ConditionalSelector cs = (ConditionalSelector) selector;
+ if (cs.getCondition().getConditionType() == Condition.SAC_PSEUDO_CLASS_CONDITION) {
+ if (element instanceof PseudoElement) {
+ AttributeCondition ac = (AttributeCondition) cs.getCondition();
+ return ac.getValue().equals(element.getName())
+ && matches(cs.getSimpleSelector(), element.getParent());
+ } else {
+ return false;
+ }
+ } else {
+ return matches(cs.getSimpleSelector(), element) &&
+ matchesCondition(cs.getCondition(), element);
+ }
+
+ case Selector.SAC_ELEMENT_NODE_SELECTOR:
+ String selectorName = ((ElementSelector)selector).getLocalName();
+ if (selectorName == null) {
+ // We land here if we have a wildcard selector (*) or
+ // a pseudocondition w/o an element name (:before)
+ // Probably other situations too (conditional w/o element
+ // name? e.g. [attr=value])
+ return true;
+ }
+ if (selectorName.equals(elementName)) {
+ return true;
+ }
+ break;
+
+ case Selector.SAC_DESCENDANT_SELECTOR:
+ DescendantSelector ds = (DescendantSelector) selector;
+ return matches(ds.getSimpleSelector(), element) &&
+ matchesAncestor(ds.getAncestorSelector(), element.getParent());
+
+ case Selector.SAC_CHILD_SELECTOR:
+ DescendantSelector ds2 = (DescendantSelector) selector;
+ Element parent = element.getParent();
+ if (element instanceof PseudoElement) {
+ parent = parent.getParent(); // sigh - this looks inelegant, but whatcha gonna do?
+ }
+ return matches(ds2.getSimpleSelector(), element) &&
+ matches(ds2.getAncestorSelector(), parent);
+
+ case Selector.SAC_DIRECT_ADJACENT_SELECTOR:
+
+ SiblingSelector ss = (SiblingSelector) selector;
+
+ if (element != null && element.getParent() != null
+ && matches(ss.getSiblingSelector(), element) ) {
+
+ // find next sibling
+
+ final Iterator i = element.getParent().getChildIterator();
+ Element e = null;
+ Element f = null;
+
+ while (i.hasNext() && e != element) {
+ f = e;
+ e = (Element)i.next();
+ }
+
+ if (e == element) {
+ return matches(ss.getSelector(), f);
+ }
+ }
+ return false;
+
+ default:
+ //System.out.println("DEBUG: selector type not supported");
+ // TODO: warning: selector not supported
+ }
+ return false;
+ }
+
+ /**
+ * Returns true if some ancestor of the given element matches the given
+ * selector.
+ */
+ private static boolean matchesAncestor(Selector selector, Element element) {
+ Element e = element;
+ while (e != null) {
+ if (matches(selector, e)) {
+ return true;
+ }
+ e = e.getParent();
+ }
+ return false;
+ }
+
+ private static boolean matchesCondition(Condition condition, Element element) {
+
+ AttributeCondition acon;
+ String attributeName;
+ String value;
+
+ switch (condition.getConditionType()) {
+ case Condition.SAC_PSEUDO_CLASS_CONDITION:
+ return false;
+
+ case Condition.SAC_ATTRIBUTE_CONDITION:
+ acon = (AttributeCondition)condition;
+ value = element.getAttribute(acon.getLocalName());
+ if (acon.getValue() != null) {
+ return acon.getValue().equals(value);
+ } else {
+ return value != null;
+ }
+
+ case Condition.SAC_ONE_OF_ATTRIBUTE_CONDITION:
+ case Condition.SAC_CLASS_CONDITION:
+
+ acon = (AttributeCondition)condition;
+
+ if (condition.getConditionType() == Condition.SAC_CLASS_CONDITION) {
+ attributeName = "class";
+ } else {
+ attributeName = acon.getLocalName();
+ }
+
+ value = element.getAttribute(attributeName);
+ if (value == null) {
+ return false;
+ }
+ StringTokenizer st = new StringTokenizer(value);
+ while (st.hasMoreTokens()) {
+ if (st.nextToken().equals(acon.getValue())) {
+ return true;
+ }
+ }
+ return false;
+
+
+ case Condition.SAC_AND_CONDITION:
+ CombinatorCondition ccon = (CombinatorCondition)condition;
+ return matchesCondition(ccon.getFirstCondition(), element)
+ && matchesCondition(ccon.getSecondCondition(), element);
+
+ default:
+ // TODO: warning: condition not supported
+ System.out.println("Unsupported condition type: " + condition.getConditionType());
+ }
+ return false;
+ }
+
+ /**
+ * Calculates the specificity for a selector.
+ */
+ private static int specificity(Selector sel) {
+ if (sel instanceof ElementSelector) {
+ if (((ElementSelector)sel).getLocalName() == null) {
+ // actually wildcard selector -- see comment in matches()
+ return 0;
+ } else {
+ return 1;
+ }
+ } else if (sel instanceof DescendantSelector) {
+ DescendantSelector ds = (DescendantSelector) sel;
+ return specificity(ds.getAncestorSelector()) +
+ specificity(ds.getSimpleSelector());
+ } else if (sel instanceof SiblingSelector) {
+ SiblingSelector ss = (SiblingSelector) sel;
+ return specificity(ss.getSelector()) +
+ specificity(ss.getSiblingSelector());
+ } else if (sel instanceof NegativeSelector) {
+ NegativeSelector ns = (NegativeSelector) sel;
+ return specificity(ns.getSimpleSelector());
+ } else if (sel instanceof ConditionalSelector) {
+ ConditionalSelector cs = (ConditionalSelector) sel;
+ return specificity(cs.getCondition()) +
+ specificity(cs.getSimpleSelector());
+ } else {
+ return 0;
+ }
+ }
+
+ /**
+ * Calculates the specificity for a condition.
+ */
+ private static int specificity(Condition cond) {
+ if (cond instanceof CombinatorCondition) {
+ CombinatorCondition cc = (CombinatorCondition) cond;
+ return specificity(cc.getFirstCondition()) +
+ specificity(cc.getSecondCondition());
+ } else if (cond instanceof AttributeCondition) {
+ if (cond.getConditionType() == Condition.SAC_ID_CONDITION) {
+ return 1000000;
+ } else {
+ return 1000;
+ }
+ } else {
+ return 0;
+ }
+ }
+}
+
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/css/SacFactory.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/css/SacFactory.java
new file mode 100644
index 0000000..9ef27b4
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/css/SacFactory.java
@@ -0,0 +1,448 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.css;
+
+import java.io.Serializable;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.w3c.css.sac.AttributeCondition;
+import org.w3c.css.sac.CharacterDataSelector;
+import org.w3c.css.sac.CombinatorCondition;
+import org.w3c.css.sac.Condition;
+import org.w3c.css.sac.ConditionalSelector;
+import org.w3c.css.sac.ContentCondition;
+import org.w3c.css.sac.DescendantSelector;
+import org.w3c.css.sac.ElementSelector;
+import org.w3c.css.sac.LangCondition;
+import org.w3c.css.sac.LexicalUnit;
+import org.w3c.css.sac.NegativeCondition;
+import org.w3c.css.sac.NegativeSelector;
+import org.w3c.css.sac.PositionalCondition;
+import org.w3c.css.sac.ProcessingInstructionSelector;
+import org.w3c.css.sac.Selector;
+import org.w3c.css.sac.SiblingSelector;
+import org.w3c.css.sac.SimpleSelector;
+
+/**
+ * Factory for producing serializable Conditions, LexicalUnits, and
+ * Selectors. The SAC parser creates instances of these that may not be
+ * serializable (and in fact, those from Flute aren't). To serialize
+ * StyleSheets, which contain references to these SAC objects, we must
+ * clone the objects into our own serializable ones.
+ */
+public class SacFactory {
+
+ /**
+ * Clone the given condition, returning one that is serializable.
+ * @param condition Condition to clone.
+ */
+ public Condition cloneCondition(Condition condition) {
+ Condition clone = null;
+ if (condition == null) {
+ return null;
+ } else if (condition instanceof AttributeCondition) {
+ clone = new AttributeConditionImpl((AttributeCondition) condition);
+ } else if (condition instanceof CombinatorCondition) {
+ clone = new CombinatorConditionImpl((CombinatorCondition) condition, this);
+ } else if (condition instanceof ContentCondition) {
+ clone = new ContentConditionImpl((ContentCondition) condition);
+ } else if (condition instanceof LangCondition) {
+ clone = new LangConditionImpl((LangCondition) condition);
+ } else if (condition instanceof NegativeCondition) {
+ clone = new NegativeConditionImpl((NegativeCondition) condition, this);
+ } else if (condition instanceof PositionalCondition) {
+ clone = new PositionalConditionImpl((PositionalCondition) condition);
+ } else {
+ throw new IllegalArgumentException("Unsupported condition type: " + condition.getClass());
+ }
+ return clone;
+ }
+
+ /**
+ * Clone the given lexical unit, returning one that is serializable.
+ * @param lu LexicalUnit to clone.
+ */
+ public LexicalUnit cloneLexicalUnit(LexicalUnit lu) {
+ if (lu == null) {
+ return null;
+ } else if (this.cache.containsKey(lu)) {
+ return (LexicalUnit) this.cache.get(lu);
+ } else {
+ return new LexicalUnitImpl(lu, this);
+ }
+ }
+
+ /**
+ * Clone the given selector, returning one that is serializable.
+ */
+ public Selector cloneSelector(Selector selector) {
+ Selector clone = null;
+ if (selector == null) {
+ return null;
+ } else if (selector instanceof CharacterDataSelector) {
+ clone = new CharacterDataSelectorImpl((CharacterDataSelector) selector);
+ } else if (selector instanceof ConditionalSelector) {
+ clone = new ConditionalSelectorImpl((ConditionalSelector) selector, this);
+ } else if (selector instanceof DescendantSelector) {
+ clone = new DescendantSelectorImpl((DescendantSelector) selector, this);
+ } else if (selector instanceof ElementSelector) {
+ clone = new ElementSelectorImpl((ElementSelector) selector);
+ } else if (selector instanceof NegativeSelector) {
+ clone = new NegativeSelectorImpl((NegativeSelector) selector, this);
+ } else if (selector instanceof ProcessingInstructionSelector) {
+ clone = new ProcessingInstructionSelectorImpl((ProcessingInstructionSelector) selector);
+ } else if (selector instanceof SiblingSelector) {
+ clone = new SiblingSelectorImpl((SiblingSelector) selector, this);
+ } else {
+ throw new IllegalArgumentException("Unsupported selector type: " + selector.getClass());
+ }
+ return clone;
+ }
+
+ //===================================================== PRIVATE
+
+ private Map cache = new HashMap();
+
+ private static class ConditionImpl implements Condition, Serializable {
+ public ConditionImpl(Condition condition) {
+ this.type = condition.getConditionType();
+ }
+ public short getConditionType() {
+ return this.type;
+ }
+ private short type;
+ }
+
+ private static class AttributeConditionImpl extends ConditionImpl
+ implements AttributeCondition {
+ public AttributeConditionImpl(AttributeCondition condition) {
+ super(condition);
+ this.namespaceURI = condition.getNamespaceURI();
+ this.localName = condition.getLocalName();
+ this.specified = condition.getSpecified();
+ this.value = condition.getValue();
+ }
+ public String getNamespaceURI() {
+ return this.namespaceURI;
+ }
+ public String getLocalName() {
+ return this.localName;
+ }
+ public boolean getSpecified() {
+ return this.specified;
+ }
+ public String getValue() {
+ return this.value;
+ }
+ private String namespaceURI;
+ private String localName;
+ private boolean specified;
+ private String value;
+ }
+
+ private static class CombinatorConditionImpl extends ConditionImpl
+ implements CombinatorCondition {
+ public CombinatorConditionImpl(CombinatorCondition condition, SacFactory factory) {
+ super(condition);
+ this.firstCondition = factory.cloneCondition(condition.getFirstCondition());
+ this.secondCondition = factory.cloneCondition(condition.getSecondCondition());
+ }
+ public Condition getFirstCondition() {
+ return this.firstCondition;
+ }
+ public Condition getSecondCondition() {
+ return this.secondCondition;
+ }
+ private Condition firstCondition;
+ private Condition secondCondition;
+ }
+
+ private static class ContentConditionImpl extends ConditionImpl
+ implements ContentCondition {
+ public ContentConditionImpl(ContentCondition condition) {
+ super(condition);
+ this.data = condition.getData();
+ }
+ public String getData() {
+ return this.data;
+ }
+ private String data;
+ }
+
+ private static class LangConditionImpl extends ConditionImpl
+ implements LangCondition {
+ public LangConditionImpl(LangCondition condition) {
+ super(condition);
+ this.lang = condition.getLang();
+ }
+ public String getLang() {
+ return this.lang;
+ }
+ private String lang;
+ }
+
+ private static class NegativeConditionImpl extends ConditionImpl
+ implements NegativeCondition {
+ public NegativeConditionImpl(NegativeCondition condition, SacFactory factory) {
+ super(condition);
+ this.condition = factory.cloneCondition(condition.getCondition());
+ }
+ public Condition getCondition() {
+ return this.condition;
+ }
+ private Condition condition;
+ }
+
+ private static class PositionalConditionImpl extends ConditionImpl
+ implements PositionalCondition {
+ public PositionalConditionImpl(PositionalCondition condition) {
+ super(condition);
+ this.position = condition.getPosition();
+ this.typeNode = condition.getTypeNode();
+ this.type = condition.getType();
+ }
+ public int getPosition() {
+ return this.position;
+ }
+ public boolean getTypeNode() {
+ return this.typeNode;
+ }
+ public boolean getType() {
+ return this.type;
+ }
+ private int position;
+ private boolean typeNode;
+ private boolean type;
+ }
+
+
+ private static class LexicalUnitImpl implements LexicalUnit, Serializable {
+
+ public LexicalUnitImpl(LexicalUnit lu, SacFactory factory) {
+ factory.cache.put(lu, this);
+ this.type = lu.getLexicalUnitType();
+ this.s = lu.getStringValue();
+ this.i = lu.getIntegerValue();
+ this.f = lu.getFloatValue();
+ this.functionName = lu.getFunctionName();
+ this.next = factory.cloneLexicalUnit(lu.getNextLexicalUnit());
+ this.prev = factory.cloneLexicalUnit(lu.getPreviousLexicalUnit());
+ this.parameters = factory.cloneLexicalUnit(lu.getParameters());
+ if (this.type == SAC_PERCENTAGE
+ || this.type == SAC_EM
+ || this.type == SAC_EX
+ || this.type == SAC_PIXEL
+ || this.type == SAC_CENTIMETER
+ || this.type == SAC_MILLIMETER
+ || this.type == SAC_INCH
+ || this.type == SAC_POINT
+ || this.type == SAC_PICA
+ || this.type == SAC_DEGREE
+ || this.type == SAC_RADIAN
+ || this.type == SAC_GRADIAN
+ || this.type == SAC_MILLISECOND
+ || this.type == SAC_SECOND
+ || this.type == SAC_HERTZ
+ || this.type == SAC_KILOHERTZ
+ || this.type == SAC_DIMENSION) {
+ this.dimensionText = lu.getDimensionUnitText();
+ }
+ this.stringRepresentation = lu.toString();
+ }
+
+ public short getLexicalUnitType() {
+ return this.type;
+ }
+
+ public LexicalUnit getNextLexicalUnit() {
+ return this.next;
+ }
+
+ public LexicalUnit getPreviousLexicalUnit() {
+ return this.prev;
+ }
+
+ public int getIntegerValue() {
+ return this.i;
+ }
+
+ public float getFloatValue() {
+ return this.f;
+ }
+
+ public String getDimensionUnitText() {
+ return this.dimensionText;
+ }
+
+ public String getFunctionName() {
+ return this.functionName;
+ }
+
+ public LexicalUnit getParameters() {
+ return this.parameters;
+ }
+
+ public String getStringValue() {
+ return this.s;
+ }
+
+ public String getStringRepresentation() {
+ return this.stringRepresentation;
+ }
+
+ public LexicalUnit getSubValues() {
+ return this.parameters;
+ }
+
+ //================================================= PRIVATE
+
+ private LexicalUnitImpl() {
+ }
+
+ private short type;
+ private LexicalUnit next;
+ private LexicalUnit prev;
+ private LexicalUnit parameters;
+ private String s;
+ private int i;
+ private float f;
+ private String functionName;
+ private String dimensionText;
+ private String stringRepresentation;
+ }
+
+ private static class SelectorImpl implements Selector, Serializable {
+ public SelectorImpl(Selector selector) {
+ this.type = selector.getSelectorType();
+ }
+ public short getSelectorType() {
+ return this.type;
+ }
+ private short type;
+ }
+
+ private static class CharacterDataSelectorImpl extends SelectorImpl
+ implements CharacterDataSelector {
+ public CharacterDataSelectorImpl(CharacterDataSelector selector) {
+ super(selector);
+ this.data = selector.getData();
+ }
+ public String getData() {
+ return this.data;
+ }
+ private String data;
+ }
+
+ private static class ConditionalSelectorImpl extends SelectorImpl
+ implements ConditionalSelector {
+ public ConditionalSelectorImpl(ConditionalSelector selector, SacFactory factory) {
+ super(selector);
+ this.condition = factory.cloneCondition(selector.getCondition());
+ this.simpleSelector =
+ (SimpleSelector) factory.cloneSelector(selector.getSimpleSelector());
+ }
+ public SimpleSelector getSimpleSelector() {
+ return this.simpleSelector;
+ }
+ public Condition getCondition() {
+ return this.condition;
+ }
+ private Condition condition;
+ private SimpleSelector simpleSelector;
+ }
+
+ private static class DescendantSelectorImpl extends SelectorImpl
+ implements DescendantSelector {
+ public DescendantSelectorImpl(DescendantSelector selector, SacFactory factory) {
+ super(selector);
+ this.ancestor = factory.cloneSelector(selector.getAncestorSelector());
+ this.simpleSelector =
+ (SimpleSelector) factory.cloneSelector(selector.getSimpleSelector());
+ }
+ public Selector getAncestorSelector() {
+ return this.ancestor;
+ }
+ public SimpleSelector getSimpleSelector() {
+ return this.simpleSelector;
+ }
+ private Selector ancestor;
+ private SimpleSelector simpleSelector;
+ }
+
+ private static class ElementSelectorImpl extends SelectorImpl
+ implements ElementSelector {
+ public ElementSelectorImpl(ElementSelector selector) {
+ super(selector);
+ this.namespaceURI = selector.getNamespaceURI();
+ this.localName = selector.getLocalName();
+ }
+ public String getNamespaceURI() {
+ return this.namespaceURI;
+ }
+ public String getLocalName() {
+ return this.localName;
+ }
+ private String namespaceURI;
+ private String localName;
+ }
+
+ private static class NegativeSelectorImpl extends SelectorImpl
+ implements NegativeSelector {
+ public NegativeSelectorImpl(NegativeSelector selector, SacFactory factory) {
+ super(selector);
+ this.simpleSelector = (SimpleSelector) factory.cloneSelector(selector.getSimpleSelector());
+ }
+ public SimpleSelector getSimpleSelector() {
+ return this.simpleSelector;
+ }
+ private SimpleSelector simpleSelector;
+ }
+
+ private static class ProcessingInstructionSelectorImpl extends SelectorImpl
+ implements ProcessingInstructionSelector {
+ public ProcessingInstructionSelectorImpl(ProcessingInstructionSelector selector) {
+ super(selector);
+ this.target = selector.getTarget();
+ this.data = selector.getData();
+ }
+ public String getTarget() {
+ return this.target;
+ }
+ public String getData() {
+ return this.data;
+ }
+ private String target;
+ private String data;
+ }
+
+ private static class SiblingSelectorImpl extends SelectorImpl
+ implements SiblingSelector {
+ private SiblingSelectorImpl(SiblingSelector selector, SacFactory factory) {
+ super(selector);
+ this.nodeType = selector.getNodeType();
+ this.selector = factory.cloneSelector(selector.getSelector());
+ this.siblingSelector = (SimpleSelector) factory.cloneSelector(selector.getSiblingSelector());
+ }
+ public short getNodeType() {
+ return this.nodeType;
+ }
+ public Selector getSelector() {
+ return this.selector;
+ }
+ public SimpleSelector getSiblingSelector() {
+ return this.siblingSelector;
+ }
+ private short nodeType;
+ private Selector selector;
+ private SimpleSelector siblingSelector;
+ }
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/css/StyleSheet.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/css/StyleSheet.java
new file mode 100644
index 0000000..f33e939
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/css/StyleSheet.java
@@ -0,0 +1,351 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * John Krasnay - initial API and implementation
+ * Dave Holroyd - Implement font-weight:bolder
+ * Dave Holroyd - Implement text decoration
+ * John Austin - More complete CSS constants. Add the colour "orange".
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.css;
+
+import java.io.Serializable;
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.WeakHashMap;
+
+
+import org.eclipse.wst.xml.vex.core.internal.core.FontSpec;
+import org.eclipse.wst.xml.vex.core.internal.dom.Element;
+import org.w3c.css.sac.LexicalUnit;
+
+/**
+ * Represents a CSS style sheet.
+ */
+public class StyleSheet implements Serializable {
+
+ /**
+ * Standard CSS properties.
+ */
+ private static final IProperty[] CSS_PROPERTIES = new IProperty[] {
+ new DisplayProperty(),
+ new LineHeightProperty(),
+ new ListStyleTypeProperty(),
+ new TextAlignProperty(),
+ new WhiteSpaceProperty(),
+
+ new FontFamilyProperty(),
+ new FontSizeProperty(),
+ new FontStyleProperty(),
+ new FontWeightProperty(),
+ new TextDecorationProperty(),
+
+ new ColorProperty(CSS.COLOR),
+ new ColorProperty(CSS.BACKGROUND_COLOR),
+
+ new LengthProperty(CSS.MARGIN_BOTTOM, IProperty.AXIS_VERTICAL),
+ new LengthProperty(CSS.MARGIN_LEFT, IProperty.AXIS_HORIZONTAL),
+ new LengthProperty(CSS.MARGIN_RIGHT, IProperty.AXIS_HORIZONTAL),
+ new LengthProperty(CSS.MARGIN_TOP, IProperty.AXIS_VERTICAL),
+
+ new LengthProperty(CSS.PADDING_BOTTOM, IProperty.AXIS_VERTICAL),
+ new LengthProperty(CSS.PADDING_LEFT, IProperty.AXIS_HORIZONTAL),
+ new LengthProperty(CSS.PADDING_RIGHT, IProperty.AXIS_HORIZONTAL),
+ new LengthProperty(CSS.PADDING_TOP, IProperty.AXIS_VERTICAL),
+
+ new ColorProperty(CSS.BORDER_BOTTOM_COLOR),
+ new ColorProperty(CSS.BORDER_LEFT_COLOR),
+ new ColorProperty(CSS.BORDER_RIGHT_COLOR),
+ new ColorProperty(CSS.BORDER_TOP_COLOR),
+ new BorderStyleProperty(CSS.BORDER_BOTTOM_STYLE),
+ new BorderStyleProperty(CSS.BORDER_LEFT_STYLE),
+ new BorderStyleProperty(CSS.BORDER_RIGHT_STYLE),
+ new BorderStyleProperty(CSS.BORDER_TOP_STYLE),
+ new BorderWidthProperty(CSS.BORDER_BOTTOM_WIDTH, CSS.BORDER_BOTTOM_STYLE, IProperty.AXIS_VERTICAL),
+ new BorderWidthProperty(CSS.BORDER_LEFT_WIDTH, CSS.BORDER_LEFT_STYLE, IProperty.AXIS_HORIZONTAL),
+ new BorderWidthProperty(CSS.BORDER_RIGHT_WIDTH, CSS.BORDER_RIGHT_STYLE, IProperty.AXIS_HORIZONTAL),
+ new BorderWidthProperty(CSS.BORDER_TOP_WIDTH, CSS.BORDER_TOP_STYLE, IProperty.AXIS_VERTICAL),
+ new BorderSpacingProperty(),
+ };
+
+ /**
+ * The properties to calculate. This can be changed by the app.
+ */
+ private static IProperty[] properties = CSS_PROPERTIES;
+
+ /**
+ * Style sheet is the default for the renderer.
+ */
+ public static final byte SOURCE_DEFAULT = 0;
+
+ /**
+ * Style sheet was provided by the document author.
+ */
+ public static final byte SOURCE_AUTHOR = 1;
+
+ /**
+ * Style sheet was provided by the user.
+ */
+ public static final byte SOURCE_USER = 2;
+
+ /**
+ * The rules that comprise the stylesheet.
+ */
+ private Rule[] rules;
+
+ /**
+ * Computing styles can be expensive, e.g. we have to calculate the styles
+ * of all parents of an element. We therefore cache styles in a map of
+ * element => WeakReference(styles). A weak hash map is used to avoid
+ * leaking memory as elements are deleted. By using weak references to
+ * the values, we also ensure the cache is memory-sensitive.
+ *
+ * This must be transient to prevent it from being serialized, as
+ * WeakHashMaps are not serializable.
+ */
+ private transient Map styleMap = null;
+
+ /**
+ * Class constructor.
+ *
+ * @param rules Rules that constitute the style sheet.
+ */
+ public StyleSheet(Rule[] rules) {
+ this.rules = rules;
+ }
+
+ /**
+ * Flush any cached styles for the given element.
+ * @param element Element for which styles are to be flushed.
+ */
+ public void flushStyles(Element element) {
+ this.getStyleMap().remove(element);
+ }
+
+ /**
+ * Returns a pseudo-element representing content to be displayed
+ * after the given element, or null if there is no such content.
+ * @param element Parent element of the pseudo-element.
+ */
+ public Element getAfterElement(Element element) {
+ PseudoElement pe = new PseudoElement(element, PseudoElement.AFTER);
+ Styles styles = this.getStyles(pe);
+ if (styles == null) {
+ return null;
+ } else {
+ return pe;
+ }
+ }
+
+ /**
+ * Returns a pseudo-element representing content to be displayed
+ * before the given element, or null if there is no such content.
+ * @param element Parent element of the pseudo-element.
+ */
+ public Element getBeforeElement(Element element) {
+ PseudoElement pe = new PseudoElement(element, PseudoElement.BEFORE);
+ Styles styles = this.getStyles(pe);
+ if (styles == null) {
+ return null;
+ } else {
+ return pe;
+ }
+ }
+
+ /**
+ * Returns the array of standard CSS properties.
+ */
+ public static IProperty[] getCssProperties() {
+ return CSS_PROPERTIES;
+ }
+
+ /**
+ * Returns the styles for the given element. The styles are cached to
+ * ensure reasonable performance.
+ *
+ * @param element Element for which to calculate the styles.
+ */
+ public Styles getStyles(Element element) {
+
+ Styles styles;
+ WeakReference ref = (WeakReference) this.getStyleMap().get(element);
+
+ if (ref != null) {
+ // can't combine these tests, since calling ref.get() twice
+ // (once to query for null, once to get the value) would
+ // cause a race condition should the GC happen btn the two.
+ styles = (Styles) ref.get();
+ if (styles != null) {
+ return styles;
+ }
+ } else if (this.getStyleMap().containsKey(element)) {
+ // this must be a pseudo-element with no content
+ return null;
+ }
+
+ styles = calculateStyles(element);
+
+ if (styles == null) {
+ // Yes, they can be null if element is a PseudoElement with no content property
+ this.getStyleMap().put(element, null);
+ } else {
+ this.getStyleMap().put(element, new WeakReference(styles));
+ }
+
+ return styles;
+ }
+
+
+
+ private Styles calculateStyles(Element element) {
+
+ Styles styles = new Styles();
+ Styles parentStyles = null;
+ if (element.getParent() != null) {
+ parentStyles = this.getStyles(element.getParent());
+ }
+
+ Map decls = this.getApplicableDecls(element);
+
+ LexicalUnit lu;
+
+ // If we're finding a pseudo-element, look at the 'content' property
+ // first, since most of the time it'll be empty and we'll return null.
+ if (element instanceof PseudoElement) {
+ lu = (LexicalUnit) decls.get(CSS.CONTENT);
+ if (lu == null) {
+ return null;
+ }
+
+ List content = new ArrayList();
+ while (lu != null) {
+ if (lu.getLexicalUnitType() == LexicalUnit.SAC_STRING_VALUE) {
+ content.add(lu.getStringValue());
+ }
+ lu = lu.getNextLexicalUnit();
+ }
+ styles.setContent(content);
+ }
+
+ for (int i = 0; i < properties.length; i++) {
+ IProperty property = properties[i];
+ lu = (LexicalUnit) decls.get(property.getName());
+ Object value = property.calculate(lu, parentStyles, styles);
+ styles.put(property.getName(), value);
+ }
+
+ // Now, map font-family, font-style, font-weight, and font-size onto
+ // an AWT font.
+
+ int styleFlags = FontSpec.PLAIN;
+ String fontStyle = styles.getFontStyle();
+ if (fontStyle.equals(CSS.ITALIC) || fontStyle.equals(CSS.OBLIQUE)) {
+ styleFlags |= FontSpec.ITALIC;
+ }
+ if (styles.getFontWeight() > 550) {
+ // 550 is halfway btn normal (400) and bold (700)
+ styleFlags |= FontSpec.BOLD;
+ }
+ String textDecoration = styles.getTextDecoration();
+ if (textDecoration.equals(CSS.UNDERLINE)) {
+ styleFlags |= FontSpec.UNDERLINE;
+ } else if (textDecoration.equals(CSS.OVERLINE)) {
+ styleFlags |= FontSpec.OVERLINE;
+ } else if (textDecoration.equals(CSS.LINE_THROUGH)) {
+ styleFlags |= FontSpec.LINE_THROUGH;
+ }
+
+ styles.setFont(new FontSpec(styles.getFontFamilies(),
+ styleFlags,
+ Math.round(styles.getFontSize())));
+
+ return styles;
+ }
+
+ /**
+ * Returns the list of properties to be parsed by StyleSheets in this app.
+ */
+ public static IProperty[] getProperties() {
+ return StyleSheet.properties;
+ }
+
+ /**
+ * Returns the rules comprising this stylesheet.
+ */
+ public Rule[] getRules() {
+ return this.rules;
+ }
+
+
+ /**
+ * Sets the list of properties to be used by StyleSheets in this application.
+ * @param properties New array of IProperty objects to be used.
+ */
+ public static void setProperties(IProperty[] properties) {
+ StyleSheet.properties = properties;
+ }
+
+ //========================================================= PRIVATE
+
+
+ /**
+ * Returns all the declarations that apply to the given element.
+ */
+ private Map getApplicableDecls(Element element) {
+ // Find all the property declarations that apply to this element.
+ List declList = new ArrayList();
+ Rule[] rules = this.getRules();
+
+ for (int i = 0; i < rules.length; i++) {
+ Rule rule = rules[i];
+ if (rule.matches(element)) {
+ PropertyDecl[] ruleDecls = rule.getPropertyDecls();
+ for (int j = 0; j < ruleDecls.length; j++) {
+ declList.add(ruleDecls[j]);
+ }
+ }
+ }
+
+ // Sort in cascade order. We can then just stuff them into a
+ // map and get the right values since higher-priority values
+ // come later and overwrite lower-priority ones.
+ Collections.sort(declList);
+
+ Map decls = new HashMap();
+ Iterator iter = declList.iterator();
+ while (iter.hasNext()) {
+ PropertyDecl decl = (PropertyDecl) iter.next();
+ PropertyDecl prevDecl = (PropertyDecl) decls.get(decl.getProperty());
+ if (prevDecl == null || !prevDecl.isImportant() || decl.isImportant()) {
+ decls.put(decl.getProperty(), decl);
+ }
+ }
+
+ Map values = new HashMap();
+ for (Iterator it = decls.keySet().iterator(); it.hasNext();) {
+ PropertyDecl decl = (PropertyDecl) decls.get(it.next());
+ values.put(decl.getProperty(), decl.getValue());
+ }
+
+ return values;
+ }
+
+
+ private Map getStyleMap() {
+ if (this.styleMap == null) {
+ this.styleMap = new WeakHashMap();
+ }
+ return this.styleMap;
+ }
+}
+
+
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/css/StyleSheetReader.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/css/StyleSheetReader.java
new file mode 100644
index 0000000..b32d4c8
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/css/StyleSheetReader.java
@@ -0,0 +1,545 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.css;
+
+import java.io.CharArrayReader;
+import java.io.IOException;
+import java.io.Reader;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+import org.w3c.css.sac.CSSException;
+import org.w3c.css.sac.DocumentHandler;
+import org.w3c.css.sac.InputSource;
+import org.w3c.css.sac.LexicalUnit;
+import org.w3c.css.sac.Parser;
+import org.w3c.css.sac.SACMediaList;
+import org.w3c.css.sac.Selector;
+import org.w3c.css.sac.SelectorList;
+
+/**
+ * Driver for the creation of StyleSheet objects.
+ */
+public class StyleSheetReader {
+
+ /**
+ * Class constructor.
+ */
+ public StyleSheetReader() {
+
+ }
+
+ protected static Parser createParser() {
+ //return new org.apache.batik.css.parser.Parser();
+ return new org.w3c.flute.parser.Parser();
+ }
+
+ /**
+ * Creates a new StyleSheet object from a URL.
+ * @param url URL from which to read the style sheet.
+ */
+ public StyleSheet read(URL url) throws IOException {
+ return this.read(new InputSource(url.toString()), url);
+ }
+
+ /**
+ * Creates a style sheet from a string. This is mainly used for small
+ * style sheets within unit tests.
+ * @param s String containing the style sheet.
+ */
+ public StyleSheet read(String s) throws CSSException, IOException {
+ Reader reader = new CharArrayReader(s.toCharArray());
+ return this.read(new InputSource(reader), null);
+ }
+
+ /**
+ * Creates a new Stylesheet from an input source.
+ * @param inputSource InputSource from which to read the stylesheet.
+ * @param url URL representing the input source, used to resolve @import
+ * rules with relative URIs. May be null, in which case @import rules
+ * are ignored.
+ */
+ public StyleSheet read(InputSource inputSource, URL url)
+ throws CSSException, IOException {
+
+ Parser parser = createParser();
+ List rules = new ArrayList();
+ StyleSheetBuilder ssBuilder = new StyleSheetBuilder(this.source, rules, url);
+ parser.setDocumentHandler(ssBuilder);
+ parser.parseStyleSheet(inputSource);
+ Rule[] ruleArray = (Rule[]) rules.toArray(new Rule[rules.size()]);
+ return new StyleSheet(ruleArray);
+ }
+
+ /**
+ * Returns the source of the stylesheet.
+ */
+ public byte getSource() {
+ return source;
+ }
+
+ /**
+ * Sets the source of the stylesheet. Must be one of StyleSheet.SOURCE_USER,
+ * StyleSheet.SOURCE_AUTHOR, or StyleSheet.SOURCE_DEFAULT.
+ * @param source The source to set.
+ */
+ public void setSource(byte source) {
+ this.source = source;
+ }
+
+ //======================================================== PRIVATE
+
+ private byte source = StyleSheet.SOURCE_DEFAULT;
+
+ private static class StyleSheetBuilder implements DocumentHandler {
+
+ private byte source;
+
+ // The rules that will be added to the stylesheet
+ private List rules;
+
+ // The rules to which decls are currently being added
+ private List currentRules;
+
+ // URL from which @import rules relative URIs are resolved.
+ // May be null!
+ private URL url;
+
+ // Factory for creating serializable clones of SAC objects
+ SacFactory factory = new SacFactory();
+
+ public StyleSheetBuilder(byte source, List rules, URL url) {
+ this.source = source;
+ this.rules = rules;
+ this.url = url;
+ }
+
+ //-------------------------------------------- DocumentHandler methods
+
+ public void comment(java.lang.String text) {
+ }
+
+ public void endDocument(InputSource source) {
+ }
+
+ public void endFontFace() {
+ }
+
+ public void endMedia(SACMediaList media) {
+ }
+
+ public void endPage(String name, String pseudo_page) {
+ }
+
+ public void endSelector(SelectorList selectors) {
+ this.rules.addAll(this.currentRules);
+ this.currentRules = null;
+ }
+
+ public void ignorableAtRule(String atRule) {
+ }
+
+ public void importStyle(
+ String uri,
+ SACMediaList media,
+ String defaultNamespaceURI) {
+
+ if (this.url == null) {
+ return;
+ }
+
+ try {
+ Parser parser = createParser();
+ URL importUrl = new URL(this.url, uri);
+ StyleSheetBuilder ssBuilder = new StyleSheetBuilder(this.source, rules, importUrl);
+ parser.setDocumentHandler(ssBuilder);
+ parser.parseStyleSheet(new InputSource(importUrl.toString()));
+ } catch (CSSException e) {
+ } catch (IOException e) {
+ }
+
+ }
+
+ public void namespaceDeclaration(String prefix, String uri) {
+ }
+
+ public void property(
+ String name,
+ LexicalUnit value,
+ boolean important) {
+
+ // Create a serializable clone of the value for storage in our
+ // stylesheet.
+ LexicalUnit val = factory.cloneLexicalUnit(value);
+
+ if (name.equals(CSS.BORDER)) {
+ this.expandBorder(val, important);
+ } else if (name.equals(CSS.BORDER_BOTTOM)) {
+ this.expandBorder(val, CSS.BORDER_BOTTOM, important);
+ } else if (name.equals(CSS.BORDER_LEFT)) {
+ this.expandBorder(val, CSS.BORDER_LEFT, important);
+ } else if (name.equals(CSS.BORDER_RIGHT)) {
+ this.expandBorder(val, CSS.BORDER_RIGHT, important);
+ } else if (name.equals(CSS.BORDER_TOP)) {
+ this.expandBorder(val, CSS.BORDER_TOP, important);
+ } else if (name.equals(CSS.BORDER_COLOR)) {
+ this.expandBorderColor(val, important);
+ } else if (name.equals(CSS.BORDER_STYLE)) {
+ this.expandBorderStyle(val, important);
+ } else if (name.equals(CSS.BORDER_WIDTH)) {
+ this.expandBorderWidth(val, important);
+ } else if (name.equals(CSS.FONT)) {
+ this.expandFont(val, important);
+ } else if (name.equals(CSS.MARGIN)) {
+ this.expandMargin(val, important);
+ } else if (name.equals(CSS.PADDING)) {
+ this.expandPadding(val, important);
+ } else {
+ this.addDecl(name, val, important);
+ }
+ }
+
+ public void startDocument(InputSource source) {
+ }
+
+ public void startFontFace() {
+ }
+
+ public void startMedia(SACMediaList media) {
+ }
+
+ public void startPage(String name, String pseudo_page) {
+ }
+
+ public void startSelector(SelectorList selectors) {
+ this.currentRules = new ArrayList();
+ for (int i = 0; i < selectors.getLength(); i++) {
+ Selector selector = factory.cloneSelector(selectors.item(i));
+ Rule rule = new Rule(this.source, selector);
+ this.currentRules.add(rule);
+ }
+ }
+
+ //----------------------------------------- DocumentHandler methods end
+
+ //======================================================= PRIVATE
+
+ /**
+ * Adds a PropertyDecl to the current set of rules.
+ */
+ private void addDecl(String name, LexicalUnit value, boolean important) {
+ Iterator iter = this.currentRules.iterator();
+ while (iter.hasNext()) {
+ Rule rule = (Rule) iter.next();
+ rule.add(new PropertyDecl(rule, name, value, important));
+ }
+ }
+
+ /**
+ * Expand the "border" shorthand property.
+ */
+ private void expandBorder(LexicalUnit value, boolean important) {
+ this.expandBorder(value, CSS.BORDER_BOTTOM, important);
+ this.expandBorder(value, CSS.BORDER_LEFT, important);
+ this.expandBorder(value, CSS.BORDER_RIGHT, important);
+ this.expandBorder(value, CSS.BORDER_TOP, important);
+ }
+
+ /**
+ * Expand one of the the "border-xxx" shorthand
+ * properties. whichBorder must be one of CSS.BORDER_BOTTOM,
+ * CSS.BORDER_LEFT, CSS.BORDER_RIGHT, CSS.BORDER_TOP.
+ */
+ private void expandBorder(
+ LexicalUnit value,
+ String whichBorder,
+ boolean important) {
+
+ if (AbstractProperty.isInherit(value)) {
+ this.addDecl(whichBorder + CSS.COLOR_SUFFIX, value, important);
+ this.addDecl(whichBorder + CSS.STYLE_SUFFIX, value, important);
+ this.addDecl(whichBorder + CSS.WIDTH_SUFFIX, value, important);
+ return;
+ }
+
+ LexicalUnit[] lus = getLexicalUnitList(value);
+ int i = 0;
+ if (BorderWidthProperty.isBorderWidth(lus[i])) {
+ this.addDecl(whichBorder + CSS.WIDTH_SUFFIX, lus[i], important);
+ i++;
+ }
+
+ if (i < lus.length && BorderStyleProperty.isBorderStyle(lus[i])) {
+ this.addDecl(whichBorder + CSS.STYLE_SUFFIX, lus[i], important);
+ i++;
+ }
+
+ if (i < lus.length && ColorProperty.isColor(lus[i])) {
+ this.addDecl(whichBorder + CSS.COLOR_SUFFIX, lus[i], important);
+ i++;
+ }
+
+ }
+
+ /**
+ * Expand the "border-color" shorthand property.
+ */
+ private void expandBorderColor(LexicalUnit value, boolean important) {
+
+ if (AbstractProperty.isInherit(value)) {
+ this.addDecl(CSS.BORDER_TOP_COLOR, value, important);
+ this.addDecl(CSS.BORDER_LEFT_COLOR, value, important);
+ this.addDecl(CSS.BORDER_RIGHT_COLOR, value, important);
+ this.addDecl(CSS.BORDER_BOTTOM_COLOR, value, important);
+ return;
+ }
+
+ LexicalUnit[] lus = getLexicalUnitList(value);
+ if (lus.length >= 4) {
+ this.addDecl(CSS.BORDER_TOP_COLOR, lus[0], important);
+ this.addDecl(CSS.BORDER_RIGHT_COLOR, lus[1], important);
+ this.addDecl(CSS.BORDER_BOTTOM_COLOR, lus[2], important);
+ this.addDecl(CSS.BORDER_LEFT_COLOR, lus[3], important);
+ } else if (lus.length == 3) {
+ this.addDecl(CSS.BORDER_TOP_COLOR, lus[0], important);
+ this.addDecl(CSS.BORDER_LEFT_COLOR, lus[1], important);
+ this.addDecl(CSS.BORDER_RIGHT_COLOR, lus[1], important);
+ this.addDecl(CSS.BORDER_BOTTOM_COLOR, lus[2], important);
+ } else if (lus.length == 2) {
+ this.addDecl(CSS.BORDER_TOP_COLOR, lus[0], important);
+ this.addDecl(CSS.BORDER_LEFT_COLOR, lus[1], important);
+ this.addDecl(CSS.BORDER_RIGHT_COLOR, lus[1], important);
+ this.addDecl(CSS.BORDER_BOTTOM_COLOR, lus[0], important);
+ } else if (lus.length == 1) {
+ this.addDecl(CSS.BORDER_TOP_COLOR, lus[0], important);
+ this.addDecl(CSS.BORDER_LEFT_COLOR, lus[0], important);
+ this.addDecl(CSS.BORDER_RIGHT_COLOR, lus[0], important);
+ this.addDecl(CSS.BORDER_BOTTOM_COLOR, lus[0], important);
+ }
+ }
+
+ /**
+ * Expand the "border-style" shorthand property.
+ */
+ private void expandBorderStyle(LexicalUnit value, boolean important) {
+
+ if (AbstractProperty.isInherit(value)) {
+ this.addDecl(CSS.BORDER_TOP_STYLE, value, important);
+ this.addDecl(CSS.BORDER_LEFT_STYLE, value, important);
+ this.addDecl(CSS.BORDER_RIGHT_STYLE, value, important);
+ this.addDecl(CSS.BORDER_BOTTOM_STYLE, value, important);
+ return;
+ }
+
+ LexicalUnit[] lus = getLexicalUnitList(value);
+ if (lus.length >= 4) {
+ this.addDecl(CSS.BORDER_TOP_STYLE, lus[0], important);
+ this.addDecl(CSS.BORDER_RIGHT_STYLE, lus[1], important);
+ this.addDecl(CSS.BORDER_BOTTOM_STYLE, lus[2], important);
+ this.addDecl(CSS.BORDER_LEFT_STYLE, lus[3], important);
+ } else if (lus.length == 3) {
+ this.addDecl(CSS.BORDER_TOP_STYLE, lus[0], important);
+ this.addDecl(CSS.BORDER_LEFT_STYLE, lus[1], important);
+ this.addDecl(CSS.BORDER_RIGHT_STYLE, lus[1], important);
+ this.addDecl(CSS.BORDER_BOTTOM_STYLE, lus[2], important);
+ } else if (lus.length == 2) {
+ this.addDecl(CSS.BORDER_TOP_STYLE, lus[0], important);
+ this.addDecl(CSS.BORDER_LEFT_STYLE, lus[1], important);
+ this.addDecl(CSS.BORDER_RIGHT_STYLE, lus[1], important);
+ this.addDecl(CSS.BORDER_BOTTOM_STYLE, lus[0], important);
+ } else if (lus.length == 1) {
+ this.addDecl(CSS.BORDER_TOP_STYLE, lus[0], important);
+ this.addDecl(CSS.BORDER_LEFT_STYLE, lus[0], important);
+ this.addDecl(CSS.BORDER_RIGHT_STYLE, lus[0], important);
+ this.addDecl(CSS.BORDER_BOTTOM_STYLE, lus[0], important);
+ }
+ }
+
+ /**
+ * Expand the "border-width" shorthand property.
+ */
+ private void expandBorderWidth(LexicalUnit value, boolean important) {
+
+ if (AbstractProperty.isInherit(value)) {
+ this.addDecl(CSS.BORDER_TOP_WIDTH, value, important);
+ this.addDecl(CSS.BORDER_LEFT_WIDTH, value, important);
+ this.addDecl(CSS.BORDER_RIGHT_WIDTH, value, important);
+ this.addDecl(CSS.BORDER_BOTTOM_WIDTH, value, important);
+ return;
+ }
+
+ LexicalUnit[] lus = getLexicalUnitList(value);
+ if (lus.length >= 4) {
+ this.addDecl(CSS.BORDER_TOP_WIDTH, lus[0], important);
+ this.addDecl(CSS.BORDER_RIGHT_WIDTH, lus[1], important);
+ this.addDecl(CSS.BORDER_BOTTOM_WIDTH, lus[2], important);
+ this.addDecl(CSS.BORDER_LEFT_WIDTH, lus[3], important);
+ } else if (lus.length == 3) {
+ this.addDecl(CSS.BORDER_TOP_WIDTH, lus[0], important);
+ this.addDecl(CSS.BORDER_LEFT_WIDTH, lus[1], important);
+ this.addDecl(CSS.BORDER_RIGHT_WIDTH, lus[1], important);
+ this.addDecl(CSS.BORDER_BOTTOM_WIDTH, lus[2], important);
+ } else if (lus.length == 2) {
+ this.addDecl(CSS.BORDER_TOP_WIDTH, lus[0], important);
+ this.addDecl(CSS.BORDER_LEFT_WIDTH, lus[1], important);
+ this.addDecl(CSS.BORDER_RIGHT_WIDTH, lus[1], important);
+ this.addDecl(CSS.BORDER_BOTTOM_WIDTH, lus[0], important);
+ } else if (lus.length == 1) {
+ this.addDecl(CSS.BORDER_TOP_WIDTH, lus[0], important);
+ this.addDecl(CSS.BORDER_LEFT_WIDTH, lus[0], important);
+ this.addDecl(CSS.BORDER_RIGHT_WIDTH, lus[0], important);
+ this.addDecl(CSS.BORDER_BOTTOM_WIDTH, lus[0], important);
+ }
+ }
+
+ /**
+ * Expand the "font" shorthand property.
+ */
+ private void expandFont(LexicalUnit value, boolean important) {
+
+ if (AbstractProperty.isInherit(value)) {
+ this.addDecl(CSS.FONT_STYLE, value, important);
+ this.addDecl(CSS.FONT_VARIANT, value, important);
+ this.addDecl(CSS.FONT_WEIGHT, value, important);
+ this.addDecl(CSS.FONT_SIZE, value, important);
+ this.addDecl(CSS.FONT_FAMILY, value, important);
+ return;
+ }
+
+ LexicalUnit[] lus = getLexicalUnitList(value);
+ int n = lus.length;
+ int i = 0;
+ if (i < n && FontStyleProperty.isFontStyle(lus[i])) {
+ this.addDecl(CSS.FONT_STYLE, lus[i], important);
+ i++;
+ }
+
+ if (i < n && FontVariantProperty.isFontVariant(lus[i])) {
+ this.addDecl(CSS.FONT_VARIANT, lus[i], important);
+ i++;
+ }
+
+ if (i < n && FontWeightProperty.isFontWeight(lus[i])) {
+ this.addDecl(CSS.FONT_WEIGHT, lus[i], important);
+ i++;
+ }
+
+ if (i < n && FontSizeProperty.isFontSize(lus[i])) {
+ this.addDecl(CSS.FONT_SIZE, lus[i], important);
+ i++;
+ }
+
+ if (i < n
+ && lus[i].getLexicalUnitType()
+ == LexicalUnit.SAC_OPERATOR_SLASH) {
+ i++; // gobble slash
+ if (i < n) {
+ this.addDecl(CSS.LINE_HEIGHT, lus[i], important);
+ }
+ i++;
+ }
+
+ if (i < n) {
+ this.addDecl(CSS.FONT_FAMILY, lus[i], important);
+ }
+ }
+
+ /**
+ * Expand the "margin" shorthand property.
+ */
+ private void expandMargin(LexicalUnit value, boolean important) {
+
+ if (AbstractProperty.isInherit(value)) {
+ this.addDecl(CSS.MARGIN_TOP, value, important);
+ this.addDecl(CSS.MARGIN_RIGHT, value, important);
+ this.addDecl(CSS.MARGIN_BOTTOM, value, important);
+ this.addDecl(CSS.MARGIN_LEFT, value, important);
+ return;
+ }
+
+ LexicalUnit[] lus = getLexicalUnitList(value);
+ if (lus.length >= 4) {
+ this.addDecl(CSS.MARGIN_TOP, lus[0], important);
+ this.addDecl(CSS.MARGIN_RIGHT, lus[1], important);
+ this.addDecl(CSS.MARGIN_BOTTOM, lus[2], important);
+ this.addDecl(CSS.MARGIN_LEFT, lus[3], important);
+ } else if (lus.length == 3) {
+ this.addDecl(CSS.MARGIN_TOP, lus[0], important);
+ this.addDecl(CSS.MARGIN_LEFT, lus[1], important);
+ this.addDecl(CSS.MARGIN_RIGHT, lus[1], important);
+ this.addDecl(CSS.MARGIN_BOTTOM, lus[2], important);
+ } else if (lus.length == 2) {
+ this.addDecl(CSS.MARGIN_TOP, lus[0], important);
+ this.addDecl(CSS.MARGIN_LEFT, lus[1], important);
+ this.addDecl(CSS.MARGIN_RIGHT, lus[1], important);
+ this.addDecl(CSS.MARGIN_BOTTOM, lus[0], important);
+ } else if (lus.length == 1) {
+ this.addDecl(CSS.MARGIN_TOP, lus[0], important);
+ this.addDecl(CSS.MARGIN_LEFT, lus[0], important);
+ this.addDecl(CSS.MARGIN_RIGHT, lus[0], important);
+ this.addDecl(CSS.MARGIN_BOTTOM, lus[0], important);
+ }
+ }
+
+ /**
+ * Expand the "padding" shorthand property.
+ */
+ private void expandPadding(LexicalUnit value, boolean important) {
+
+ if (AbstractProperty.isInherit(value)) {
+ this.addDecl(CSS.PADDING_TOP, value, important);
+ this.addDecl(CSS.PADDING_LEFT, value, important);
+ this.addDecl(CSS.PADDING_RIGHT, value, important);
+ this.addDecl(CSS.PADDING_BOTTOM, value, important);
+ return;
+ }
+
+ LexicalUnit[] lus = getLexicalUnitList(value);
+ if (lus.length >= 4) {
+ this.addDecl(CSS.PADDING_TOP, lus[0], important);
+ this.addDecl(CSS.PADDING_RIGHT, lus[1], important);
+ this.addDecl(CSS.PADDING_BOTTOM, lus[2], important);
+ this.addDecl(CSS.PADDING_LEFT, lus[3], important);
+ } else if (lus.length == 3) {
+ this.addDecl(CSS.PADDING_TOP, lus[0], important);
+ this.addDecl(CSS.PADDING_LEFT, lus[1], important);
+ this.addDecl(CSS.PADDING_RIGHT, lus[1], important);
+ this.addDecl(CSS.PADDING_BOTTOM, lus[2], important);
+ } else if (lus.length == 2) {
+ this.addDecl(CSS.PADDING_TOP, lus[0], important);
+ this.addDecl(CSS.PADDING_LEFT, lus[1], important);
+ this.addDecl(CSS.PADDING_RIGHT, lus[1], important);
+ this.addDecl(CSS.PADDING_BOTTOM, lus[0], important);
+ } else if (lus.length == 1) {
+ this.addDecl(CSS.PADDING_TOP, lus[0], important);
+ this.addDecl(CSS.PADDING_LEFT, lus[0], important);
+ this.addDecl(CSS.PADDING_RIGHT, lus[0], important);
+ this.addDecl(CSS.PADDING_BOTTOM, lus[0], important);
+ }
+ }
+
+ /**
+ * Returns an array of <code>LexicalUnit</code> objects, the first
+ * of which is given.
+ */
+ private static LexicalUnit[] getLexicalUnitList(LexicalUnit lu) {
+ List lus = new ArrayList();
+ while (lu != null) {
+ lus.add(lu);
+ lu = lu.getNextLexicalUnit();
+ }
+ return (LexicalUnit[]) lus.toArray(new LexicalUnit[lus.size()]);
+ }
+
+ }
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/css/Styles.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/css/Styles.java
new file mode 100644
index 0000000..0a2a7d5
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/css/Styles.java
@@ -0,0 +1,335 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * John Krasnay - initial API and implementation
+ * Dave Holroyd - Implement text decoration
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.css;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.eclipse.wst.xml.vex.core.internal.core.Color;
+import org.eclipse.wst.xml.vex.core.internal.core.FontSpec;
+
+
+
+/**
+ * Represents the computed style properties for a particular element.
+ */
+public class Styles {
+
+ /** Maps property name (String) => value (Object) */
+ private Map values = new HashMap();
+
+ private List content;
+ private FontSpec font;
+
+ /**
+ * Returns the value of the given property, or null if the property
+ * does not have a value.
+ * @param propertyName
+ * @return
+ */
+ public Object get(String propertyName) {
+ return this.values.get(propertyName);
+ }
+
+ /**
+ * Returns the value of the <code>backgroundColor</code> property.
+ */
+ public Color getBackgroundColor() {
+ return (Color) this.values.get(CSS.BACKGROUND_COLOR);
+ }
+
+ /**
+ * Returns the value of the <code>borderBottomColor</code> property.
+ */
+ public Color getBorderBottomColor() {
+ return (Color) this.values.get(CSS.BORDER_BOTTOM_COLOR);
+ }
+
+ /**
+ * Returns the value of the <code>borderBottomStyle</code> property.
+ */
+ public String getBorderBottomStyle() {
+ return (String) this.values.get(CSS.BORDER_BOTTOM_STYLE);
+ }
+
+ /**
+ * Returns the value of the <code>borderLeftColor</code> property.
+ */
+ public Color getBorderLeftColor() {
+ return (Color) this.values.get(CSS.BORDER_LEFT_COLOR);
+ }
+
+ /**
+ * Returns the value of the <code>borderLeftStyle</code> property.
+ */
+ public String getBorderLeftStyle() {
+ return (String) this.values.get(CSS.BORDER_LEFT_STYLE);
+ }
+
+ /**
+ * Returns the value of the <code>borderRightColor</code> property.
+ */
+ public Color getBorderRightColor() {
+ return (Color) this.values.get(CSS.BORDER_RIGHT_COLOR);
+ }
+
+ /**
+ * Returns the value of the <code>borderRightStyle</code> property.
+ */
+ public String getBorderRightStyle() {
+ return (String) this.values.get(CSS.BORDER_RIGHT_STYLE);
+ }
+
+ /**
+ * Returns the value of the <code>borderSpacing</code> property.
+ */
+ public BorderSpacingProperty.Value getBorderSpacing() {
+ return (BorderSpacingProperty.Value) this.values.get(CSS.BORDER_SPACING);
+ }
+
+ /**
+ * Returns the value of the <code>borderTopColor</code> property.
+ */
+ public Color getBorderTopColor() {
+ return (Color) this.values.get(CSS.BORDER_TOP_COLOR);
+ }
+
+ /**
+ * Returns the value of the <code>borderTopStyle</code> property.
+ */
+ public String getBorderTopStyle() {
+ return (String) this.values.get(CSS.BORDER_TOP_STYLE);
+ }
+
+ /**
+ * Returns the value of the <code>color</code> property.
+ */
+ public Color getColor() {
+ return (Color) this.values.get(CSS.COLOR);
+ }
+
+ /**
+ * Returns a <code>List</code> of <code>ContentPart</code> objects
+ * representing the <code>content</code> property.
+ */
+ public List getContent() {
+ return content;
+ }
+
+ /**
+ * Returns the value of the <code>display</code> property.
+ */
+ public String getDisplay() {
+ return (String) this.values.get(CSS.DISPLAY);
+ }
+
+ /**
+ * Returns the value of the <code>font</code> property.
+ */
+ public FontSpec getFont() {
+ return font;
+ }
+
+ /**
+ * Returns the value of the <code>fontFamily</code> property.
+ */
+ public String[] getFontFamilies() {
+ return (String[]) this.values.get(CSS.FONT_FAMILY);
+ }
+
+ /**
+ * Returns the value of the <code>fontSize</code> property.
+ */
+ public float getFontSize() {
+ return ((Float) this.values.get(CSS.FONT_SIZE)).floatValue();
+ }
+
+ /**
+ * Returns the value of the <code>fontStyle</code> property.
+ */
+ public String getFontStyle() {
+ return (String) this.values.get(CSS.FONT_STYLE);
+ }
+
+ /**
+ * Returns the value of the <code>fontWeight</code> property.
+ */
+ public int getFontWeight() {
+ return ((Integer) this.values.get(CSS.FONT_WEIGHT)).intValue();
+ }
+
+ /**
+ * Returns the value of the <code>lineHeight</code> property.
+ */
+ public int getLineHeight() {
+ return ((RelativeLength) this.values.get(CSS.LINE_HEIGHT)).get(Math.round(this.getFontSize()));
+ }
+
+ /**
+ * Returns the value of the <code>listStyleType</code> property.
+ */
+ public String getListStyleType() {
+ return (String) this.values.get(CSS.LIST_STYLE_TYPE);
+ }
+
+ /**
+ * Returns the value of the <code>textAlign</code> property.
+ */
+ public String getTextAlign() {
+ return (String) this.values.get(CSS.TEXT_ALIGN);
+ }
+
+ /**
+ * Returns the value of the <code>textDecoration</code> property.
+ */
+ public String getTextDecoration() {
+ return (String) this.values.get(CSS.TEXT_DECORATION);
+ }
+
+ /**
+ * Returns the value of the <code>whiteSpace</code> property.
+ */
+ public String getWhiteSpace() {
+ return (String) this.values.get(CSS.WHITE_SPACE);
+ }
+
+ /**
+ * Returns true if this element is block-formatted, or false if it
+ * is inline-formatted.
+ */
+ public boolean isBlock() {
+ return this.getDisplay().equals(CSS.BLOCK)
+ || this.getDisplay().equals(CSS.LIST_ITEM)
+ || this.getDisplay().equals(CSS.TABLE)
+ || this.getDisplay().equals(CSS.TABLE_CAPTION)
+ || this.getDisplay().equals(CSS.TABLE_CELL)
+ || this.getDisplay().equals(CSS.TABLE_COLUMN)
+ || this.getDisplay().equals(CSS.TABLE_COLUMN_GROUP)
+ || this.getDisplay().equals(CSS.TABLE_FOOTER_GROUP)
+ || this.getDisplay().equals(CSS.TABLE_HEADER_GROUP)
+ || this.getDisplay().equals(CSS.TABLE_ROW)
+ || this.getDisplay().equals(CSS.TABLE_ROW_GROUP);
+ }
+
+ /**
+ * Sets the value of a property in this stylesheet.
+ * @param propertyName Name of the property being set.
+ * @param value Value of the property.
+ */
+ public void put(String propertyName, Object value) {
+ this.values.put(propertyName, value);
+ }
+
+ /**
+ * Sets the vale of the <code>content</code> property.
+ * @param content <code>List</code> of <code>ContentPart</code> objects
+ * representing the content.
+ */
+ public void setContent(List content) {
+ this.content = content;
+ }
+
+ /**
+ * Sets the value of the <code>font</code> property.
+ * @param font new value for the <code>font</code> property.
+ */
+ public void setFont(FontSpec font) {
+ this.font = font;
+ }
+
+ /**
+ * @return the value of border-bottom-width
+ */
+ public int getBorderBottomWidth() {
+ return ((Integer) this.values.get(CSS.BORDER_BOTTOM_WIDTH)).intValue();
+ }
+
+ /**
+ * @return the value of border-left-width
+ */
+ public int getBorderLeftWidth() {
+ return ((Integer) this.values.get(CSS.BORDER_LEFT_WIDTH)).intValue();
+ }
+
+ /**
+ * @return the value of border-right-width
+ */
+ public int getBorderRightWidth() {
+ return ((Integer) this.values.get(CSS.BORDER_RIGHT_WIDTH)).intValue();
+ }
+
+ /**
+ * @return the value of border-top-width
+ */
+ public int getBorderTopWidth() {
+ return ((Integer) this.values.get(CSS.BORDER_TOP_WIDTH)).intValue();
+ }
+
+ /**
+ * @return the value of margin-bottom
+ */
+ public RelativeLength getMarginBottom() {
+ return (RelativeLength) this.values.get(CSS.MARGIN_BOTTOM);
+ //return marginBottom;
+ }
+
+ /**
+ * @return the value of margin-left
+ */
+ public RelativeLength getMarginLeft() {
+ return (RelativeLength) this.values.get(CSS.MARGIN_LEFT);
+ }
+
+ /**
+ * @return the value of margin-right
+ */
+ public RelativeLength getMarginRight() {
+ return (RelativeLength) this.values.get(CSS.MARGIN_RIGHT);
+ }
+
+ /**
+ * @return the value of margin-top
+ */
+ public RelativeLength getMarginTop() {
+ return (RelativeLength) this.values.get(CSS.MARGIN_TOP);
+ }
+
+ /**
+ * @return the value of padding-bottom
+ */
+ public RelativeLength getPaddingBottom() {
+ return (RelativeLength) this.values.get(CSS.PADDING_BOTTOM);
+ }
+
+ /**
+ * @return the value of padding-left
+ */
+ public RelativeLength getPaddingLeft() {
+ return (RelativeLength) this.values.get(CSS.PADDING_LEFT);
+ }
+
+ /**
+ * @return the value of padding-right
+ */
+ public RelativeLength getPaddingRight() {
+ return (RelativeLength) this.values.get(CSS.PADDING_RIGHT);
+ }
+
+ /**
+ * @return the value of padding-top
+ */
+ public RelativeLength getPaddingTop() {
+ return (RelativeLength) this.values.get(CSS.PADDING_TOP);
+ }
+
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/css/TextAlignProperty.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/css/TextAlignProperty.java
new file mode 100644
index 0000000..c67d9c2
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/css/TextAlignProperty.java
@@ -0,0 +1,53 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.css;
+
+import org.w3c.css.sac.LexicalUnit;
+
+/**
+ * The CSS text-align property.
+ */
+public class TextAlignProperty extends AbstractProperty {
+
+ /**
+ * Class constructor
+ */
+ public TextAlignProperty() {
+ super(CSS.TEXT_ALIGN);
+ }
+
+ public Object calculate(LexicalUnit lu, Styles parentStyles, Styles styles) {
+ if (TextAlignProperty.isTextAlign(lu)) {
+ return lu.getStringValue();
+ } else {
+ // not specified, "inherit", or some other value
+ if (parentStyles != null) {
+ return parentStyles.getTextAlign();
+ } else {
+ return CSS.LEFT;
+ }
+ }
+
+ }
+
+ //=================================================== PRIVATE
+
+ private static boolean isTextAlign(LexicalUnit lu) {
+ if (lu == null) {
+ return false;
+ } else if (lu.getLexicalUnitType() == LexicalUnit.SAC_IDENT) {
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/css/TextDecorationProperty.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/css/TextDecorationProperty.java
new file mode 100644
index 0000000..ceb2f7d
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/css/TextDecorationProperty.java
@@ -0,0 +1,62 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.css;
+
+import org.w3c.css.sac.LexicalUnit;
+
+/**
+ * The CSS text-decoration property.
+ */
+public class TextDecorationProperty extends AbstractProperty {
+
+ /**
+ * Class constructor.
+ */
+ public TextDecorationProperty() {
+ super(CSS.TEXT_DECORATION);
+ }
+
+ public Object calculate(LexicalUnit lu, Styles parentStyles, Styles styles) {
+ if (isTextDecoration(lu)) {
+ return lu.getStringValue();
+ } else {
+ // not specified, "inherit", or some other value
+ if (parentStyles != null) {
+ return parentStyles.getTextDecoration();
+ } else {
+ return CSS.NONE;
+ }
+ }
+ }
+
+ //=================================================== PRIVATE
+
+ /**
+ * Returns true if the given lexical unit represents a text decoration.
+ *
+ * @param lu LexicalUnit to check.
+ */
+ private static boolean isTextDecoration(LexicalUnit lu) {
+ if (lu == null) {
+ return false;
+ } else if (lu.getLexicalUnitType() == LexicalUnit.SAC_IDENT) {
+ String s = lu.getStringValue();
+ return s.equals(CSS.NONE)
+ || s.equals(CSS.UNDERLINE)
+ || s.equals(CSS.OVERLINE)
+ || s.equals(CSS.LINE_THROUGH)
+ || s.equals(CSS.BLINK);
+ } else {
+ return false;
+ }
+ }
+
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/css/WhiteSpaceProperty.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/css/WhiteSpaceProperty.java
new file mode 100644
index 0000000..ef60a50
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/css/WhiteSpaceProperty.java
@@ -0,0 +1,63 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.css;
+
+import org.w3c.css.sac.LexicalUnit;
+
+/**
+ * The CSS white-space property.
+ */
+public class WhiteSpaceProperty extends AbstractProperty {
+
+ /**
+ * Class constructor.
+ */
+ public WhiteSpaceProperty() {
+ super(CSS.WHITE_SPACE);
+ }
+
+ /**
+ *
+ */
+
+ public Object calculate(LexicalUnit lu, Styles parentStyles, Styles styles) {
+ if (isWhiteSpace(lu)) {
+ return lu.getStringValue();
+ } else {
+ // not specified, "inherit", or some other value
+ if (parentStyles != null) {
+ return parentStyles.getWhiteSpace();
+ } else {
+ return CSS.NORMAL;
+ }
+ }
+
+ }
+
+ /**
+ * Returns true if the given lexical unit represents a white space value.
+ *
+ * @param lu LexicalUnit to check.
+ */
+ public static boolean isWhiteSpace(LexicalUnit lu) {
+ if (lu == null) {
+ return false;
+ } else if (lu.getLexicalUnitType() == LexicalUnit.SAC_IDENT) {
+ String s = lu.getStringValue();
+ return s.equals(CSS.NORMAL)
+ || s.equals(CSS.PRE)
+ || s.equals(CSS.NOWRAP);
+ } else {
+ return false;
+ }
+ }
+
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/css/package.html b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/css/package.html
new file mode 100644
index 0000000..44d5ee2
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/css/package.html
@@ -0,0 +1,15 @@
+<html>
+ <head>
+ <title>net.sf.vex.css</title>
+ </head>
+ <body>
+
+ <p>Classes implementing an object model for CSS stylesheets. Style
+ sheets can be built from an input stream using a
+ <code>StyleSheetBuilder</code> and a SAC (Simple API for CSS)
+ parser. The <code>StyleSheet</code> class encapsulates the
+ style sheet data and implements the the CSS algorithms for
+ calculating computed style values.</p>
+
+ </body>
+</html>
\ No newline at end of file
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/dom/AbstractValidator.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/dom/AbstractValidator.java
new file mode 100644
index 0000000..4138c4b
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/dom/AbstractValidator.java
@@ -0,0 +1,63 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.dom;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Partial implementation of the Validator interface.
+ */
+public abstract class AbstractValidator implements Validator {
+
+
+ /**
+ * @see Validator#isValidSequence
+ */
+ public boolean isValidSequence(
+ String element,
+ String[] seq1,
+ String[] seq2,
+ String[] seq3,
+ boolean partial) {
+
+ List list = new ArrayList();
+ for (int i = 0; i < seq1.length; i++) {
+ list.add(seq1[i]);
+ }
+ if (seq2 != null) {
+ for (int i = 0; i < seq2.length; i++) {
+ if (i == 0 && seq2[i].equals(Validator.PCDATA) && list.size() > 0
+ && list.get(list.size() - 1).equals(Validator.PCDATA)) {
+ // Avoid consecutive PCDATA's
+ continue;
+ }
+ list.add(seq2[i]);
+ }
+ }
+ if (seq3 != null) {
+ for (int i = 0; i < seq3.length; i++) {
+ if (i == 0 && seq3[i].equals(Validator.PCDATA) && list.size() > 0
+ && list.get(list.size() - 1).equals(Validator.PCDATA)) {
+ // Avoid consecutive PCDATA's
+ continue;
+ }
+ list.add(seq3[i]);
+ }
+ }
+
+ String[] nodes = (String[]) list.toArray(new String[list.size()]);
+ return this.isValidSequence(element, nodes, partial);
+ }
+
+
+}
+
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/dom/AttributeDefinition.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/dom/AttributeDefinition.java
new file mode 100644
index 0000000..ca5ffe9
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/dom/AttributeDefinition.java
@@ -0,0 +1,165 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.dom;
+
+import java.io.ObjectStreamException;
+import java.io.Serializable;
+
+
+/**
+ * <code>AttributeDefinition</code> represents an attribute definition in a DTD.
+ */
+public class AttributeDefinition implements Comparable, Serializable {
+
+ private String name;
+ private Type type;
+ private String defaultValue;
+ private String[] values;
+ private boolean required;
+ private boolean fixed;
+
+ /**
+ * Enumeration of attribute types.
+ */
+ public static final class Type implements Serializable {
+
+ private String s;
+
+ public static final Type CDATA = new Type("CDATA");
+ public static final Type ID = new Type("ID");
+ public static final Type IDREF = new Type("IDREF");
+ public static final Type IDREFS = new Type("IDREFS");
+ public static final Type NMTOKEN = new Type("NMTOKEN");
+ public static final Type NMTOKENS = new Type("NMTOKENS");
+ public static final Type ENTITY = new Type("ENTITY");
+ public static final Type ENTITIES = new Type("ENTITIES");
+ public static final Type NOTATION = new Type("NOTATION");
+ public static final Type ENUMERATION = new Type("ENUMERATION");
+
+ private Type(String s) {
+ this.s = s;
+ }
+
+ public static Type get(String s) {
+ if (s.equals(CDATA.toString())) {
+ return CDATA;
+ } else if (s.equals(ID.toString())) {
+ return ID;
+ } else if (s.equals(IDREF.toString())) {
+ return IDREF;
+ } else if (s.equals(IDREFS.toString())) {
+ return IDREFS;
+ } else if (s.equals(NMTOKEN.toString())) {
+ return NMTOKEN;
+ } else if (s.equals(NMTOKENS.toString())) {
+ return NMTOKENS;
+ } else if (s.equals(ENTITY.toString())) {
+ return ENTITY;
+ } else if (s.equals(ENTITIES.toString())) {
+ return ENTITIES;
+ } else if (s.equals(NOTATION.toString())) {
+ return NOTATION;
+ } else if (s.equals(ENUMERATION.toString())) {
+ return ENUMERATION;
+ } else {
+ throw new IllegalArgumentException(
+ "Attribute type '" + s + "' not recognized");
+ }
+ }
+
+ public String toString() {
+ return this.s;
+ }
+
+ /**
+ * Serialization method, to ensure that we do not introduce new
+ * instances.
+ */
+ private Object readResolve() throws ObjectStreamException {
+ return get(this.toString());
+ }
+ }
+
+
+ /**
+ * Class constructor.
+ */
+ public AttributeDefinition(
+ String name,
+ Type type,
+ String defaultValue,
+ String[] values,
+ boolean required,
+ boolean fixed) {
+
+ this.name = name;
+ this.type = type;
+ this.defaultValue = defaultValue;
+ this.values = values;
+ this.required = required;
+ this.fixed = fixed;
+ }
+
+ /**
+ * Implements <code>Comparable.compareTo</code> to sort alphabetically
+ * by name.
+ *
+ * @param other The attribute to which this one is to be compared.
+ */
+ public int compareTo(Object other) {
+ return this.name.compareTo(((AttributeDefinition)other).name);
+ }
+
+ /**
+ * Returns the attribute's type.
+ */
+ public Type getType() {
+ return this.type;
+ }
+
+ /**
+ * Returns the default value of the attribute.
+ */
+ public String getDefaultValue() {
+ return defaultValue;
+ }
+
+ /**
+ * Returns true if the attribute value is fixed.
+ */
+ public boolean isFixed() {
+ return fixed;
+ }
+
+ /**
+ * Returns the name of the attribute.
+ */
+ public String getName() {
+ return name;
+ }
+
+ /**
+ * Returns true if the attribute is required.
+ */
+ public boolean isRequired() {
+ return required;
+ }
+
+ /**
+ * Returns an array of acceptable values for the attribute.
+ * If null is returned, any value is acceptable for the attribute.
+ */
+ public String[] getValues() {
+ return values;
+ }
+
+}
+
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/dom/Content.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/dom/Content.java
new file mode 100644
index 0000000..758f3e8
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/dom/Content.java
@@ -0,0 +1,54 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.dom;
+
+/**
+ * Interface for classes that manage a string of characters representing
+ * the content of a document.
+ */
+public interface Content {
+
+ /**
+ * Creates a new Position object at the given initial offset.
+ *
+ * @param offset initial offset of the position
+ */
+ public Position createPosition(int offset);
+
+ /**
+ * Insert a string into the content.
+ *
+ * @param offset Offset at which to insert the string.
+ * @param s String to insert.
+ */
+ public void insertString(int offset, String s);
+
+ /**
+ * Deletes the given range of characters.
+ *
+ * @param offset Offset from which characters should be deleted.
+ * @param length Number of characters to delete.
+ */
+ public void remove(int offset, int length);
+
+ /**
+ * Gets a substring of the content.
+ *
+ * @param offset Offset at which the string begins.
+ * @param length Number of characters to return.
+ */
+ public String getString(int offset, int length);
+
+ /**
+ * Return the length of the content.
+ */
+ public int getLength();
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/dom/DFABuilder.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/dom/DFABuilder.java
new file mode 100644
index 0000000..5e4671c
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/dom/DFABuilder.java
@@ -0,0 +1,498 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.dom;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Set;
+import java.util.Stack;
+
+
+/**
+ * Tools for building a deterministic finite automaton (DFA)
+ * recognizer for regular expression-like languages.
+ */
+public class DFABuilder {
+
+ /**
+ * Node represents a node in an abstract syntax tree. The first
+ * step to creating a DFA is to build an AST using the given
+ * createXxx methods.
+ */
+ public interface Node {
+ public void accept(NodeVisitor visitor);
+ public Object clone();
+ public Set getFirstPos();
+ public Set getLastPos();
+ public boolean isNullable();
+ }
+
+ /**
+ * Create a node that represents a choice between two nodes.
+ *
+ * @param child1 first choice
+ * @param child2 second choice
+ */
+ public static Node createChoiceNode(Node child1, Node child2) {
+ return new OrNode(child1, child2);
+ }
+
+ /**
+ * Create a DFA given the root node of the syntax tree.
+ *
+ * @return Initial state of the resulting DFA.
+ * @param root Root node of the syntax tree.
+ */
+ public static DFAState createDFA(Node root) {
+
+ // Append a sentinel to indicate accepting states.
+ SymbolNode sentinelNode = new SymbolNode(Sentinel.getInstance());
+ Node fakeRoot = new CatNode(root, sentinelNode);
+
+ // map symbol node set => state in the new DFA
+ Map stateMap = new HashMap();
+
+ // symbol node sets we have considered
+ Set marked = new HashSet();
+
+ // stack of symbol node sets we have yet to consider
+ Stack unmarked = new Stack();
+
+ // calculate followPos and symbolMap
+ FollowPosBuilder fpb = new FollowPosBuilder();
+ fakeRoot.accept(fpb);
+
+ // map symbol node => set of symbol nodes that follow it
+ Map followPos = fpb.getFollowPos();
+
+ // map symbol => set of symbol nodes that represent it
+ Map symbolMap = fpb.getSymbolMap();
+
+ Set nodeSet = fakeRoot.getFirstPos();
+ DFAState startState = new DFAState();
+ if (nodeSet.contains(sentinelNode)) {
+ startState.setAccepting(true);
+ }
+
+ stateMap.put(nodeSet, startState);
+
+ unmarked.push(nodeSet);
+
+ while (unmarked.size() > 0) {
+ nodeSet = (Set) unmarked.pop();
+ marked.add(nodeSet);
+ DFAState state = (DFAState) stateMap.get(nodeSet);
+ if (state == null) {
+ state = new DFAState();
+ stateMap.put(nodeSet, state);
+ }
+
+ Iterator iterSymbols = symbolMap.keySet().iterator();
+ while (iterSymbols.hasNext()) {
+ Object symbol = iterSymbols.next();
+ Set targetSet = new HashSet();
+ Iterator iterNodes = nodeSet.iterator();
+ while (iterNodes.hasNext()) {
+ SymbolNode node = (SymbolNode) iterNodes.next();
+ if (node.getSymbol().equals(symbol)) {
+ targetSet.addAll((Set)followPos.get(node));
+ }
+ }
+
+ if (!targetSet.isEmpty()) {
+ if (!unmarked.contains(targetSet)
+ && !marked.contains(targetSet)) {
+
+ unmarked.push(targetSet);
+ }
+
+ DFAState targetState = (DFAState)
+ stateMap.get(targetSet);
+
+ if (targetState == null) {
+ targetState = new DFAState();
+ if (targetSet.contains(sentinelNode)) {
+ targetState.setAccepting(true);
+ }
+ stateMap.put(targetSet, targetState);
+ }
+
+ state.addTransition(symbol, targetState);
+ }
+ }
+
+
+ }
+
+ return startState;
+ }
+
+ /**
+ * Create optional node.
+ *
+ * @param child Node that is optional.
+ */
+ public static Node createOptionalNode(Node child) {
+ return new OrNode(child, new NullNode());
+ }
+
+ /**
+ * Create a repeating node.
+ *
+ * @param child Node that can be repeated.
+ * @param minRepeat minimum number of times the node can be repeated.
+ */
+ public static Node createRepeatingNode(Node child, int minRepeat) {
+ Node node = new StarNode(child);
+ for (int i = 0; i < minRepeat; i++) {
+ node = new CatNode(node, (Node) child.clone());
+ }
+ return node;
+ }
+
+ /**
+ * Creates a node representing a sequence of two other nodes.
+ *
+ * @param child1 first node in the sequence.
+ * @param child2 second node in the sequence.
+ */
+ public static Node createSequenceNode(Node child1, Node child2) {
+ return new CatNode(child1, child2);
+ }
+
+ /**
+ * Create a node for a symbol.
+ *
+ * @param symbol Symbol contained by the node.
+ */
+ public static Node createSymbolNode(Object symbol) {
+ return new SymbolNode(symbol);
+ }
+
+ //============================================================ PRIVATE
+
+ /**
+ * Implementation of node that keeps firstPos, lastPos, and nullable
+ * as instance variables. The accept method is undefined.
+ */
+ private abstract static class AbstractNode implements Node {
+ protected Set firstPos;
+ protected Set lastPos;
+ protected boolean nullable;
+
+ public abstract Object clone();
+
+ public Set getFirstPos() {
+ return this.firstPos;
+ }
+ public Set getLastPos() {
+ return this.lastPos;
+ }
+ public boolean isNullable() {
+ return this.nullable;
+ }
+
+ protected Set union(Set set1, Set set2) {
+ Set retval = new HashSet();
+ retval.addAll(set1);
+ retval.addAll(set2);
+ return retval;
+ }
+ }
+
+ /**
+ * Node representing a sequence of two nodes.
+ */
+ private static class CatNode extends AbstractNode {
+ private Node leftChild;
+ private Node rightChild;
+
+ public CatNode(Node leftChild, Node rightChild) {
+ this.leftChild = leftChild;
+ this.rightChild = rightChild;
+
+ if (leftChild.isNullable()) {
+ this.firstPos = union(leftChild.getFirstPos(),
+ rightChild.getFirstPos());
+ } else {
+ this.firstPos = leftChild.getFirstPos();
+ }
+
+ if (rightChild.isNullable()) {
+ this.lastPos = union(leftChild.getLastPos(),
+ rightChild.getLastPos());
+ } else {
+ this.lastPos = rightChild.getLastPos();
+ }
+
+ this.nullable = leftChild.isNullable() && rightChild.isNullable();
+ }
+
+ public void accept(NodeVisitor visitor) {
+ leftChild.accept(visitor);
+ rightChild.accept(visitor);
+ visitor.visitCatNode(this);
+ }
+
+ public Object clone() {
+ return new CatNode((Node) this.leftChild.clone(),
+ (Node) this.rightChild.clone());
+ }
+
+ public Node getLeftChild() {
+ return this.leftChild;
+ }
+
+ public Node getRightChild() {
+ return this.rightChild;
+ }
+ }
+
+ /**
+ * Builds the followPos function. The function is represented by a
+ * map from symbol nodes to sets of symbol nodes that can follow
+ * them. Also generates a map of symbols to sets of symbol nodes
+ * that represent them.
+ */
+ private static class FollowPosBuilder implements NodeVisitor {
+ private Map followPos = new HashMap();
+ private Map symbolMap = new HashMap();
+
+ public Map getFollowPos() {
+ return this.followPos;
+ }
+
+ public Map getSymbolMap() {
+ return this.symbolMap;
+ }
+
+ public void visitCatNode(CatNode node) {
+ Iterator iter = node.getLeftChild().getLastPos().iterator();
+ while (iter.hasNext()) {
+ SymbolNode symbolNode = (SymbolNode) iter.next();
+ Set set = this.getFollowPos(symbolNode);
+ set.addAll(node.getRightChild().getFirstPos());
+ }
+ }
+
+ public void visitNullNode(NullNode node) {
+ }
+
+ public void visitOrNode(OrNode node) {
+ }
+
+ public void visitStarNode(StarNode node) {
+ Iterator iter = node.getChild().getLastPos().iterator();
+ while (iter.hasNext()) {
+ SymbolNode symbolNode = (SymbolNode) iter.next();
+ Set set = this.getFollowPos(symbolNode);
+ set.addAll(node.getChild().getFirstPos());
+ }
+ }
+
+ public void visitSymbolNode(SymbolNode node) {
+
+ // Done by getFollowPos(SymbolNode)
+ //this.followPos.put(node, new HashSet());
+
+ // Ensure we have an entry for this symbol
+ this.getFollowPos(node);
+
+ Object symbol = node.getSymbol();
+ Set symbolNodeSet = (Set) this.symbolMap.get(symbol);
+ if (symbolNodeSet == null) {
+ symbolNodeSet = new HashSet();
+ this.symbolMap.put(symbol, symbolNodeSet);
+ }
+ symbolNodeSet.add(node);
+ }
+
+ private Set getFollowPos(SymbolNode node) {
+ Set ret = (Set) this.followPos.get(node);
+ if (ret == null) {
+ ret = new HashSet();
+ this.followPos.put(node, ret);
+ }
+ return ret;
+ }
+
+ }
+
+ /**
+ * Describes a visitor that can walk an AST.
+ */
+ private interface NodeVisitor {
+ public void visitCatNode(CatNode node);
+ public void visitNullNode(NullNode node);
+ public void visitOrNode(OrNode node);
+ public void visitStarNode(StarNode node);
+ public void visitSymbolNode(SymbolNode node);
+ }
+
+ /**
+ * Node representing nothing. It is used with OrNode to construct an
+ * optional entry.
+ */
+ private static class NullNode extends AbstractNode {
+ public NullNode() {
+ this.firstPos = Collections.EMPTY_SET;
+ this.lastPos = Collections.EMPTY_SET;
+ this.nullable = true;
+ }
+
+ public void accept(NodeVisitor visitor) {
+ visitor.visitNullNode(this);
+ }
+
+ public Object clone() {
+ return new NullNode();
+ }
+ }
+
+ /**
+ * Node representing a choice between two alternatives.
+ */
+ private static class OrNode extends AbstractNode {
+ private Node leftChild;
+ private Node rightChild;
+
+ public OrNode(Node leftChild, Node rightChild) {
+ this.leftChild = leftChild;
+ this.rightChild = rightChild;
+
+ this.firstPos = union(leftChild.getFirstPos(),
+ rightChild.getFirstPos());
+ this.lastPos = union(leftChild.getLastPos(),
+ rightChild.getLastPos());
+
+ this.nullable = leftChild.isNullable() || rightChild.isNullable();
+ }
+
+ public void accept(NodeVisitor visitor) {
+ leftChild.accept(visitor);
+ rightChild.accept(visitor);
+ visitor.visitOrNode(this);
+ }
+
+ public Object clone() {
+ return new OrNode((Node) this.leftChild.clone(),
+ (Node) this.rightChild.clone());
+ }
+
+ public Node getLeftChild() {
+ return this.leftChild;
+ }
+
+ public Node getRightChild() {
+ return this.rightChild;
+ }
+ }
+
+ /**
+ * Symbol appended to the AST to mark accepting states.
+ */
+ private static class Sentinel {
+ private static final Sentinel instance = new Sentinel();
+
+ private Sentinel() {
+ }
+
+ public static Sentinel getInstance() {
+ return instance;
+ }
+
+ public String toString() {
+ return "#";
+ }
+ }
+
+ /**
+ * Node representing zero or more repetitions of its child.
+ */
+ private static class StarNode extends AbstractNode {
+ private Node child;
+
+ public StarNode(Node child) {
+ this.child = child;
+ this.firstPos = child.getFirstPos();
+ this.lastPos = child.getLastPos();
+ this.nullable = true;
+ }
+
+ public void accept(NodeVisitor visitor) {
+ child.accept(visitor);
+ visitor.visitStarNode(this);
+ }
+
+ public Object clone() {
+ return new StarNode((Node) this.child.clone());
+ }
+
+ public Node getChild() {
+ return this.child;
+ }
+ }
+
+ /**
+ * Node representing a symbol.
+ */
+ private static class SymbolNode extends AbstractNode {
+
+ private static int pos = 1;
+ private int myPos;
+ private Object symbol;
+
+ public SymbolNode (Object symbol) {
+ this.symbol = symbol;
+ this.firstPos = Collections.singleton(this);
+ this.lastPos = Collections.singleton(this);
+ this.nullable = false;
+ this.myPos = pos++;
+ }
+
+ public void accept(NodeVisitor visitor) {
+ visitor.visitSymbolNode(this);
+ }
+
+ public Object clone() {
+ return new SymbolNode(this.symbol);
+ }
+
+ public int getMyPos() {
+ return this.myPos;
+ }
+
+ public Object getSymbol() {
+ return this.symbol;
+ }
+ }
+
+
+ /*
+ private static String snSetToString(Set set) {
+ StringBuffer sb = new StringBuffer();
+ sb.append("{ ");
+ Iterator i2 = set.iterator();
+ while (i2.hasNext()) {
+ SymbolNode sn2 = (SymbolNode) i2.next();
+ sb.append(sn2.getMyPos());
+ sb.append(" ");
+ }
+ sb.append("}");
+ return sb.toString();
+
+ }
+ */
+}
+
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/dom/DFAState.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/dom/DFAState.java
new file mode 100644
index 0000000..18d8453
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/dom/DFAState.java
@@ -0,0 +1,113 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.dom;
+
+import java.io.Serializable;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+
+/**
+ * Represents a state in a deterministic finite automaton (DFA). A DFA
+ * can be thought of as a directed graph, where each arc in the graph
+ * represents a transition on an input symbol to a new state (i.e. a
+ * node in the graph.) The DFA has a start state and one or more
+ * accepting states. The DFA represents a grammar. If a sequence of
+ * input symbols drives the DFA from the start state to one of the
+ * accepting states, the sequence is a valid sentence in the grammar
+ * represented by the DFA.
+ *
+ * <p>Within VEX, we use a DFA to validate the sequence of children of
+ * a given element. A DFA is constructed for each element declaration
+ * in the DTD.</p>
+ */
+public class DFAState implements Serializable {
+
+ private boolean accepting = false;
+ private Map transitions = new HashMap();
+
+ /**
+ * Class constructor.
+ */
+ public DFAState() {
+ }
+
+ /**
+ * Return the state obtained by traversing the given list of symbols.
+ * Returns null if the given sequence does not lead to a state in the DFA.
+ *
+ * @param sequence Sequence of symbols to use.
+ */
+ public DFAState getState(List sequence) {
+ DFAState state = this;
+ Iterator iter = sequence.iterator();
+ while (iter.hasNext()) {
+ state = state.getNextState(iter.next());
+ if (state == null) {
+ break;
+ }
+ }
+ return state;
+ }
+
+ /**
+ * Adds an outgoing transition to the state.
+ *
+ * @param symbol Symbol that initiates the transition.
+ * @param target State to which the transition leads.
+ */
+ public void addTransition(Object symbol, DFAState target) {
+ this.transitions.put(symbol, target);
+ }
+
+ /**
+ * Returns the set of symbols that are valid for this state.
+ */
+ public Set getValidSymbols() {
+ return this.transitions.keySet();
+ }
+
+ /**
+ * Returns true if this is an accepting state of the DFA.
+ */
+ public boolean isAccepting() {
+ return this.accepting;
+ }
+
+ /**
+ * Returns the next state given the given input symbol, or
+ * null if there are no outgoing transitions corresponding to
+ * the given symbol.
+ *
+ * @param symbol input symbol
+ */
+ public DFAState getNextState(Object symbol) {
+ return (DFAState) this.transitions.get(symbol);
+ }
+
+ /**
+ * Sets the value of the accepting property.
+ *
+ * @param accepting true if this an accepting state of the DFA.
+ */
+ public void setAccepting(boolean accepting) {
+ this.accepting = accepting;
+ }
+
+
+
+ //========================================================= PRIVATE
+
+}
+
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/dom/DTDValidator.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/dom/DTDValidator.java
new file mode 100644
index 0000000..a0d4901
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/dom/DTDValidator.java
@@ -0,0 +1,301 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.dom;
+
+import java.io.IOException;
+import java.net.URL;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Set;
+
+import com.wutka.dtd.DTD;
+import com.wutka.dtd.DTDAny;
+import com.wutka.dtd.DTDAttribute;
+import com.wutka.dtd.DTDCardinal;
+import com.wutka.dtd.DTDChoice;
+import com.wutka.dtd.DTDContainer;
+import com.wutka.dtd.DTDDecl;
+import com.wutka.dtd.DTDElement;
+import com.wutka.dtd.DTDEmpty;
+import com.wutka.dtd.DTDEnumeration;
+import com.wutka.dtd.DTDItem;
+import com.wutka.dtd.DTDMixed;
+import com.wutka.dtd.DTDName;
+import com.wutka.dtd.DTDNotationList;
+import com.wutka.dtd.DTDPCData;
+import com.wutka.dtd.DTDParser;
+import com.wutka.dtd.DTDSequence;
+
+/**
+ * A validator driven by a DTD.
+ */
+public class DTDValidator extends AbstractValidator {
+
+ // DFA representing an EMPTY element; just a single non-accepting state
+ // with no transitions.
+ private static final DFAState emptyDFA = new DFAState();
+
+ // map element names to DFAs
+ private Map elementDFAs = new HashMap();
+
+ // list of all element names plus PCDATA
+ private Set anySet;
+
+ // map element names to arrays of AttributeDefinition objects
+ private Map attributeArrays = new HashMap();
+
+ // map element names to maps of attribute name to attribute def
+ private Map attributeMaps = new HashMap();
+
+ /**
+ * Creates a instance of DtdValidator given a URL.
+ *
+ * @param url URL of the DTD file to use.
+ */
+ public static DTDValidator create(URL url) throws IOException {
+
+ // Compute the DFAs for each element in the DTD
+
+ DTDParser parser = new DTDParser(url);
+ DTD dtd = parser.parse();
+
+ DTDValidator validator = new DTDValidator();
+ Iterator iter = dtd.elements.values().iterator();
+ while (iter.hasNext()) {
+ DTDElement element = (DTDElement) iter.next();
+ DFAState dfa;
+ if (element.getContent() instanceof DTDEmpty) {
+ dfa = emptyDFA;
+ } else if (element.getContent() instanceof DTDAny) {
+ dfa = null;
+ } else {
+ DFABuilder.Node node = createDFANode(element.getContent());
+ dfa = DFABuilder.createDFA(node);
+ }
+ validator.elementDFAs.put(element.getName(), dfa);
+
+ Map defMap = new HashMap();
+ AttributeDefinition[] defArray = new AttributeDefinition[element.attributes.size()];
+ int i = 0;
+ Iterator iter2 = element.attributes.values().iterator();
+ while (iter2.hasNext()) {
+ DTDAttribute attr = (DTDAttribute) iter2.next();
+ AttributeDefinition.Type type;
+ String[] values = null;
+ if (attr.getType() instanceof DTDEnumeration) {
+ type = AttributeDefinition.Type.ENUMERATION;
+ values = ((DTDEnumeration)attr.getType()).getItems();
+ } else if (attr.getType() instanceof DTDNotationList) {
+ type = AttributeDefinition.Type.ENUMERATION;
+ values = ((DTDNotationList)attr.getType()).getItems();
+ } else if (attr.getType() instanceof String) {
+ type = AttributeDefinition.Type.get((String) attr.getType());
+ } else {
+ throw new RuntimeException("Unrecognized attribute type for element "
+ + element.getName() + " attribute " + attr.getName()
+ + " type " + attr.getType().getClass().getName());
+ }
+
+ AttributeDefinition ad = new AttributeDefinition(attr.getName(),
+ type,
+ attr.getDefaultValue(),
+ values,
+ attr.getDecl() == DTDDecl.REQUIRED,
+ attr.getDecl() == DTDDecl.FIXED);
+
+ defMap.put(attr.getName(), ad);
+ defArray[i] = ad;
+
+ i++;
+ }
+ validator.attributeMaps.put(element.getName(), defMap);
+ Arrays.sort(defArray);
+ validator.attributeArrays.put(element.getName(), defArray);
+ }
+
+ // Calculate anySet
+
+ validator.anySet = new HashSet();
+ validator.anySet.addAll(validator.elementDFAs.keySet());
+ validator.anySet.add(Validator.PCDATA);
+
+ return validator;
+ }
+
+ public AttributeDefinition getAttributeDefinition(String element, String attribute) {
+ Map attrMap = (Map) this.attributeMaps.get(element);
+ return attrMap == null ? null : (AttributeDefinition) attrMap.get(attribute);
+ }
+
+ public AttributeDefinition[] getAttributeDefinitions(String element) {
+ if (this.attributeArrays.containsKey(element)) {
+ return (AttributeDefinition[]) this.attributeArrays.get(element);
+ } else {
+ return new AttributeDefinition[0];
+ }
+ }
+
+ public Set getValidRootElements() {
+ return this.elementDFAs.keySet();
+ }
+
+ /** @see Validator#getValidItems */
+ public Set getValidItems(String element, String[] prefix, String[] suffix) {
+
+ // First, get a set of candidates. We'll later test to see if each is
+ // valid to insert here.
+
+ Set candidates = null;
+ DFAState dfa = (DFAState) elementDFAs.get(element);
+ if (dfa == null) {
+ // Anything goes!
+ return this.anySet;
+ }
+
+ DFAState target = dfa.getState(Arrays.asList(prefix));
+ if (target == null) {
+ return Collections.EMPTY_SET;
+ } else {
+ // If the last transition was due to PCDATA, adding more PCDATA
+ // is also valid
+ if (prefix.length > 0
+ && prefix[prefix.length - 1].equals(Validator.PCDATA)) {
+ candidates = new HashSet();
+ candidates.addAll(target.getValidSymbols());
+ candidates.add(Validator.PCDATA);
+ } else {
+ candidates = target.getValidSymbols();
+ }
+ }
+
+ // Now, see if each candidate can be inserted at the given
+ // offset. This second test is necessary in some simple
+ // cases. Consider a <section> with an optional <title>; if
+ // we're at the first offset of the <section> and a <title>
+ // already exists, we should not allow another <title>.
+
+ Set results = new HashSet();
+ String[] middle = new String[1];
+ for (Iterator iter = candidates.iterator(); iter.hasNext(); ) {
+ middle[0] = (String) iter.next();
+ if (this.isValidSequence(element, prefix, middle, suffix, true)) {
+ results.add(middle[0]);
+ }
+ }
+
+ return Collections.unmodifiableSet(results);
+ }
+
+ /**
+ * @see Validator#isValidSequence
+ */
+ public boolean isValidSequence(
+ String element,
+ String[] nodes,
+ boolean partial) {
+
+ DFAState dfa = (DFAState) this.elementDFAs.get(element);
+ if (dfa == null) {
+ // Unrecognized element. Give the user the benefit of the doubt.
+ return true;
+ }
+
+ DFAState target = dfa.getState(Arrays.asList(nodes));
+
+ return target != null && (partial || target.isAccepting());
+ }
+
+ //==================================================== PRIVATE
+
+ /**
+ * Homeys must call create()
+ */
+ private DTDValidator() {
+ }
+
+ /**
+ * Create a DFABuilder.Node corresponding to the given DTDItem.
+ */
+ private static DFABuilder.Node createDFANode(DTDItem item) {
+ DFABuilder.Node node = null;
+
+ if (item instanceof DTDName) {
+ String name = ((DTDName) item).getValue();
+ node = DFABuilder.createSymbolNode(name);
+
+ } else if (item instanceof DTDPCData) {
+ node = DFABuilder.createSymbolNode(Validator.PCDATA);
+
+ } else if (item instanceof DTDChoice) {
+ Iterator iter = ((DTDContainer)item).getItemsVec().iterator();
+ while (iter.hasNext()) {
+ DTDItem child = (DTDItem) iter.next();
+ DFABuilder.Node newNode = createDFANode(child);
+ if (node == null) {
+ node = newNode;
+ } else {
+ node = DFABuilder.createChoiceNode(node, newNode);
+ }
+ }
+
+ } else if (item instanceof DTDMixed) {
+ Iterator iter = ((DTDContainer)item).getItemsVec().iterator();
+ while (iter.hasNext()) {
+ DTDItem child = (DTDItem) iter.next();
+ DFABuilder.Node newNode = createDFANode(child);
+ if (node == null) {
+ node = newNode;
+ } else {
+ node = DFABuilder.createChoiceNode(node, newNode);
+ }
+ }
+ DFABuilder.Node pcdata =
+ DFABuilder.createSymbolNode(Validator.PCDATA);
+ node = DFABuilder.createChoiceNode(node, pcdata);
+
+ } else if (item instanceof DTDSequence) {
+ Iterator iter = ((DTDContainer)item).getItemsVec().iterator();
+ while (iter.hasNext()) {
+ DTDItem child = (DTDItem) iter.next();
+ DFABuilder.Node newNode = createDFANode(child);
+ if (node == null) {
+ node = newNode;
+ } else {
+ node = DFABuilder.createSequenceNode(node, newNode);
+ }
+ }
+ } else {
+ throw new RuntimeException("Unexpected DTDItem subclass: " +
+ item.getClass().getName());
+ }
+
+ // Cardinality is moot if it's a null node
+ if (node == null) {
+ return node;
+ }
+
+ if (item.cardinal == DTDCardinal.OPTIONAL) {
+ node = DFABuilder.createOptionalNode(node);
+ } else if (item.cardinal == DTDCardinal.ZEROMANY) {
+ node = DFABuilder.createRepeatingNode(node, 0);
+ } else if (item.cardinal == DTDCardinal.ONEMANY) {
+ node = DFABuilder.createRepeatingNode(node, 1);
+ }
+
+ return node;
+ }
+
+}
+
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/dom/Document.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/dom/Document.java
new file mode 100644
index 0000000..016d3b1
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/dom/Document.java
@@ -0,0 +1,966 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.dom;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+import org.eclipse.wst.xml.vex.core.internal.core.ListenerList;
+import org.eclipse.wst.xml.vex.core.internal.undo.CannotRedoException;
+import org.eclipse.wst.xml.vex.core.internal.undo.CannotUndoException;
+import org.eclipse.wst.xml.vex.core.internal.undo.IUndoableEdit;
+
+
+/**
+ * Represents an XML document.
+ */
+public class Document {
+
+ private Content content;
+ private RootElement rootElement;
+ private ListenerList listeners = new ListenerList(DocumentListener.class, DocumentEvent.class);
+ private boolean undoEnabled = true;
+
+ private String publicID;
+ private String systemID;
+ private String encoding;
+ private Validator validator;
+
+ /**
+ * Class constructor.
+ * @param rootElement root element of the document. The
+ * document property of this RootElement is set by this
+ * constructor.
+ */
+ public Document(RootElement rootElement) {
+ this.content = new GapContent(100);
+ this.rootElement = rootElement;
+ rootElement.setDocument(this);
+ this.content.insertString(0, "\0\0");
+ rootElement.setContent(this.content, 0, 1);
+ }
+
+ /**
+ * Class constructor. This constructor is used by the document builder
+ * and assumes that the content and root element have bee properly set up.
+ *
+ * @param content Content object used to store the document's content.
+ * @param rootElement RootElement of the document.
+ */
+ public Document(Content content, RootElement rootElement) {
+ this.content = content;
+ this.rootElement = rootElement;
+ }
+
+ /**
+ * Adds a document listener to the list of listeners to be notified of
+ * document changes.
+ *
+ * @param listener <code>DocumentListener</code> to add.
+ */
+ public void addDocumentListener(DocumentListener listener) {
+ this.listeners.add(listener);
+ }
+
+ /**
+ * Returns true if the given document fragment can be inserted at the
+ * given offset.
+ *
+ * @param offset offset where the insertion is to occur
+ * @param fragment fragment to be inserted
+ */
+ public boolean canInsertFragment(int offset, DocumentFragment fragment) {
+
+ if (this.validator == null) {
+ return true;
+ }
+
+ Element element = this.getElementAt(offset);
+ String[] seq1 = this.getNodeNames(element.getStartOffset() + 1, offset);
+ String[] seq2 = fragment.getNodeNames();
+ String[] seq3 = this.getNodeNames(offset, element.getEndOffset());
+ return this.validator.isValidSequence(
+ element.getName(),
+ seq1, seq2, seq3, true);
+ }
+
+ /**
+ * Returns true if text can be inserted at the
+ * given offset.
+ *
+ * @param offset offset where the insertion is to occur
+ */
+ public boolean canInsertText(int offset) {
+
+ if (this.validator == null) {
+ return true;
+ }
+
+ Element element = this.getElementAt(offset);
+ String[] seq1 = this.getNodeNames(element.getStartOffset() + 1, offset);
+ String[] seq2 = new String[] { Validator.PCDATA };
+ String[] seq3 = this.getNodeNames(offset, element.getEndOffset());
+
+ return this.validator.isValidSequence(
+ element.getName(), seq1, seq2, seq3, true);
+ }
+
+ /**
+ * Creates a <code>Position</code> object at the given character offset.
+ *
+ * @param offset initial character offset of the position
+ */
+ public Position createPosition(int offset) {
+ return this.content.createPosition(offset);
+ }
+
+ /**
+ * Deletes a portion of the document. No element may straddle the
+ * deletion span.
+ *
+ * @param startOffset start of the range to delete
+ * @param endOffset end of the range to delete
+ * @throws DocumentValidationException if the change would result
+ * in an invalid document.
+ */
+ public void delete(int startOffset, int endOffset)
+ throws DocumentValidationException {
+
+ Element e1 = this.getElementAt(startOffset);
+ Element e2 = this.getElementAt(endOffset);
+ if (e1 != e2) {
+ throw new IllegalArgumentException("Deletion from " + startOffset + " to " + endOffset + " is unbalanced");
+ }
+
+ Validator validator = this.getValidator();
+ if (validator != null) {
+ String[] seq1 = this.getNodeNames(e1.getStartOffset() + 1, startOffset);
+ String[] seq2 = this.getNodeNames(endOffset, e1.getEndOffset());
+ if (!validator.isValidSequence(
+ e1.getName(), seq1, seq2, null, true)) {
+ throw new DocumentValidationException("Unable to delete from " + startOffset + " to " + endOffset);
+ }
+ }
+
+ // Grab the fragment for the undoable edit while it's still here
+ DocumentFragment frag = getFragment(startOffset, endOffset);
+
+ this.fireBeforeContentDeleted(
+ new DocumentEvent(this, e1, startOffset, endOffset - startOffset, null));
+
+ Iterator iter = e1.getChildIterator();
+ while (iter.hasNext()) {
+ Element child = (Element) iter.next();
+ if (startOffset <= child.getStartOffset() &&
+ child.getEndOffset() < endOffset) {
+ iter.remove();
+ }
+ }
+
+ this.content.remove(startOffset, endOffset - startOffset);
+
+ IUndoableEdit edit = this.undoEnabled ?
+ new DeleteEdit(startOffset, endOffset, frag) : null;
+
+ this.fireContentDeleted(
+ new DocumentEvent(this, e1, startOffset, endOffset - startOffset, edit));
+ }
+
+ /**
+ * Finds the lowest element that contains both of the given offsets.
+ *
+ * @param offset1 the first offset
+ * @param offset2 the second offset
+ */
+ public Element findCommonElement(int offset1, int offset2) {
+ Element element = this.rootElement;
+ for (;;) {
+ boolean tryAgain = false;
+ Element[] children = element.getChildElements();
+ for (int i = 0; i < children.length; i++) {
+ if (offset1 > children[i].getStartOffset() &&
+ offset2 > children[i].getStartOffset() &&
+ offset1 <= children[i].getEndOffset() &&
+ offset2 <= children[i].getEndOffset()) {
+
+ element = children[i];
+ tryAgain = true;
+ break;
+ }
+ }
+ if (!tryAgain) {
+ break;
+ }
+ }
+ return element;
+ }
+
+ /**
+ * Returns the character at the given offset.
+ */
+ public char getCharacterAt(int offset) {
+ return this.content.getString(offset, 1).charAt(0);
+ }
+
+ /**
+ * Returns the element at the given offset. The given offset must be
+ * greater or equal to 1 and less than the current document length.
+ */
+ public Element getElementAt(int offset) {
+ if (offset < 1 || offset >= this.getLength()) {
+ throw new IllegalArgumentException("Illegal offset: " + offset + ". Must be between 1 and n-1");
+ }
+ Element element = this.rootElement;
+ for (;;) {
+ boolean tryAgain = false;
+ Element[] children = element.getChildElements();
+ for (int i = 0; i < children.length; i++) {
+ Element child = children[i];
+ if (offset <= child.getStartOffset()) {
+ return element;
+ } else if (offset <= child.getEndOffset()) {
+ element = child;
+ tryAgain = true;
+ break;
+ }
+ }
+ if (!tryAgain) {
+ break;
+ }
+ }
+ return element;
+ }
+
+ /**
+ * Returns the encoding used for this document, or null if no
+ * encoding has been declared.
+ */
+ public String getEncoding() {
+ return this.encoding;
+ }
+
+ /**
+ * Create a <code>DocumentFragment</code> representing the given
+ * range of offsets.
+ *
+ * @return
+ */
+ public DocumentFragment getFragment(int startOffset, int endOffset) {
+
+ assertOffset(startOffset, 0, this.content.getLength());
+ assertOffset(endOffset, 0, this.content.getLength());
+
+ if (endOffset <= startOffset) {
+ throw new IllegalArgumentException(
+ "Invalid range (" + startOffset + ", " + endOffset + ")");
+ }
+
+ Element e1 = this.getElementAt(startOffset);
+ Element e2 = this.getElementAt(endOffset);
+ if (e1 != e2) {
+ throw new IllegalArgumentException(
+ "Fragment from "
+ + startOffset
+ + " to "
+ + endOffset
+ + " is unbalanced");
+ }
+
+ Element[] children = e1.getChildElements();
+
+ Content newContent = new GapContent(endOffset - startOffset);
+ String s = this.content.getString(startOffset, endOffset - startOffset);
+ newContent.insertString(0, s);
+ List newChildren = new ArrayList();
+ for (int i = 0; i < children.length; i++) {
+ Element child = children[i];
+ if (child.getEndOffset() <= startOffset) {
+ continue;
+ } else if (child.getStartOffset() >= endOffset) {
+ break;
+ } else {
+ newChildren.add(
+ this.cloneElement(child, newContent, -startOffset, null));
+ }
+ }
+
+ Element[] elementArray =
+ (Element[]) newChildren.toArray(new Element[newChildren.size()]);
+ return new DocumentFragment(newContent, elementArray);
+ }
+
+ /**
+ * Returns the length of the document in characters, including the null
+ * characters that delimit each element.
+ */
+ public int getLength() {
+ return this.content.getLength();
+ }
+
+ /**
+ * Returns an array of element names and Validator.PCDATA representing
+ * the content between the given offsets. The given offsets must both
+ * be directly in the same element.
+ *
+ * @param startOffset the offset at which the sequence begins
+ * @param endOffset the offset at which the sequence ends
+ */
+ public String[] getNodeNames(int startOffset, int endOffset) {
+
+ Node[] nodes = this.getNodes(startOffset, endOffset);
+ String[] names = new String[nodes.length];
+
+ for (int i = 0; i < nodes.length; i++) {
+ Node node = nodes[i];
+ if (node instanceof Element) {
+ names[i] = ((Element)node).getName();
+ } else {
+ names[i] = Validator.PCDATA;
+ }
+ }
+
+ return names;
+ }
+
+ /**
+ * Returns an array of Nodes representing the selected range. The given offsets must both
+ * be directly in the same element.
+ *
+ * @param startOffset the offset at which the sequence begins
+ * @param endOffset the offset at which the sequence ends
+ */
+ public Node[] getNodes(int startOffset, int endOffset) {
+
+ Element element = this.getElementAt(startOffset);
+ if (element != this.getElementAt(endOffset)) {
+ throw new IllegalArgumentException(
+ "Offsets are unbalanced: " +
+ startOffset + " is in " + element.getName() + ", " +
+ endOffset + " is in " + this.getElementAt(endOffset).getName());
+ }
+
+ List list = new ArrayList();
+ Node[] nodes = element.getChildNodes();
+ for (int i = 0; i < nodes.length; i++) {
+ Node node = nodes[i];
+ if (node.getEndOffset() <= startOffset) {
+ continue;
+ } else if (node.getStartOffset() >= endOffset) {
+ break;
+ } else {
+ if (node instanceof Element) {
+ list.add(node);
+ } else {
+ Text text = (Text) node;
+ if (text.getStartOffset() < startOffset) {
+ text.setContent(text.getContent(), startOffset, text.getEndOffset());
+ } else if (text.getEndOffset() > endOffset) {
+ text.setContent(text.getContent(), text.getStartOffset(), endOffset);
+ }
+ list.add(text);
+ }
+ }
+ }
+
+ return (Node[]) list.toArray(new Node[list.size()]);
+ }
+
+ /**
+ * Creates an array of nodes for a given run of content. The returned array includes the given child
+ * elements and <code>Text</code> objects where text appears between elements.
+ *
+ * @param content Content object containing the content
+ * @param startOffset start offset of the run
+ * @param endOffset end offset of the run
+ * @param elements child elements that are within the run
+ */
+ static Node[] createNodeArray(Content content, int startOffset, int endOffset, Element[] elements) {
+
+ List nodes = new ArrayList();
+ int offset = startOffset;
+ for (int i = 0; i < elements.length; i++) {
+ int start = elements[i].getStartOffset();
+ if (offset < start) {
+ nodes.add(new Text(content, offset, start));
+ }
+ nodes.add(elements[i]);
+ offset = elements[i].getEndOffset() + 1;
+ }
+
+ if (offset < endOffset) {
+ nodes.add(new Text(content, offset, endOffset));
+ }
+
+ return (Node[]) nodes.toArray(new Node[nodes.size()]);
+ }
+
+ /**
+ * Returns the public ID of the document type.
+ */
+ public String getPublicID() {
+ return this.publicID;
+ }
+
+ /**
+ * Returns the text between the two given offsets. Unlike getText,
+ * sentinel characters are not removed.
+ *
+ * @param startOffset character offset of the start of the text
+ * @param endOffset character offset of the end of the text
+ */
+ public String getRawText(int startOffset, int endOffset) {
+ return this.content.getString(startOffset,
+ endOffset - startOffset);
+ }
+
+ /**
+ * Returns the root element of this document.
+ */
+ public Element getRootElement() {
+ return this.rootElement;
+ }
+
+ /**
+ * Returns the system ID of the document type.
+ */
+ public String getSystemID() {
+ return this.systemID;
+ }
+
+ /**
+ * Returns the text between the two given offsets. Sentinal characters
+ * are removed.
+ *
+ * @param startOffset character offset of the start of the text
+ * @param endOffset character offset of the end of the text
+ */
+ public String getText(int startOffset, int endOffset) {
+ String raw = this.content.getString(startOffset,
+ endOffset - startOffset);
+ StringBuffer sb = new StringBuffer(raw.length());
+ for (int i = 0; i < raw.length(); i++) {
+ char c = raw.charAt(i);
+ if (c != '\0') {
+ sb.append(c);
+ }
+ }
+ return sb.toString();
+ }
+
+ /**
+ * Returns the validator used to validate the document, or null if
+ * a validator has not been set. Note that the DocumentFactory
+ * does not automatically create a validator.
+ */
+ public Validator getValidator() {
+ return this.validator;
+ }
+
+ /**
+ * Inserts an element at the given position.
+ *
+ * @param offset character offset at which the element is to be inserted.
+ * Must be greater or equal to 1 and less than the current length of the
+ * document, i.e. it must be within the range of the root element.
+ * @param element element to insert
+ * @throws DocumentValidationException if the change would result
+ * in an invalid document.
+ */
+ public void insertElement(int offset, Element element)
+ throws DocumentValidationException {
+
+ if (offset < 1 || offset >= this.getLength()) {
+ throw new IllegalArgumentException("Error inserting element <" + element.getName() + ">: offset is " + offset + ", but it must be between 1 and " + (this.getLength() - 1));
+ }
+
+ Validator validator = this.getValidator();
+ if (validator != null) {
+ Element parent = this.getElementAt(offset);
+ String[] seq1 = this.getNodeNames(parent.getStartOffset() + 1, offset);
+ String[] seq2 = new String[] { element.getName() };
+ String[] seq3 = this.getNodeNames(offset, parent.getEndOffset());
+ if (!validator.isValidSequence(
+ parent.getName(), seq1, seq2, seq3, true)) {
+ throw new DocumentValidationException("Cannot insert element " + element.getName() + " at offset " + offset);
+ }
+ }
+
+ // find the parent, and the index into its children at which
+ // this element should be inserted
+ Element parent = this.rootElement;
+ int childIndex = -1;
+ while (childIndex == -1) {
+ boolean tryAgain = false;
+ Element[] children = parent.getChildElements();
+ for (int i = 0; i < children.length; i++) {
+ Element child = children[i];
+ if (offset <= child.getStartOffset()) {
+ childIndex = i;
+ break;
+ } else if (offset <= child.getEndOffset()) {
+ parent = child;
+ tryAgain = true;
+ break;
+ }
+ }
+ if (!tryAgain && childIndex == -1) {
+ childIndex = children.length;
+ break;
+ }
+ }
+
+ this.fireBeforeContentInserted(new DocumentEvent(this, parent, offset, 2, null));
+
+ this.content.insertString(offset, "\0\0");
+
+ element.setContent(this.content, offset, offset + 1);
+ element.setParent(parent);
+ parent.insertChild(childIndex, element);
+
+ IUndoableEdit edit = this.undoEnabled ?
+ new InsertElementEdit(offset, element) : null;
+
+ this.fireContentInserted(new DocumentEvent(this, parent, offset, 2, edit));
+ }
+
+
+ /**
+ * Inserts a document fragment at the given position.
+ *
+ * @param offset character offset at which the element is to be inserted.
+ * Must be greater or equal to 1 and less than the current length of the
+ * document, i.e. it must be within the range of the root element.
+ * @param fragment fragment to insert
+ * @throws DocumentValidationException if the change would result
+ * in an invalid document.
+ */
+ public void insertFragment(int offset, DocumentFragment fragment)
+ throws DocumentValidationException {
+
+ if (offset < 1 || offset >= this.getLength()) {
+ throw new IllegalArgumentException("Error inserting document fragment");
+ }
+
+ Element parent = this.getElementAt(offset);
+
+ if (this.validator != null) {
+ String[] seq1 = this.getNodeNames(parent.getStartOffset() + 1, offset);
+ String[] seq2 = fragment.getNodeNames();
+ String[] seq3 = this.getNodeNames(offset, parent.getEndOffset());
+ if (!validator.isValidSequence(
+ parent.getName(), seq1, seq2, seq3, true)) {
+
+ throw new DocumentValidationException("Cannot insert document fragment");
+ }
+ }
+
+ this.fireBeforeContentInserted(new DocumentEvent(this, parent, offset, 2, null));
+
+ Content c = fragment.getContent();
+ String s = c.getString(0, c.getLength());
+ this.content.insertString(offset, s);
+
+ Element[] children = parent.getChildElements();
+ int index = 0;
+ while (index < children.length
+ && children[index].getEndOffset() < offset) {
+ index++;
+ }
+
+ Element[] elements = fragment.getElements();
+ for (int i = 0; i < elements.length; i++) {
+ Element newElement = this.cloneElement(elements[i], this.content, offset, parent);
+ parent.insertChild(index, newElement);
+ index++;
+ }
+
+ IUndoableEdit edit = this.undoEnabled ?
+ new InsertFragmentEdit(offset, fragment) : null;
+
+ this.fireContentInserted(
+ new DocumentEvent(this, parent, offset, fragment.getContent().getLength(), edit));
+ }
+
+ /**
+ * Inserts text at the given position.
+ *
+ * @param offset character offset at which the text is to be inserted.
+ * Must be greater or equal to 1 and less than the current length of the
+ * document, i.e. it must be within the range of the root element.
+ * @param text text to insert
+ * @return UndoableEdit that can be used to undo the deletion
+ * @throws DocumentValidationException if the change would result
+ * in an invalid document.
+ */
+ public void insertText(int offset, String text)
+ throws DocumentValidationException {
+
+ if (offset < 1 || offset >= this.getLength()) {
+ throw new IllegalArgumentException("Offset must be between 1 and n-1");
+ }
+
+ Element parent = this.getElementAt(offset);
+
+ boolean isValid = false;
+ if (this.getCharacterAt(offset-1) != '\0') {
+ isValid = true;
+ } else if (this.getCharacterAt(offset) != '\0') {
+ isValid = true;
+ } else {
+ Validator validator = this.getValidator();
+ if (validator != null) {
+ String[] seq1 = this.getNodeNames(parent.getStartOffset() + 1, offset);
+ String[] seq2 = new String[] { Validator.PCDATA };
+ String[] seq3 = this.getNodeNames(offset, parent.getEndOffset());
+ isValid = validator.isValidSequence(
+ parent.getName(), seq1, seq2, seq3, true);
+ } else {
+ isValid = true;
+ }
+ }
+
+ if (!isValid) {
+ throw new DocumentValidationException("Cannot insert text '" + text + "' at offset " + offset);
+ }
+
+ // Convert control chars to spaces
+ StringBuffer sb = new StringBuffer(text);
+ for (int i = 0; i < sb.length(); i++) {
+ if (Character.isISOControl(sb.charAt(i)) && sb.charAt(i) != '\n') {
+ sb.setCharAt(i, ' ');
+ }
+ }
+
+ String s = sb.toString();
+
+ this.fireBeforeContentInserted(new DocumentEvent(this, parent, offset, 2, null));
+
+ this.content.insertString(offset, s);
+
+ IUndoableEdit edit = this.undoEnabled ?
+ new InsertTextEdit(offset, s) : null;
+
+ this.fireContentInserted(
+ new DocumentEvent(this, parent, offset, s.length(), edit));
+ }
+
+ /**
+ * Returns true if undo is enabled, that is, undoable edit events are fired
+ * to registered listeners.
+ */
+ public boolean isUndoEnabled() {
+ return this.undoEnabled;
+ }
+
+ /**
+ * Removes a document listener from the list of listeners so that
+ * it is no longer notified of document changes.
+ *
+ * @param listener <code>DocumentListener</code> to remove.
+ */
+ public void removeDocumentListener(DocumentListener listener) {
+ this.listeners.remove(listener);
+ }
+
+ /**
+ * Sets the public ID for the document's document type.
+ *
+ * @param publicID New value for the public ID.
+ */
+ public void setPublicID(String publicID) {
+ this.publicID = publicID;
+ }
+
+ /**
+ * Sets the system ID for the document's document type.
+ *
+ * @param systemID New value for the system ID.
+ */
+ public void setSystemID(String systemID) {
+ this.systemID = systemID;
+ }
+
+ /**
+ * Sets whether undo events are enabled. Typically, undo events are
+ * disabled while an edit is being undone or redone.
+ *
+ * @param undoEnabled If true, undoable edit events are fired to
+ * registered listeners.
+ */
+ public void setUndoEnabled(boolean undoEnabled) {
+ this.undoEnabled = undoEnabled;
+ }
+
+ /**
+ * Sets the validator to use for this document.
+ *
+ * @param validator Validator to use for this document.
+ */
+ public void setValidator(Validator validator) {
+ this.validator = validator;
+ }
+
+ //==================================================== PRIVATE
+
+ /**
+ * Represents a deletion from a document that can be undone
+ * and redone.
+ */
+ private class DeleteEdit implements IUndoableEdit {
+
+ private int startOffset;
+ private int endOffset;
+ private DocumentFragment frag;
+
+ public DeleteEdit(int startOffset, int endOffset, DocumentFragment frag) {
+ this.startOffset = startOffset;
+ this.endOffset = endOffset;
+ this.frag = frag;
+ }
+
+ public boolean combine(IUndoableEdit edit) {
+ return false;
+ }
+
+ public void undo() throws CannotUndoException {
+ try {
+ setUndoEnabled(false);
+ insertFragment(this.startOffset, this.frag);
+ } catch (DocumentValidationException ex) {
+ throw new CannotUndoException();
+ } finally {
+ setUndoEnabled(true);
+ }
+ }
+
+ public void redo() throws CannotRedoException {
+ try {
+ setUndoEnabled(false);
+ delete(this.startOffset, this.endOffset);
+ } catch (DocumentValidationException ex) {
+ throw new CannotUndoException();
+ } finally {
+ setUndoEnabled(true);
+ }
+ }
+
+ }
+
+
+ /**
+ * Represents an insertion of an element into the document.
+ */
+ private class InsertElementEdit implements IUndoableEdit {
+
+ private int offset;
+ private Element element;
+
+ public InsertElementEdit(int offset, Element element) {
+ this.offset = offset;
+ this.element = element;
+ }
+
+ public boolean combine(IUndoableEdit edit) {
+ return false;
+ }
+
+ public void undo() throws CannotUndoException {
+ try {
+ setUndoEnabled(false);
+ delete(this.offset, this.offset + 2);
+ } catch (DocumentValidationException ex) {
+ throw new CannotUndoException();
+ } finally {
+ setUndoEnabled(true);
+ }
+ }
+
+ public void redo() throws CannotRedoException {
+ try {
+ setUndoEnabled(false);
+ insertElement(this.offset, this.element);
+ } catch (DocumentValidationException ex) {
+ throw new CannotUndoException();
+ } finally {
+ setUndoEnabled(true);
+ }
+ }
+
+ }
+
+
+ /**
+ * Represents an insertion of a fragment into the document.
+ */
+ private class InsertFragmentEdit implements IUndoableEdit {
+
+ private int offset;
+ private DocumentFragment frag;
+
+ public InsertFragmentEdit(int offset, DocumentFragment frag) {
+ this.offset = offset;
+ this.frag = frag;
+ }
+
+ public boolean combine(IUndoableEdit edit) {
+ return false;
+ }
+
+ public void undo() throws CannotUndoException {
+ try {
+ setUndoEnabled(false);
+ int length = this.frag.getContent().getLength();
+ delete(this.offset, this.offset + length);
+ } catch (DocumentValidationException ex) {
+ throw new CannotUndoException();
+ } finally {
+ setUndoEnabled(true);
+ }
+ }
+
+ public void redo() throws CannotRedoException {
+ try {
+ setUndoEnabled(false);
+ insertFragment(this.offset, this.frag);
+ } catch (DocumentValidationException ex) {
+ throw new CannotUndoException();
+ } finally {
+ setUndoEnabled(true);
+ }
+ }
+
+ }
+
+
+ /**
+ * Represents an insertion of text into the document.
+ */
+ private class InsertTextEdit implements IUndoableEdit {
+
+ private int offset;
+ private String text;
+
+ public InsertTextEdit(int offset, String text) {
+ this.offset = offset;
+ this.text = text;
+ }
+
+ public boolean combine(IUndoableEdit edit) {
+ if (edit instanceof InsertTextEdit) {
+ InsertTextEdit ite = (InsertTextEdit) edit;
+ if (ite.offset == this.offset + this.text.length()) {
+ this.text = this.text + ite.text;
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public void undo() throws CannotUndoException {
+ try {
+ setUndoEnabled(false);
+ delete(this.offset, this.offset + this.text.length());
+ } catch (DocumentValidationException ex) {
+ throw new CannotUndoException();
+ } finally {
+ setUndoEnabled(true);
+ }
+ }
+
+ public void redo() throws CannotRedoException {
+ try {
+ setUndoEnabled(false);
+ insertText(this.offset, this.text);
+ } catch (DocumentValidationException ex) {
+ throw new CannotUndoException();
+ } finally {
+ setUndoEnabled(true);
+ }
+ }
+
+ }
+
+ /**
+ * Assert that the given offset is within the given range,
+ * throwing IllegalArgumentException if not.
+ */
+ private static void assertOffset(int offset, int min, int max) {
+ if (offset < min || offset > max) {
+ throw new IllegalArgumentException("Bad offset " + offset +
+ "must be between " + min +
+ " and " + max);
+ }
+ }
+
+
+
+ /**
+ * Clone an element tree, pointing to a new Content object.
+ *
+ * @param original Element to be cloned
+ * @param content new Content object to which the clone will point
+ * @param shift amount to shift offsets to be valid in the new Content.
+ * @param parent parent for the cloned Element
+ */
+ private Element cloneElement(
+ Element original,
+ Content content,
+ int shift,
+ Element parent) {
+
+ Element clone = new Element(original.getName());
+ clone.setContent(
+ content,
+ original.getStartOffset() + shift,
+ original.getEndOffset() + shift);
+ String[] attrNames = original.getAttributeNames();
+ for (int i = 0; i < attrNames.length; i++) {
+ try {
+ clone.setAttribute(attrNames[i], original.getAttribute(attrNames[i]));
+ } catch (DocumentValidationException ex) {
+ throw new RuntimeException("Unexpected exception: " + ex);
+ }
+ }
+ clone.setParent(parent);
+
+ Element[] children = original.getChildElements();
+ for (int i = 0; i < children.length; i++) {
+ Element cloneChild =
+ this.cloneElement(children[i], content, shift, clone);
+ clone.insertChild(i, cloneChild);
+ }
+
+ return clone;
+ }
+
+ void fireAttributeChanged(DocumentEvent e) {
+ this.listeners.fireEvent("attributeChanged", e);
+ }
+
+ private void fireBeforeContentDeleted(DocumentEvent e) {
+ this.listeners.fireEvent("beforeContentDeleted", e);
+ }
+
+ private void fireBeforeContentInserted(DocumentEvent e) {
+ this.listeners.fireEvent("beforeContentInserted", e);
+ }
+
+ private void fireContentDeleted(DocumentEvent e) {
+ this.listeners.fireEvent("contentDeleted", e);
+ }
+
+ private void fireContentInserted(DocumentEvent e) {
+ this.listeners.fireEvent("contentInserted", e);
+ }
+
+
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/dom/DocumentBuilder.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/dom/DocumentBuilder.java
new file mode 100644
index 0000000..d222949
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/dom/DocumentBuilder.java
@@ -0,0 +1,325 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.dom;
+
+import java.util.LinkedList;
+import org.xml.sax.Attributes;
+import org.xml.sax.ContentHandler;
+import org.xml.sax.Locator;
+import org.xml.sax.SAXException;
+import org.xml.sax.SAXParseException;
+import org.xml.sax.ext.LexicalHandler;
+
+/**
+ * A SAX handler that builds a Vex document. This builder collapses whitespace
+ * as it goes, according to the following rules.
+ *
+ * <ul>
+ * <li>Elements with style white-space: pre are left alone.</li>
+ * <li>Runs of whitespace are replaced with a single space.</li>
+ * <li>Space just inside the start and end of elements is removed.</li>
+ * <li>Space just outside the start and end of block-formatted elements
+ * is removed.</li>
+ * </ul>
+ */
+public class DocumentBuilder implements ContentHandler, LexicalHandler {
+
+ /**
+ * Class constructor.
+ * @param policyFactory Used to determine the WhitespacePolicy to use
+ * for a given document type.
+ */
+ public DocumentBuilder(IWhitespacePolicyFactory policyFactory) {
+ this.policyFactory = policyFactory;
+ }
+
+ /**
+ * Returns the newly built <code>Document</code> object.
+ */
+ public Document getDocument() {
+ return this.doc;
+ }
+
+ //============================================= ContentHandler methods
+
+ public void characters(char[] ch, int start, int length)
+ throws SAXException {
+
+ // Convert nuls to spaces, since we use nulls for element delimiters
+ char[] chars = new char[length];
+ System.arraycopy(ch, start, chars, 0, length);
+ for (int i = 0; i < chars.length; i++) {
+ if (Character.isISOControl(chars[i]) && chars[i] != '\n' && chars[i] != '\r') {
+ chars[i] = ' ';
+ }
+ }
+ this.pendingChars.append(chars);
+ }
+
+ public void endDocument() {
+ this.doc = new Document(this.content, this.rootElement);
+ this.doc.setPublicID(this.dtdPublicID);
+ this.doc.setSystemID(this.dtdSystemID);
+ this.rootElement.setDocument(this.doc);
+ }
+
+ public void endElement(String namespaceURI,
+ String localName,
+ String qName) {
+
+ this.appendChars(true);
+
+ StackEntry entry = (StackEntry) this.stack.removeLast();
+
+ // we must insert the trailing sentinel first, else the insertion
+ // pushes the end position of the element to after the sentinel
+ this.content.insertString(content.getLength(), "\0");
+ entry.element.setContent(this.content, entry.offset, content.getLength() - 1);
+
+ if (this.isBlock(entry.element)) {
+ this.trimLeading = true;
+ }
+ }
+
+ public void endPrefixMapping(java.lang.String prefix) {
+ }
+
+ public void ignorableWhitespace(char[] ch, int start, int length) {
+ }
+
+ public void processingInstruction(String target, String data) {
+ }
+
+ public void setDocumentLocator(Locator locator) {
+ this.locator = locator;
+ }
+
+ public void skippedEntity(java.lang.String name) {
+ }
+
+ public void startDocument() {
+ }
+
+ public void startElement(String namespaceURI,
+ String localName,
+ String qName,
+ Attributes attrs)
+
+ throws SAXException {
+
+ try {
+ Element element;
+ if (stack.size() == 0) {
+ this.rootElement = new RootElement(qName);
+ element = this.rootElement;
+ if (this.policyFactory != null) {
+ this.policy = this.policyFactory.getPolicy(this.dtdPublicID);
+ }
+ } else {
+ element = new Element(qName);
+ Element parent = ((StackEntry) stack.getLast()).element;
+ parent.addChild(element);
+ }
+
+ int n = attrs.getLength();
+ for (int i = 0; i < n; i++) {
+ element.setAttribute(attrs.getQName(i), attrs.getValue(i));
+ }
+
+ this.appendChars(this.isBlock(element));
+
+ stack.add(new StackEntry(element, content.getLength(), this.isPre(element)));
+ content.insertString(content.getLength(), "\0");
+
+ this.trimLeading = true;
+
+ } catch (DocumentValidationException ex) {
+ throw new SAXParseException("DocumentValidationException",
+ this.locator, ex);
+ }
+
+ }
+
+ public void startPrefixMapping(String prefix, String uri) {
+ }
+
+ //============================================== LexicalHandler methods
+
+ public void comment(char[] ch, int start, int length) {
+ }
+
+ public void endCDATA() {
+ }
+
+ public void endDTD() {
+ }
+
+ public void endEntity(String name) {
+ }
+
+ public void startCDATA() {
+ }
+
+ public void startDTD(String name, String publicId, String systemId) {
+ this.dtdPublicID = publicId;
+ this.dtdSystemID = systemId;
+ }
+
+ public void startEntity(java.lang.String name) {
+ }
+
+ //======================================================== PRIVATE
+
+ private IWhitespacePolicyFactory policyFactory;
+ private IWhitespacePolicy policy;
+
+ // Holds pending characters until we see another element boundary.
+ // This is (a) so we can collapse spaces in multiple adjacent character
+ // blocks, and (b) so we can trim trailing whitespace, if necessary.
+ private StringBuffer pendingChars = new StringBuffer();
+
+ // If true, trim the leading whitespace from the next received block of
+ // text.
+ private boolean trimLeading = false;
+
+ // Content object to hold document content
+ private Content content = new GapContent(100);
+
+ // Stack of StackElement objects
+ private LinkedList stack = new LinkedList();
+
+ private RootElement rootElement;
+
+ private String dtdPublicID;
+ private String dtdSystemID;
+ private Document doc;
+ private Locator locator;
+
+ // Append any pending characters to the content
+ private void appendChars(boolean trimTrailing) {
+
+ StringBuffer sb;
+
+ StackEntry entry = this.stack.size() > 0 ? (StackEntry) this.stack.getLast() : null;
+
+ if (entry != null && entry.pre) {
+
+ sb = this.pendingChars;
+
+ } else{
+
+ // collapse the space in the pending characters
+ sb = new StringBuffer(this.pendingChars.length());
+ boolean ws = false; // true if we're in a run of whitespace
+ for (int i = 0; i < this.pendingChars.length(); i++) {
+ char c = this.pendingChars.charAt(i);
+ if (Character.isWhitespace(c)) {
+ ws = true;
+ } else {
+ if (ws) {
+ sb.append(' ');
+ ws = false;
+ }
+ sb.append(c);
+ }
+ }
+ if (ws) {
+ sb.append(' ');
+ }
+ // trim leading and trailing space, if necessary
+ if (this.trimLeading && sb.length() > 0 && sb.charAt(0) == ' ') {
+ sb.deleteCharAt(0);
+ }
+ if (trimTrailing && sb.length() > 0
+ && sb.charAt(sb.length() - 1) == ' ') {
+ sb.setLength(sb.length() - 1);
+ }
+ }
+
+ this.normalizeNewlines(sb);
+
+ this.content.insertString(this.content.getLength(), sb.toString());
+
+ this.pendingChars.setLength(0);
+ this.trimLeading = false;
+ }
+
+ private boolean isBlock(Element element) {
+ return this.policy != null && this.policy.isBlock(element);
+ }
+
+ private boolean isPre(Element element) {
+ return this.policy != null && this.policy.isPre(element);
+ }
+
+ /**
+ * Convert lines that end in CR and CRLFs to plain newlines.
+ *
+ * @param sb StringBuffer to be normalized.
+ */
+ private void normalizeNewlines(StringBuffer sb) {
+
+ // State machine states
+ final int START = 0;
+ final int SEEN_CR = 1;
+
+ int state = START;
+ int i = 0;
+ while (i < sb.length()) {
+ // No simple 'for' here, since we may delete chars
+
+ char c = sb.charAt(i);
+
+ switch (state) {
+ case START:
+ if (c == '\r') {
+ state = SEEN_CR;
+ }
+ i++;
+ break;
+
+ case SEEN_CR:
+ if (c == '\n') {
+ // CR-LF, just delete the previous CR
+ sb.deleteCharAt(i - 1);
+ state = START;
+ // no need to advance i, since it's done implicitly
+ } else if (c == '\r') {
+ // CR line ending followed by another
+ // Replace the first with a newline...
+ sb.setCharAt(i - 1, '\n');
+ i++;
+ // ...and stay in the SEEN_CR state
+ } else {
+ // CR line ending, replace it with a newline
+ sb.setCharAt(i - 1, '\n');
+ i++;
+ state = START;
+ }
+ }
+ }
+
+ if (state == SEEN_CR) {
+ // CR line ending, replace it with a newline
+ }
+ }
+
+ private static class StackEntry {
+ public Element element;
+ public int offset;
+ public boolean pre;
+ public StackEntry(Element element, int offset, boolean pre) {
+ this.element = element;
+ this.offset = offset;
+ this.pre = pre;
+ }
+ }
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/dom/DocumentEvent.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/dom/DocumentEvent.java
new file mode 100644
index 0000000..37345ae
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/dom/DocumentEvent.java
@@ -0,0 +1,142 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.dom;
+
+import java.util.EventObject;
+
+import org.eclipse.wst.xml.vex.core.internal.undo.IUndoableEdit;
+
+
+/**
+ * Encapsulation of the details of a document change
+ */
+public class DocumentEvent extends EventObject {
+
+ private Document document;
+ private Element parentElement;
+ private int offset;
+ private int length;
+ private String attributeName;
+ private String oldAttributeValue;
+ private String newAttributeValue;
+ private IUndoableEdit undoableEdit;
+
+ /**
+ * Class constructor.
+ *
+ * @param document Document that changed.
+ * @param parentElement Element containing the change.
+ * @param offset offset at which the change occurred.
+ * @param length length of the change.
+ * @param undoableEdit IUndoableEdit that can be used to undo the change.
+ */
+ public DocumentEvent(Document document,
+ Element parentElement,
+ int offset,
+ int length,
+ IUndoableEdit undoableEdit) {
+
+ super(document);
+ this.document = document;
+ this.parentElement = parentElement;
+ this.offset = offset;
+ this.length = length;
+ this.undoableEdit = undoableEdit;
+ }
+
+
+ /**
+ * Class constructor used when firing an attributeChanged event.
+ *
+ * @param document Document that changed.
+ * @param parentElement element containing the attribute that
+ * changed
+ * @param attributeName name of the attribute that changed
+ * @param oldAttributeValue value of the attribute before the
+ * change.
+ * @param newAttributeValue value of the attribute after the change.
+ * @param undoableEdit IUndoableEdit that can be used to undo the change.
+ */
+ public DocumentEvent(Document document,
+ Element parentElement,
+ String attributeName,
+ String oldAttributeValue,
+ String newAttributeValue,
+ IUndoableEdit undoableEdit) {
+
+ super(document);
+ this.document = document;
+ this.parentElement = parentElement;
+ this.attributeName = attributeName;
+ this.oldAttributeValue = oldAttributeValue;
+ this.newAttributeValue = newAttributeValue;
+ this.undoableEdit = undoableEdit;
+ }
+
+ /**
+ * Returns the length of the change.
+ */
+ public int getLength() {
+ return this.length;
+ }
+
+ /**
+ * Returns the offset at which the change occurred.
+ */
+ public int getOffset() {
+ return this.offset;
+ }
+
+ /**
+ * Returns the element containing the change.
+ */
+ public Element getParentElement() {
+ return this.parentElement;
+ }
+ /**
+ * @return the value of the attribute before the change.
+ * If null, indicates that the attribute was removed.
+ */
+ public String getNewAttributeValue() {
+ return newAttributeValue;
+ }
+
+ /**
+ * @return the value of the attribute after the change.
+ * If null, indicates the attribute did not exist before
+ * the change.
+ */
+ public String getOldAttributeValue() {
+ return oldAttributeValue;
+ }
+
+ /**
+ * @return the name of the attribute that was changed.
+ */
+ public String getAttributeName() {
+ return attributeName;
+ }
+
+ /**
+ * @return the document for which this event was generated
+ */
+ public Document getDocument() {
+ return document;
+ }
+
+ /**
+ * Returns the undoable edit that can be used to undo the action.
+ * May be null, in which case the action cannot be undone.
+ */
+ public IUndoableEdit getUndoableEdit() {
+ return undoableEdit;
+ }
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/dom/DocumentFragment.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/dom/DocumentFragment.java
new file mode 100644
index 0000000..0a72a9c
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/dom/DocumentFragment.java
@@ -0,0 +1,173 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.dom;
+
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.io.Serializable;
+
+/**
+ * Represents a fragment of an XML document.
+ */
+public class DocumentFragment implements Serializable {
+
+ /**
+ * Mime type representing document fragments:
+ * "text/x-vex-document-fragment"
+ */
+ public static final String MIME_TYPE = "application/x-vex-document-fragment";
+
+ private Content content;
+ private Element[] elements;
+
+ /**
+ * Class constructor.
+ *
+ * @param content Content holding the fragment's content.
+ * @param elements Elements that make up this fragment.
+ */
+ public DocumentFragment(Content content, Element[] elements) {
+ this.content = content;
+ this.elements = elements;
+ }
+
+ /**
+ * Returns the Content object holding this fragment's content.
+ */
+ public Content getContent() {
+ return this.content;
+ }
+
+ /**
+ * Returns the number of characters, including sentinels, represented
+ * by the fragment.
+ */
+ public int getLength() {
+ return this.content.getLength();
+ }
+
+ /**
+ * Returns the elements that make up this fragment.
+ */
+ public Element[] getElements() {
+ return this.elements;
+ }
+
+ /**
+ * Returns an array of element names and Validator.PCDATA representing
+ * the content of the fragment.
+ */
+ public String[] getNodeNames() {
+
+ Node[] nodes = this.getNodes();
+ String[] names = new String[nodes.length];
+ for (int i = 0; i < nodes.length; i++) {
+ if (nodes[i] instanceof Text) {
+ names[i] = Validator.PCDATA;
+ } else {
+ names[i] = ((Element) nodes[i]).getName();
+ }
+ }
+
+ return names;
+ }
+
+ /**
+ * Returns the nodes that make up this fragment, including
+ * elements and <code>Text</code> objects.
+ */
+ public Node[] getNodes() {
+ return Document.createNodeArray(
+ this.getContent(),
+ 0,
+ this.getContent().getLength(),
+ this.getElements());
+ }
+
+
+ //======================================================= PRIVATE
+
+ /*
+ * Custom Serialization Methods
+ */
+
+ private void writeObject(ObjectOutputStream out) throws IOException {
+ out.writeUTF(this.content.getString(0, this.content.getLength()));
+ out.writeInt(this.elements.length);
+ for (int i = 0; i < this.elements.length; i++) {
+ this.writeElement(this.elements[i], out);
+ }
+ }
+
+ private void writeElement(Element element, ObjectOutputStream out)
+ throws IOException {
+
+ out.writeObject(element.getName());
+ out.writeInt(element.getStartOffset());
+ out.writeInt(element.getEndOffset());
+ String[] attrNames = element.getAttributeNames();
+ out.writeInt(attrNames.length);
+ for (int i = 0; i < attrNames.length; i++) {
+ out.writeObject(attrNames[i]);
+ out.writeObject(element.getAttribute(attrNames[i]));
+ }
+ Element[] children = element.getChildElements();
+ out.writeInt(children.length);
+ for (int i = 0; i < children.length; i++) {
+ this.writeElement(children[i], out);
+ }
+ }
+
+ private void readObject(ObjectInputStream in)
+ throws IOException, ClassNotFoundException {
+
+ String s = in.readUTF();
+ this.content = new GapContent(s.length());
+ content.insertString(0, s);
+ int n = in.readInt();
+ this.elements = new Element[n];
+ for (int i = 0; i < n; i++) {
+ this.elements[i] = this.readElement(in);
+ }
+ }
+
+ private Element readElement(ObjectInputStream in)
+ throws IOException, ClassNotFoundException {
+
+ String name = (String) in.readObject();
+ int startOffset = in.readInt();
+ int endOffset = in.readInt();
+ Element element = new Element(name);
+ element.setContent(this.content, startOffset, endOffset);
+
+ int attrCount = in.readInt();
+ for (int i = 0; i < attrCount; i++) {
+ String key = (String) in.readObject();
+ String value = (String) in.readObject();
+ try {
+ element.setAttribute(key, value);
+ } catch (DocumentValidationException e) {
+ // Should never happen; there ain't no document
+ e.printStackTrace();
+ }
+ }
+
+ int childCount = in.readInt();
+ for (int i = 0; i < childCount; i++) {
+ Element child = this.readElement(in);
+ child.setParent(element);
+ element.insertChild(i, child);
+ }
+
+ return element;
+ }
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/dom/DocumentListener.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/dom/DocumentListener.java
new file mode 100644
index 0000000..8d771be
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/dom/DocumentListener.java
@@ -0,0 +1,54 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.dom;
+
+/**
+ * Receives notifications of document changes.
+ */
+public interface DocumentListener extends java.util.EventListener {
+
+ /**
+ * Called when an attribute is changed in one of the document's
+ * elements.
+ *
+ * @param e the document event.
+ */
+ public void attributeChanged(DocumentEvent e);
+
+ /**
+ * Called before content is deleted from a document.
+ *
+ * @param e the document event
+ */
+ public void beforeContentDeleted(DocumentEvent e);
+
+ /**
+ * Called before content is inserted into a document.
+ *
+ * @param e the document event
+ */
+ public void beforeContentInserted(DocumentEvent e);
+
+ /**
+ * Called when content is deleted from a document.
+ *
+ * @param e the document event
+ */
+ public void contentDeleted(DocumentEvent e);
+
+ /**
+ * Called when content is inserted into a document.
+ *
+ * @param e the document event
+ */
+ public void contentInserted(DocumentEvent e);
+
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/dom/DocumentReader.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/dom/DocumentReader.java
new file mode 100644
index 0000000..330b56a
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/dom/DocumentReader.java
@@ -0,0 +1,161 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.dom;
+
+import java.io.CharArrayReader;
+import java.io.IOException;
+import java.io.Reader;
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
+import java.net.URL;
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.parsers.SAXParserFactory;
+import org.xml.sax.ContentHandler;
+import org.xml.sax.EntityResolver;
+import org.xml.sax.InputSource;
+import org.xml.sax.SAXException;
+import org.xml.sax.XMLReader;
+import org.xml.sax.ext.LexicalHandler;
+
+
+/**
+ * Class for creating documents given a URL.
+ */
+public class DocumentReader {
+
+
+ /**
+ * Returns the debugging flag.
+ */
+ public boolean isDebugging() {
+ return debugging;
+ }
+
+ /**
+ * Returns the entity resolver for this reader.
+ */
+ public EntityResolver getEntityResolver() {
+ return entityResolver;
+ }
+
+ /**
+ * Returns the whitespace policy factory for this reader.
+ */
+ public IWhitespacePolicyFactory getWhitespacePolicyFactory() {
+ return whitespacePolicyFactory;
+ }
+
+ /**
+ * Reads a document given a URL.
+ *
+ * @param url URL from which to load the document.
+ */
+ public Document read(URL url)
+ throws IOException, ParserConfigurationException, SAXException {
+
+ return read(new InputSource(url.toString()));
+ }
+
+ /**
+ * Reads a document from a string. This is mainly used for short documents
+ * in unit tests.
+ * @param s String containing the document to be read.
+ */
+ public Document read(String s)
+ throws IOException, ParserConfigurationException, SAXException {
+
+ Reader reader = new CharArrayReader(s.toCharArray());
+ return this.read(new InputSource(reader));
+ }
+
+ /**
+ * Reads a document given a SAX InputSource.
+ *
+ * @param is SAX InputSource from which to load the document.
+ */
+ public Document read(InputSource is)
+ throws IOException, ParserConfigurationException, SAXException {
+
+ SAXParserFactory factory = SAXParserFactory.newInstance();
+ factory.setValidating(false); // TODO: experimental--SWT implementation
+ XMLReader xmlReader = factory.newSAXParser().getXMLReader();
+ //xmlReader.setFeature("http://xml.org/sax/features/validation", false);
+ final org.eclipse.wst.xml.vex.core.internal.dom.DocumentBuilder builder =
+ new org.eclipse.wst.xml.vex.core.internal.dom.DocumentBuilder(this.getWhitespacePolicyFactory());
+
+ ContentHandler contentHandler = builder;
+ LexicalHandler lexicalHandler = builder;
+
+ if (this.isDebugging()) {
+ Object proxy = Proxy.newProxyInstance(
+ this.getClass().getClassLoader(),
+ new Class[] { ContentHandler.class, LexicalHandler.class },
+ new InvocationHandler() {
+ public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
+ try {
+ return method.invoke(builder, args);
+ } catch (InvocationTargetException ex) {
+ ex.getCause().printStackTrace();
+ throw ex.getCause();
+ }
+ }
+ });
+
+ contentHandler = (ContentHandler) proxy;
+ lexicalHandler = (LexicalHandler) proxy;
+ }
+
+ xmlReader.setContentHandler(contentHandler);
+ xmlReader.setProperty("http://xml.org/sax/properties/lexical-handler",
+ lexicalHandler);
+ if (this.getEntityResolver() != null) {
+ xmlReader.setEntityResolver(this.getEntityResolver());
+ }
+ xmlReader.parse(is);
+ return builder.getDocument();
+ }
+
+ /**
+ * Sets the debugging flag.
+ * @param debugging true if the component should log debugging info to stdout.
+ */
+ public void setDebugging(boolean debugging) {
+ this.debugging = debugging;
+ }
+
+ /**
+ * Sets the entity resolver for this reader.
+ * @param entityResolver The entityResolver to set.
+ */
+ public void setEntityResolver(EntityResolver entityResolver) {
+ this.entityResolver = entityResolver;
+ }
+
+ /**
+ * Sets the whitespace policy factory for this reader. This factory is used
+ * to obtain a whitespace policy once the public ID of the document is
+ * known.
+ * @param whitespacePolicyFactory The whitespacePolicyFactory to set.
+ */
+ public void setWhitespacePolicyFactory(
+ IWhitespacePolicyFactory whitespacePolicyFactory) {
+ this.whitespacePolicyFactory = whitespacePolicyFactory;
+ }
+
+ //======================================================= PRIVATE
+
+ private boolean debugging;
+ private EntityResolver entityResolver;
+ private IWhitespacePolicyFactory whitespacePolicyFactory;
+
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/dom/DocumentValidationException.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/dom/DocumentValidationException.java
new file mode 100644
index 0000000..c3a008e
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/dom/DocumentValidationException.java
@@ -0,0 +1,30 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.dom;
+
+
+/**
+ * Exception thrown when an change would have compromised document validity.
+ */
+public class DocumentValidationException extends RuntimeException {
+
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * Class constructor.
+ *
+ * @param message Message indicating the nature of the exception.
+ */
+ public DocumentValidationException(String message) {
+ super(message);
+ }
+}
+
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/dom/DocumentWriter.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/dom/DocumentWriter.java
new file mode 100644
index 0000000..45965fe
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/dom/DocumentWriter.java
@@ -0,0 +1,348 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.dom;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.PrintWriter;
+import java.util.Arrays;
+
+/**
+ * Writes a document to an output stream, using a stylesheet to provide
+ * formatting hints.
+ *
+ * <ul>
+ * <li>Children of an element are indented by a configurable amount.</li>
+ * <li>Text is wrapped to fit within a configurable width.<li>
+ * </ul>
+ *
+ * <p>Documents are currently saved UTF-8 encoding, with no encoding
+ * specified in the XML declaration.</p>
+ */
+public class DocumentWriter {
+
+ private IWhitespacePolicy whitespacePolicy;
+ private String indent;
+ private int wrapColumn;
+
+ /**
+ * Class constructor.
+ */
+ public DocumentWriter() {
+ this.indent = " ";
+ this.wrapColumn = 72;
+ }
+
+ /**
+ * Escapes special XML characters. Changes '<', '>', and '&' to
+ * '<', '>' and '&', respectively.
+ *
+ * @param s the string to be escaped.
+ * @return the escaped string
+ */
+ public static String escape(String s) {
+ StringBuffer sb = new StringBuffer(s.length());
+
+ for (int i = 0; i < s.length(); i++) {
+ char c = s.charAt(i);
+ if (c == '<') {
+ sb.append("<");
+ } else if (c == '>') {
+ sb.append(">");
+ } else if (c == '&') {
+ sb.append("&");
+ } else if (c == '"') {
+ sb.append(""");
+ } else if (c == '\'') {
+ sb.append("'");
+ } else {
+ sb.append(c);
+ }
+ }
+ return sb.toString();
+ }
+
+ /**
+ * Returns the indent string. By default this is two spaces.
+ */
+ public String getIndent() {
+ return this.indent;
+ }
+
+ /**
+ * Returns the whitespace policy used by this writer.
+ */
+ public IWhitespacePolicy getWhitespacePolicy() {
+ return whitespacePolicy;
+ }
+
+ /**
+ * Returns the column at which text should be wrapped. By default this
+ * is 72.
+ */
+ public int getWrapColumn() {
+ return this.wrapColumn;
+ }
+
+ /**
+ * Sets the value of the indent string.
+ *
+ * @param indent new value for the indent string.
+ */
+ public void setIndent(String indent) {
+ this.indent = indent;
+ }
+
+ /**
+ * Sets the whitespace policy for this writer. The whitespace policy tells
+ * the writer which elements are block-formatted and which are pre-formatted.
+ *
+ * @param whitespacePolicy The whitespacePolicy to set.
+ */
+ public void setWhitespacePolicy(IWhitespacePolicy whitespacePolicy) {
+ this.whitespacePolicy = whitespacePolicy;
+ }
+
+ /**
+ * Sets the value of the wrap column.
+ *
+ * @param wrapColumn new value for the wrap column.
+ */
+ public void setWrapColumn(int wrapColumn) {
+ this.wrapColumn = wrapColumn;
+ }
+
+ public void write(Document doc, OutputStream os)
+ throws IOException {
+
+ OutputStreamWriter osw = new OutputStreamWriter(os, "UTF-8");
+ PrintWriter pw = new PrintWriter(osw);
+ pw.println("<?xml version='1.0'?>");
+ if (doc.getSystemID() != null) {
+ StringBuffer sb = new StringBuffer();
+ sb.append("<!DOCTYPE ");
+ sb.append(doc.getRootElement().getName());
+ if (doc.getPublicID() != null) {
+ sb.append(" PUBLIC");
+ sb.append(" \"");
+ sb.append(doc.getPublicID());
+ sb.append("\"");
+ } else {
+ sb.append(" SYSTEM");
+ }
+ sb.append(" \"");
+ sb.append(doc.getSystemID());
+ sb.append("\">");
+ pw.println(sb.toString());
+ }
+ this.writeNode(doc.getRootElement(), pw, "");
+ pw.flush();
+ }
+
+
+ //====================================================== PRIVATE
+
+
+ private void writeNode(Node node,
+ PrintWriter pw,
+ String indent) {
+
+ if (node instanceof Text) {
+ TextWrapper wrapper = new TextWrapper();
+ wrapper.add(escape(node.getText()));
+
+ String[] lines = wrapper.wrap(this.wrapColumn - indent.length());
+
+ for (int i = 0; i < lines.length; i++) {
+ pw.print(indent);
+ pw.println(lines[i]);
+ }
+
+ } else {
+
+ Element element = (Element) node;
+
+ if (this.whitespacePolicy != null && this.whitespacePolicy.isPre(element)) {
+ pw.print(indent);
+ writeNodeNoWrap(node, pw);
+ pw.println();
+ return;
+ }
+
+ boolean hasBlockChild = false;
+ Element[] children = element.getChildElements();
+ for (int i = 0; i < children.length; i++) {
+ if (this.whitespacePolicy != null && this.whitespacePolicy.isBlock(children[i])) {
+ hasBlockChild = true;
+ break;
+ }
+ }
+
+ if (hasBlockChild) {
+ pw.print(indent);
+ pw.print("<");
+ pw.print(element.getName());
+
+ TextWrapper wrapper = new TextWrapper();
+ wrapper.addNoSplit(this.getAttributeString(element));
+ int outdent = indent.length() + 1 + element.getName().length();
+ String[] lines = wrapper.wrap(this.wrapColumn - outdent);
+ char[] bigIndent = new char[outdent];
+ Arrays.fill(bigIndent, ' ');
+ for (int i = 0; i < lines.length; i++) {
+ if (i > 0) {
+ pw.print(bigIndent);
+ }
+ pw.print(lines[i]);
+ if (i < lines.length - 1) {
+ pw.println();
+ }
+ }
+ pw.println(">");
+
+ String childIndent = indent + this.indent;
+ Node[] content = element.getChildNodes();
+ for (int i = 0; i < content.length; i++) {
+ this.writeNode(content[i], pw, childIndent);
+ }
+ pw.print(indent);
+ pw.print("</");
+ pw.print(element.getName());
+ pw.println(">");
+ } else {
+ TextWrapper wrapper = new TextWrapper();
+ this.addNode(element, wrapper);
+ String[] lines = wrapper.wrap(this.wrapColumn-indent.length());
+ for (int i = 0; i < lines.length; i++) {
+ pw.print(indent);
+ pw.println(lines[i]);
+ }
+ }
+
+ }
+ }
+
+ private void writeNodeNoWrap(Node node, PrintWriter pw) {
+
+ if (node instanceof Text) {
+ pw.print(escape(node.getText()));
+ } else {
+
+ Element element = (Element) node;
+
+ pw.print("<");
+ pw.print(element.getName());
+ pw.print(this.getAttributeString(element));
+ pw.print(">");
+
+ Node[] content = element.getChildNodes();
+ for (int i = 0; i < content.length; i++) {
+ this.writeNodeNoWrap(content[i], pw);
+ }
+
+ pw.print("</");
+ pw.print(element.getName());
+ pw.print(">");
+ }
+ }
+
+
+ private String attrToString(String name, String value) {
+ StringBuffer sb = new StringBuffer();
+ sb.append(" ");
+ sb.append(name);
+ sb.append("=\"");
+ sb.append(escape(value));
+ sb.append("\"");
+ return sb.toString();
+ }
+
+ private void addNode(Node node, TextWrapper wrapper) {
+ if (node instanceof Text) {
+ wrapper.add(escape(node.getText()));
+ } else {
+ Element element = (Element)node;
+ Node[] content = element.getChildNodes();
+ String[] attrs = element.getAttributeNames();
+ Arrays.sort(attrs);
+
+ if (attrs.length == 0) {
+ if (content.length == 0) {
+ wrapper.add("<" + element.getName() + " />");
+ } else {
+ wrapper.add("<" + element.getName() + ">");
+ }
+ } else {
+ Validator validator = element.getDocument().getValidator();
+ StringBuffer sb = new StringBuffer();
+ for (int i = 0; i < attrs.length; i++) {
+ sb.setLength(0);
+ if (i == 0) {
+ sb.append("<" + element.getName());
+ }
+ if (!attrHasDefaultValue(validator, element, attrs[i])) {
+ sb.append(attrToString(attrs[i], element.getAttribute(attrs[i])));
+ }
+ if (i == attrs.length - 1) {
+ if (content.length == 0) {
+ sb.append("/>");
+ } else {
+ sb.append(">");
+ }
+ }
+ wrapper.addNoSplit(sb.toString());
+ }
+ }
+
+ for (int i = 0; i < content.length; i++) {
+ addNode(content[i], wrapper);
+ }
+
+ if (content.length > 0) {
+ wrapper.add("</" + element.getName() + ">");
+ }
+ }
+ }
+
+ private String getAttributeString(Element element) {
+
+ Validator validator = element.getDocument().getValidator();
+
+ String[] attrs = element.getAttributeNames();
+ Arrays.sort(attrs);
+ StringBuffer sb = new StringBuffer();
+ for (int i = 0; i < attrs.length; i++) {
+ if (attrHasDefaultValue(validator, element, attrs[i])) {
+ continue;
+ }
+ sb.append(" ");
+ sb.append(attrs[i]);
+ sb.append("=\"");
+ sb.append(escape(element.getAttribute(attrs[i])));
+ sb.append("\"");
+ }
+ return sb.toString();
+ }
+
+ private static boolean attrHasDefaultValue(Validator validator, Element element, String attribute) {
+ if (validator != null) {
+ AttributeDefinition ad = validator.getAttributeDefinition(element.getName(), attribute);
+ if (ad != null) {
+ String value = element.getAttribute(attribute);
+ String defaultValue = ad.getDefaultValue();
+ return value != null && value.equals(defaultValue);
+ }
+ }
+ return false;
+ }
+}
+
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/dom/Element.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/dom/Element.java
new file mode 100644
index 0000000..ee45709
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/dom/Element.java
@@ -0,0 +1,324 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.dom;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+import org.eclipse.wst.xml.vex.core.internal.undo.CannotRedoException;
+import org.eclipse.wst.xml.vex.core.internal.undo.CannotUndoException;
+import org.eclipse.wst.xml.vex.core.internal.undo.IUndoableEdit;
+
+
+/**
+ * <code>Element</code> represents a tag in an XML document. Methods
+ * are available for managing the element's attributes and children.
+ */
+public class Element extends Node implements Cloneable {
+
+ private String name;
+ private Element parent = null;
+ private List children = new ArrayList();
+ private Map attributes = new HashMap();
+
+
+
+ /**
+ * Class constructor.
+ * @param name element name
+ */
+ public Element(String name) {
+ this.name = name;
+ }
+
+ /**
+ * Adds the given child to the end of the child list.
+ * Sets the parent attribute of the given element to this element.
+ */
+ public void addChild(Element child) {
+ this.children.add(child);
+ child.parent = this;
+ }
+
+
+ /**
+ * Clones the element and its attributes. The returned element has
+ * no parent or children.
+ */
+ public Object clone() {
+ try {
+ Element element = new Element(this.getName());
+ for (Iterator it = this.attributes.keySet().iterator(); it
+ .hasNext();) {
+ String attrName = (String) it.next();
+ element.setAttribute(attrName, (String) this.attributes
+ .get(attrName));
+ }
+ return element;
+
+ } catch (DocumentValidationException ex) {
+ ex.printStackTrace();
+ return null;
+ }
+ }
+
+ /**
+ * Returns the value of an attribute given its name. If no such
+ * attribute exists, returns null.
+ *
+ * @param name Name of the attribute.
+ */
+ public String getAttribute(String name) {
+ return (String) attributes.get(name);
+ }
+
+ /**
+ * Returns an array of names of the attributes in the element.
+ */
+ public String[] getAttributeNames() {
+ Collection names = this.attributes.keySet();
+ return (String[]) names.toArray(new String[names.size()]);
+ }
+
+ /**
+ * Returns an iterator over the children. Used by
+ * <code>Document.delete</code> to safely delete children.
+ */
+ public Iterator getChildIterator() {
+ return this.children.iterator();
+ }
+
+ /**
+ * Returns an array of the elements children.
+ */
+ public Element[] getChildElements() {
+ int size = this.children.size();
+ return (Element[]) this.children.toArray(new Element[size]);
+ }
+
+ /**
+ * Returns an array of nodes representing the content of this element.
+ * The array includes child elements and runs of text returned as
+ * <code>Text</code> objects.
+ */
+ public Node[] getChildNodes() {
+ return Document.createNodeArray(
+ this.getContent(),
+ this.getStartOffset() + 1,
+ this.getEndOffset(),
+ this.getChildElements());
+ }
+
+ /**
+ * @return The document to which this element belongs.
+ * Returns null if this element is part of a document
+ * fragment.
+ */
+ public Document getDocument() {
+ Element root = this;
+ while (root.getParent() != null) {
+ root = root.getParent();
+ }
+ if (root instanceof RootElement) {
+ return ((RootElement) root).getDocument();
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Returns the name of the element.
+ */
+ public String getName() {
+ return this.name;
+ }
+
+ /**
+ * Returns the parent of this element, or null if this is the root element.
+ */
+ public Element getParent() {
+ return this.parent;
+ }
+
+
+ public String getText() {
+ String s = super.getText();
+ StringBuffer sb = new StringBuffer(s.length());
+ for (int i = 0; i < s.length(); i++) {
+ char c = s.charAt(i);
+ if (c != 0) {
+ sb.append(c);
+ }
+ }
+ return sb.toString();
+ }
+
+ /**
+ * Inserts the given element as a child at the given child index.
+ * Sets the parent attribute of the given element to this element.
+ */
+ void insertChild(int index, Element child) {
+ this.children.add(index, child);
+ child.parent = this;
+ }
+
+ /**
+ * Returns true if the element has no content.
+ */
+ public boolean isEmpty() {
+ return this.getStartOffset() + 1 == this.getEndOffset();
+ }
+
+ /**
+ * Removes the given attribute from the array.
+ *
+ * @param name name of the attribute to remove.
+ */
+ public void removeAttribute(String name)
+ throws DocumentValidationException {
+
+ String oldValue = this.getAttribute(name);
+ String newValue = null;
+ if (oldValue != null) {
+ this.attributes.remove(name);
+ }
+ Document doc = this.getDocument();
+ if (doc != null) { // doc may be null, e.g. when we're cloning an element
+ // to produce a document fragment
+
+ IUndoableEdit edit = doc.isUndoEnabled() ?
+ new AttributeChangeEdit(name, oldValue, newValue) : null;
+
+ doc.fireAttributeChanged(new DocumentEvent(
+ doc, this, name, oldValue, newValue, edit));
+ }
+ }
+
+ /**
+ * Sets the value of an attribute for this element.
+ *
+ * @param name Name of the attribute to be set.
+ * @param value New value for the attribute. If null, this call
+ * has the same effect as removeAttribute(name).
+ */
+ public void setAttribute(String name, String value)
+ throws DocumentValidationException {
+
+ String oldValue = this.getAttribute(name);
+
+ if (value == null && oldValue == null) {
+ return;
+ } else if (value == null) {
+ this.removeAttribute(name);
+ } else if (value.equals(oldValue)) {
+ return;
+ } else {
+ this.attributes.put(name, value);
+ Document doc = this.getDocument();
+ if (doc != null) { // doc may be null, e.g. when we're cloning an element
+ // to produce a document fragment
+
+ IUndoableEdit edit = doc.isUndoEnabled() ?
+ new AttributeChangeEdit(name, oldValue, value) : null;
+
+ doc.fireAttributeChanged(new DocumentEvent(
+ doc, this, name, oldValue, value, edit));
+ }
+ }
+
+
+ }
+
+ /**
+ * Sets the parent of this element.
+ *
+ * @param parent Parent element.
+ */
+ public void setParent(Element parent) {
+ this.parent = parent;
+ }
+
+ public String toString() {
+
+ StringBuffer sb = new StringBuffer();
+ sb.append("<");
+ sb.append(this.getName());
+ String[] attrs = this.getAttributeNames();
+
+ for (int i = 0; i < attrs.length; i++) {
+ if (i > 0) {
+ sb.append(",");
+ }
+ sb.append(" ");
+ sb.append(attrs[i]);
+ sb.append("=\"");
+ sb.append(this.getAttribute(attrs[i]));
+ sb.append("\"");
+ }
+
+ sb.append("> (");
+ sb.append(this.getStartPosition());
+ sb.append(",");
+ sb.append(this.getEndPosition());
+ sb.append(")");
+
+ return sb.toString();
+ }
+
+ //========================================================= PRIVATE
+
+ private class AttributeChangeEdit implements IUndoableEdit {
+
+ private String name;
+ private String oldValue;
+ private String newValue;
+
+ public AttributeChangeEdit(String name, String oldValue, String newValue) {
+ this.name = name;
+ this.oldValue = oldValue;
+ this.newValue = newValue;
+ }
+
+ public boolean combine(IUndoableEdit edit) {
+ return false;
+ }
+
+ public void undo() throws CannotUndoException {
+ Document doc = getDocument();
+ try {
+ doc.setUndoEnabled(false);
+ setAttribute(name, oldValue);
+ } catch (DocumentValidationException ex) {
+ throw new CannotUndoException();
+ } finally {
+ doc.setUndoEnabled(true);
+ }
+ }
+
+ public void redo() throws CannotRedoException {
+ Document doc = getDocument();
+ try {
+ doc.setUndoEnabled(false);
+ setAttribute(name, newValue);
+ } catch (DocumentValidationException ex) {
+ throw new CannotUndoException();
+ } finally {
+ doc.setUndoEnabled(true);
+ }
+ }
+ }
+
+}
+
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/dom/GapContent.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/dom/GapContent.java
new file mode 100644
index 0000000..835d897
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/dom/GapContent.java
@@ -0,0 +1,270 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.dom;
+
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+
+/**
+ * Implementation of the <code>Content</code> interface that manages
+ * changes efficiently. Implements a buffer that keeps its free space
+ * (the "gap") at the location of the last change. Insertions at the
+ * start of the gap require no other chars to be moved so long as the
+ * insertion is smaller than the gap. Deletions that end of the gap
+ * are also very efficent. Furthermore, changes near the gap require
+ * relatively few characters to be moved.
+ */
+public class GapContent implements Content {
+
+ private char[] content;
+ private int gapStart;
+ private int gapEnd;
+ private final Map positions = new HashMap();
+
+ /**
+ * Class constructor.
+ *
+ * @param initialCapacity initial capacity of the content.
+ */
+ public GapContent(int initialCapacity) {
+
+ assertPositive(initialCapacity);
+
+ this.content = new char[initialCapacity];
+ this.gapStart = 0;
+ this.gapEnd = initialCapacity;
+ }
+
+ /**
+ * Creates a new Position object at the given initial offset.
+ *
+ * @param offset initial offset of the position
+ */
+ public Position createPosition(int offset) {
+
+ assertOffset(offset, 0, this.getLength());
+
+ Position pos = new GapContentPosition(offset);
+ this.positions.put(pos, pos);
+
+ return pos;
+ }
+
+ /**
+ * Insert a string into the content.
+ *
+ * @param offset Offset at which to insert the string.
+ * @param s String to insert.
+ */
+ public void insertString(int offset, String s) {
+
+ assertOffset(offset, 0, this.getLength());
+
+ if (s.length() > (this.gapEnd - this.gapStart)) {
+ this.expandContent(this.getLength() + s.length());
+ }
+
+ //
+ // Optimization: no need to update positions if we're inserting
+ // after existing content (offset == this.getLength()) and if
+ // we don't have to move the gap to do it (offset == gapStart).
+ //
+ // This significantly improves document load speed.
+ //
+ boolean atEnd = (offset == this.getLength() && offset == gapStart);
+
+ this.moveGap(offset);
+ s.getChars(0, s.length(), this.content, offset);
+ this.gapStart += s.length();
+
+ if (!atEnd) {
+ //
+ // Update positions
+ //
+ for (Iterator i = this.positions.keySet().iterator(); i.hasNext(); ) {
+ GapContentPosition pos = (GapContentPosition) i.next();
+ if (pos.getOffset() >= offset) {
+ pos.setOffset(pos.getOffset() + s.length());
+ }
+ }
+ }
+ }
+
+
+ /**
+ * Deletes the given range of characters.
+ *
+ * @param offset Offset from which characters should be deleted.
+ * @param length Number of characters to delete.
+ */
+ public void remove(int offset, int length) {
+
+ assertOffset(offset, 0, this.getLength() - length);
+ assertPositive(length);
+
+ this.moveGap(offset + length);
+ this.gapStart -= length;
+
+ for (Iterator i = this.positions.keySet().iterator(); i.hasNext(); ) {
+ GapContentPosition pos = (GapContentPosition) i.next();
+ if (pos.getOffset() >= offset + length) {
+ pos.setOffset(pos.getOffset() - length);
+ } else if (pos.getOffset() >= offset) {
+ pos.setOffset(offset);
+ }
+ }
+ }
+
+ /**
+ * Gets a substring of the content.
+ *
+ * @param offset Offset at which the string begins.
+ * @param length Number of characters to return.
+ */
+ public String getString(int offset, int length) {
+
+ assertOffset(offset, 0, this.getLength() - length);
+ assertPositive(length);
+
+ if (offset + length <= this.gapStart) {
+ return new String(this.content,
+ offset,
+ length);
+ } else if (offset >= this.gapStart) {
+ return new String(this.content,
+ offset - this.gapStart + this.gapEnd,
+ length);
+ } else {
+ StringBuffer sb = new StringBuffer(length);
+ sb.append(this.content,
+ offset,
+ this.gapStart - offset);
+ sb.append(this.content,
+ this.gapEnd,
+ offset + length - this.gapStart);
+ return sb.toString();
+ }
+ }
+
+ /**
+ * Return the length of the content.
+ */
+ public int getLength() {
+ return this.content.length - (this.gapEnd - this.gapStart);
+ }
+
+ //====================================================== PRIVATE
+
+ private static final int GROWTH_SLOWDOWN_SIZE = 100000;
+ private static final int GROWTH_RATE_FAST = 2;
+ private static final float GROWTH_RATE_SLOW = 1.1f;
+
+ /**
+ * Implementation of the Position interface.
+ */
+ private static class GapContentPosition implements Position {
+
+ private int offset;
+
+ public GapContentPosition(int offset) {
+ this.offset = offset;
+ }
+
+ public int getOffset() {
+ return this.offset;
+ }
+
+ public void setOffset(int offset) {
+ this.offset = offset;
+ }
+
+ public String toString() {
+ return Integer.toString(this.offset);
+ }
+ }
+
+ /**
+ * Assert that the given offset is within the given range,
+ * throwing IllegalArgumentException if not.
+ */
+ private static void assertOffset(int offset, int min, int max) {
+ if (offset < min || offset > max) {
+ throw new IllegalArgumentException("Bad offset " + offset +
+ "must be between " + min +
+ " and " + max);
+ }
+ }
+
+ /**
+ * Assert that the given value is zero or positive.
+ * throwing IllegalArgumentException if not.
+ */
+ private static void assertPositive(int value) {
+ if (value < 0) {
+ throw new IllegalArgumentException("Value should be zero or positive, but it was " + value);
+ }
+ }
+
+ /**
+ * Expand the content array to fit at least the given length.
+ */
+ private void expandContent(int newLength) {
+
+ // grow quickly when small, slower when large
+
+ int newCapacity;
+
+ if (newLength < GROWTH_SLOWDOWN_SIZE) {
+ newCapacity = Math.max((int) (newLength * GROWTH_RATE_FAST), 32);
+ } else {
+ newCapacity = (int)(newLength * GROWTH_RATE_SLOW);
+ }
+
+ char[] newContent = new char[newCapacity];
+
+ System.arraycopy(this.content, 0,
+ newContent, 0,
+ this.gapStart);
+
+ int tailLength = this.content.length - this.gapEnd;
+ System.arraycopy(this.content, this.gapEnd,
+ newContent, newCapacity - tailLength,
+ tailLength);
+
+ this.content = newContent;
+ this.gapEnd = newCapacity - tailLength;
+ }
+
+ /**
+ * Move the gap to the given offset.
+ */
+ private void moveGap(int offset) {
+
+ assertOffset(offset, 0, this.getLength());
+
+ if (offset <= this.gapStart) {
+ int length = this.gapStart - offset;
+ System.arraycopy(this.content, offset,
+ this.content, this.gapEnd - length,
+ length);
+ this.gapStart -= length;
+ this.gapEnd -= length;
+ } else {
+ int length = offset - this.gapStart;
+ System.arraycopy(this.content, this.gapEnd,
+ this.content, this.gapStart,
+ length);
+ this.gapStart += length;
+ this.gapEnd += length;
+ }
+ }
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/dom/IWhitespacePolicy.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/dom/IWhitespacePolicy.java
new file mode 100644
index 0000000..aceeae3
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/dom/IWhitespacePolicy.java
@@ -0,0 +1,33 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.dom;
+
+
+/**
+ * Determines whitespace policy for document elements. For example, a CSS
+ * stylesheet implements a whitespace policy via its display and white-space
+ * properties.
+ */
+public interface IWhitespacePolicy {
+
+ /**
+ * Returns true if the given element is normally block-formatted.
+ * @param element Element to test.
+ */
+ public boolean isBlock(Element element);
+
+ /**
+ * Returns true if the given element is pre-formatted, that is, all of
+ * its contained whitespace should be preserved.
+ * @param element Element to test.
+ */
+ public boolean isPre(Element element);
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/dom/IWhitespacePolicyFactory.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/dom/IWhitespacePolicyFactory.java
new file mode 100644
index 0000000..f4772f5
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/dom/IWhitespacePolicyFactory.java
@@ -0,0 +1,27 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.dom;
+
+
+/**
+ * Factory for returning a WhitespacePolicy object given a document type
+ * public ID. This is required by DocumentBuilder, since we don't know what
+ * WhitespacePolicy we need before we begin parsing the document.
+ */
+public interface IWhitespacePolicyFactory {
+
+ /**
+ * Return a WhitespacePolicy for documents with the given public ID.
+ * @param publicId Public ID of the document type associated with
+ * the document.
+ */
+ public IWhitespacePolicy getPolicy(String publicId);
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/dom/Node.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/dom/Node.java
new file mode 100644
index 0000000..7496ea7
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/dom/Node.java
@@ -0,0 +1,95 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.dom;
+
+/**
+ * <code>Node</code> represents a component of an XML document. .
+ */
+public class Node {
+
+ private Content content = null;
+ private Position start = null;
+ private Position end = null;
+
+ /**
+ * Class constructor.
+ */
+ public Node() {
+ }
+
+
+ /**
+ * Returns the document associated with this node. Null if the node
+ * has not yet been inserted into a document.
+ */
+ public Content getContent() {
+ return this.content;
+ }
+
+ /**
+ * Returns the character offset corresponding to the end of the
+ * node.
+ */
+ public int getEndOffset() {
+ return this.end.getOffset();
+ }
+
+ /**
+ * Returns the <code>Position</code> corresponding to the end of
+ * the node.
+ */
+ public Position getEndPosition() {
+ return this.end;
+ }
+
+ /**
+ * Returns the character offset corresponding to the start of the
+ * node.
+ */
+ public int getStartOffset() {
+ return this.start.getOffset();
+ }
+
+ /**
+ * Returns the <code>Position</code> corresponding to the start of
+ * the node.
+ */
+ public Position getStartPosition() {
+ return this.start;
+ }
+
+ /**
+ * Returns the text contained by this node. If this node is an element,
+ * the text in all child nodes is included.
+ */
+ public String getText() {
+ return this.content.getString(this.getStartOffset(),
+ this.getEndOffset() - this.getStartOffset());
+ }
+
+ /**
+ * Sets the content of the node
+ *
+ * @param content Content object holding the node's content
+ * @param startOffset offset at which the node's content starts
+ * @param endOffset offset at which the node's content ends
+ */
+ void setContent(Content content,
+ int startOffset,
+ int endOffset) {
+
+ this.content = content;
+ this.start = content.createPosition(startOffset);
+ this.end = content.createPosition(endOffset);
+ }
+
+}
+
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/dom/Position.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/dom/Position.java
new file mode 100644
index 0000000..34937e0
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/dom/Position.java
@@ -0,0 +1,24 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.dom;
+
+/**
+ * Represents a logical location in a document. As the document is
+ * modified, existing <code>Position</code> objects are updated to
+ * reflect the appropriate character offset in the document.
+ */
+public interface Position {
+
+ /**
+ * Returns the character offset corresponding to the position.
+ */
+ public int getOffset();
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/dom/RootElement.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/dom/RootElement.java
new file mode 100644
index 0000000..ec66d27
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/dom/RootElement.java
@@ -0,0 +1,50 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.dom;
+
+/**
+ * The root element of a document. Keeps track of the document to which
+ * it is associated. Any element can find the document to which it is
+ * associated by following its parents to this root. This would be done,
+ * for example, to notify document listeners that the document has changed
+ * when the element changes.
+ */
+public class RootElement extends Element {
+
+ private Document document;
+
+ /**
+ * Class constructor
+ * @param name Name of the element.
+ */
+ public RootElement(String name) {
+ super(name);
+ }
+
+ /**
+ * @return The document associated with this element.
+ */
+ public Document getDocument() {
+ return document;
+ }
+
+ /**
+ * Sets the document to which this element is associated.
+ * This is called by the document constructor, so it need not
+ * be called by client code.
+ * @param document Document to which this root element is
+ * associated.
+ */
+ public void setDocument(Document document) {
+ this.document = document;
+ }
+
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/dom/Text.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/dom/Text.java
new file mode 100644
index 0000000..9a483ad
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/dom/Text.java
@@ -0,0 +1,32 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.dom;
+
+/**
+ * <code>Text</code> represents a run of text in a document. Text
+ * objects are not used in the internal document structure; they are
+ * only returned as needed by the <code>Element.getContent</code>
+ * method.
+ */
+public class Text extends Node {
+
+ /**
+ * Class constructor.
+ *
+ * @param content Content object containing the text
+ * @param startOffset character offset of the start of the run
+ * @param endOffset character offset of the end of the run
+ */
+ public Text(Content content, int startOffset, int endOffset) {
+ this.setContent(content, startOffset, endOffset);
+ }
+
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/dom/TextWrapper.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/dom/TextWrapper.java
new file mode 100644
index 0000000..29f0aae
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/dom/TextWrapper.java
@@ -0,0 +1,125 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.dom;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * Wraps text to a given width.
+ */
+public class TextWrapper {
+
+ private List parts = new ArrayList();
+
+ private boolean lastIsWhite = true;
+
+ /**
+ * Class constructor.
+ */
+ public TextWrapper() {
+ }
+
+ /**
+ * Adds text to the list of things to be wrapped.
+ *
+ * @param s Text to be added.
+ */
+ public void add(String s) {
+ int i = 0;
+ int j = 0;
+ boolean thisIsWhite = true;
+ while (j < s.length()) {
+
+ // skip non-whitespace
+ while (j < s.length() && !Character.isWhitespace(s.charAt(j))) {
+ j++;
+ thisIsWhite = false;
+ }
+
+ // skip whitespace
+ while (j < s.length() && Character.isWhitespace(s.charAt(j))) {
+ j++;
+ thisIsWhite = true;
+ }
+
+ if (lastIsWhite)
+ this.parts.add(s.substring(i, j));
+ else
+ this.parts.add(((String)this.parts.remove(this.parts.size()-1)) +
+ s.substring(i, j));
+ i = j;
+ lastIsWhite = thisIsWhite;
+ }
+ }
+
+ /**
+ * Adds text to the list of things to be wrapped. The given text
+ * will be treated as a single unit and will not be split across
+ * lines.
+ *
+ * @param s Text to be added.
+ */
+ public void addNoSplit(String s) {
+ this.parts.add(s);
+ }
+
+ /**
+ * Clears any added text.
+ */
+ public void clear() {
+ this.parts.clear();
+ }
+
+ /**
+ * Wraps the text into the given width. The text is only
+ * broken at spaces, meaning the returned lines will not
+ * necessarily fit within width.
+ *
+ * @param width
+ */
+ public String[] wrap(int width) {
+ List lines = new ArrayList();
+ StringBuffer line = new StringBuffer();
+
+ Iterator iter = this.parts.iterator();
+ while (iter.hasNext()) {
+ String s = (String) iter.next();
+ if (line.length() > 0 &&
+ line.length() + s.length() > width) {
+ // part won't fit on the current line
+ lines.add(line.toString());
+ line.setLength(0);
+
+ if (s.length() > width) {
+ lines.add(s);
+ } else {
+ line.append(s);
+ }
+ } else {
+ line.append(s);
+ }
+ }
+
+ if (line.length() > 0) {
+ lines.add(line.toString());
+ }
+
+ return (String[]) lines.toArray(new String[lines.size()]);
+ }
+
+
+ //====================================================== PRIVATE
+
+
+}
+
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/dom/Validator.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/dom/Validator.java
new file mode 100644
index 0000000..ec6446a
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/dom/Validator.java
@@ -0,0 +1,93 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.dom;
+
+import java.io.Serializable;
+import java.util.Set;
+
+/**
+ * Represents an object that can validate the structure of a document.
+ * Validators must be serializable.
+ */
+public interface Validator extends Serializable {
+
+ /**
+ * String indicating that character data is allowed at the given
+ * point in the document.
+ */
+ public static final String PCDATA = "#PCDATA";
+
+ /**
+ * Returns the AttributeDefinition for a particular attribute.
+ * @param element Name of the element.
+ * @param attribute Name of the attribute.
+ */
+ public AttributeDefinition getAttributeDefinition(String element, String attribute);
+
+ /**
+ * Returns the attribute definitions that apply to the given element.
+ * @param element Name of the element to check.
+ */
+ public AttributeDefinition[] getAttributeDefinitions(String element);
+
+ /**
+ * Returns a set of Strings representing valid root elements for the
+ * given document type.
+ */
+ public Set getValidRootElements();
+
+ /**
+ * Returns a set of Strings representing items that are valid at
+ * point in the child nodes of a given element. Each string is either
+ * an element name or Validator.PCDATA.
+ *
+ * @param element Name of the parent element.
+ * @param prefix Array of strings representing nodes coming before the
+ * insertion point
+ * @param suffix Array of strings representing nodes coming after the
+ * insertion point
+ */
+ public Set getValidItems(String element, String[] prefix, String[] suffix);
+
+ /**
+ * Returns true if the given sequence is valid for the given element.
+ * Accepts three sequences, which will be concatenated before doing
+ * the check.
+ *
+ * @param element Name of the element being tested.
+ * @param nodes Array of element names and Validator.PCDATA.
+ * @param partial If true, an valid but incomplete sequence is acceptable.
+ */
+ public boolean isValidSequence(
+ String element,
+ String[] nodes,
+ boolean partial);
+
+ /**
+ * Returns true if the given sequence is valid for the given element.
+ * Accepts three sequences, which will be concatenated before doing
+ * the check.
+ *
+ * @param element Name of the element being tested.
+ * @param seq1 Array of element names and Validator.PCDATA.
+ * @param seq2 Array of element names and Validator.PCDATA. May be null or empty.
+ * @param seq3 Array of element names and Validator.PCDATA. May be null or empty.
+ * @param partial If true, an valid but incomplete sequence is acceptable.
+ */
+ public boolean isValidSequence(
+ String element,
+ String[] seq1,
+ String[] seq2,
+ String[] seq3,
+ boolean partial);
+
+}
+
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/dom/package.html b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/dom/package.html
new file mode 100644
index 0000000..2eca216
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/dom/package.html
@@ -0,0 +1,22 @@
+<?xml version='1.0'?>
+<html>
+
+ <head>
+
+ <title>net.sf.vex.dom</title>
+
+ </head>
+
+ <body>
+
+ <p>Classes implementing an object model for XML documents. The
+ classes in this package are designed to be similar to those in the
+ JDOM library. However, this package is unique in that content is
+ represented by one single string of characters for the entire
+ document. Implementing the model in this way simplifies the editor
+ implementation.</p>
+
+ </body>
+
+</html>
+
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/layout/AbstractBlockBox.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/layout/AbstractBlockBox.java
new file mode 100644
index 0000000..71c2b6d
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/layout/AbstractBlockBox.java
@@ -0,0 +1,922 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.layout;
+
+import java.util.ArrayList;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Set;
+
+import org.eclipse.wst.xml.vex.core.internal.core.Caret;
+import org.eclipse.wst.xml.vex.core.internal.core.Color;
+import org.eclipse.wst.xml.vex.core.internal.core.ColorResource;
+import org.eclipse.wst.xml.vex.core.internal.core.FontMetrics;
+import org.eclipse.wst.xml.vex.core.internal.core.Graphics;
+import org.eclipse.wst.xml.vex.core.internal.core.Insets;
+import org.eclipse.wst.xml.vex.core.internal.core.IntRange;
+import org.eclipse.wst.xml.vex.core.internal.css.CSS;
+import org.eclipse.wst.xml.vex.core.internal.css.StyleSheet;
+import org.eclipse.wst.xml.vex.core.internal.css.Styles;
+import org.eclipse.wst.xml.vex.core.internal.dom.Document;
+import org.eclipse.wst.xml.vex.core.internal.dom.Element;
+import org.eclipse.wst.xml.vex.core.internal.dom.Position;
+
+
+
+/**
+ * Base class of block boxes that can contain other block boxes. This class
+ * implements the layout method and various navigation methods. Subclasses must
+ * implement the createChildren method.
+ *
+ * Subclasses can be anonymous or non-anonymous (i.e. generated by an element).
+ * Since the vast majority of instances will be non-anonymous, this class can
+ * manage the element and top and bottom margins without too much inefficiency.
+ *
+ * <p>Subclasses that can be anonymous must override the getStartPosition and
+ * getEndPosition classes to return the range covered by the box.</p>
+ */
+public abstract class AbstractBlockBox extends AbstractBox implements BlockBox {
+
+
+ /**
+ * Class constructor for non-anonymous boxes.
+ *
+ * @param context LayoutContext being used.
+ * @param parent Parent box.
+ * @param element Element associated with this box.
+ * anonymous box.
+ */
+ public AbstractBlockBox(LayoutContext context, BlockBox parent, Element element) {
+
+ this.parent = parent;
+ this.element = element;
+
+ Styles styles = context.getStyleSheet().getStyles(element);
+ int parentWidth = parent.getWidth();
+ this.marginTop = styles.getMarginTop().get(parentWidth);
+ this.marginBottom = styles.getMarginBottom().get(parentWidth);
+
+ }
+
+ /**
+ * Class constructor for anonymous boxes.
+ *
+ * @param context LayoutContext to use.
+ * @param parent Parent box.
+ * @param startOffset Start of the range covered by the box.
+ * @param endOffset End of the range covered by the box.
+ */
+ public AbstractBlockBox(LayoutContext context, BlockBox parent, int startOffset, int endOffset) {
+ this.parent = parent;
+ this.marginTop = 0;
+ this.marginBottom = 0;
+
+ Document doc = context.getDocument();
+ this.startPosition = doc.createPosition(startOffset);
+ this.endPosition = doc.createPosition(endOffset);
+ }
+
+ /**
+ * Walks the box tree and returns the nearest enclosing element.
+ */
+ protected Element findContainingElement() {
+ BlockBox box = this;
+ Element element = box.getElement();
+ while (element == null) {
+ box = box.getParent();
+ element = box.getElement();
+ }
+ return element;
+ }
+
+ /**
+ * Returns this box's children as an array of BlockBoxes.
+ */
+ protected BlockBox[] getBlockChildren() {
+ return (BlockBox[]) this.getChildren();
+ }
+
+
+ public Caret getCaret(LayoutContext context, int offset) {
+
+ // If we haven't yet laid out this block, estimate the caret.
+ if (this.getLayoutState() != LAYOUT_OK) {
+ int relative = offset - this.getStartOffset();
+ int size = this.getEndOffset() - this.getStartOffset();
+ int y = 0;
+ if (size > 0) {
+ y = this.getHeight() * relative / size;
+ }
+ return new HCaret(0, y, this.getHCaretWidth());
+ }
+
+ int y;
+
+ Box[] children = this.getContentChildren();
+ for (int i = 0; i < children.length; i++) {
+
+ if (offset < children[i].getStartOffset()) {
+ if (i > 0) {
+ y = (children[i-1].getY() + children[i-1].getHeight() + children[i].getY()) / 2;
+ } else {
+ y = 0;
+ }
+ return new HCaret(0, y, this.getHCaretWidth());
+ }
+
+ if (offset >= children[i].getStartOffset()
+ && offset <= children[i].getEndOffset()) {
+
+ Caret caret = children[i].getCaret(context, offset);
+ caret.translate(children[i].getX(), children[i].getY());
+ return caret;
+ }
+ }
+
+ if (this.hasChildren()) {
+ y = this.getHeight();
+ } else {
+ y = this.getHeight() / 2;
+ }
+
+ return new HCaret(0, y, this.getHCaretWidth());
+ }
+
+
+
+ public Box[] getChildren() {
+ return this.children;
+ }
+
+ /**
+ * Return an array of children that contain content.
+ */
+ protected BlockBox[] getContentChildren() {
+ Box[] children = this.getChildren();
+ List contentChildren = new ArrayList(children.length);
+ for (int i = 0; i < children.length; i++) {
+ if (children[i].hasContent()) {
+ contentChildren.add(children[i]);
+ }
+ }
+ return (BlockBox[]) contentChildren.toArray(new BlockBox[contentChildren.size()]);
+ }
+
+ public Element getElement() {
+ return this.element;
+ }
+
+ public int getEndOffset() {
+ Element element = this.getElement();
+ if (element != null) {
+ return element.getEndOffset();
+ } else if (this.getEndPosition() != null) {
+ return this.getEndPosition().getOffset();
+ } else {
+ throw new IllegalStateException();
+ }
+ }
+
+
+ /**
+ * Returns the estimated size of the box, based on the the current font
+ * size and the number of characters covered by the box. This is a utility
+ * method that can be used in implementation of setInitialSize. It assumes
+ * the width of the box has already been correctly set.
+ *
+ * @param context LayoutContext to use.
+ */
+ protected int getEstimatedHeight(LayoutContext context) {
+
+ Element element = this.findContainingElement();
+ Styles styles = context.getStyleSheet().getStyles(element);
+ int charCount = this.getEndOffset() - this.getStartOffset();
+
+ float fontSize = styles.getFontSize();
+ float lineHeight = styles.getLineHeight();
+ float estHeight = lineHeight * fontSize * 0.6f * charCount / this.getWidth();
+
+ return Math.round(Math.max(estHeight, lineHeight));
+ }
+
+ public LineBox getFirstLine() {
+ if (this.hasChildren()) {
+ BlockBox firstChild = (BlockBox) this.getChildren()[0];
+ return firstChild.getFirstLine();
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Returns the width of the horizontal caret. This is overridden
+ * by TableBox to return a caret that is the full width of the table.
+ */
+ protected int getHCaretWidth() {
+ return H_CARET_LENGTH;
+ }
+
+ public Insets getInsets(LayoutContext context, int containerWidth) {
+
+ if (this.getElement() != null) {
+ Styles styles = context.getStyleSheet().getStyles(this.getElement());
+
+ int top = this.marginTop
+ + styles.getBorderTopWidth()
+ + styles.getPaddingTop().get(containerWidth);
+
+ int left = styles.getMarginLeft().get(containerWidth)
+ + styles.getBorderLeftWidth()
+ + styles.getPaddingLeft().get(containerWidth);
+
+ int bottom = this.marginBottom
+ + styles.getBorderBottomWidth()
+ + styles.getPaddingBottom().get(containerWidth);
+
+ int right = styles.getMarginRight().get(containerWidth)
+ + styles.getBorderRightWidth()
+ + styles.getPaddingRight().get(containerWidth);
+
+ return new Insets(top, left, bottom, right);
+ } else {
+ return new Insets(this.marginTop, 0, this.marginBottom, 0);
+ }
+ }
+
+ public LineBox getLastLine() {
+ if (this.hasChildren()) {
+ BlockBox lastChild = (BlockBox) this.getChildren()[this.getChildren().length - 1];
+ return lastChild.getLastLine();
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Returns the layout state of this box.
+ */
+ protected byte getLayoutState() {
+ return this.layoutState;
+ }
+
+ public int getLineEndOffset(int offset) {
+ BlockBox[] children = this.getContentChildren();
+ for (int i = 0; i < children.length; i++) {
+ if (children[i].containsOffset(offset)) {
+ return children[i].getLineEndOffset(offset);
+ }
+ }
+ return offset;
+ }
+
+ public int getLineStartOffset(int offset) {
+ BlockBox[] children = this.getContentChildren();
+ for (int i = 0; i < children.length; i++) {
+ if (children[i].containsOffset(offset)) {
+ return children[i].getLineStartOffset(offset);
+ }
+ }
+ return offset;
+ }
+
+
+ public int getMarginBottom() {
+ return this.marginBottom;
+ }
+
+ public int getMarginTop() {
+ return this.marginTop;
+ }
+
+ public int getNextLineOffset(LayoutContext context, int offset, int x) {
+
+ //
+ // This algorithm works when this block owns the offsets between
+ // its children.
+ //
+
+ if (offset == this.getEndOffset()) {
+ return -1;
+ }
+
+ BlockBox[] children = this.getContentChildren();
+
+ if (offset < this.getStartOffset()
+ && children.length > 0
+ && children[0].getStartOffset() > this.getStartOffset()) {
+ //
+ // If there's an offset before the first child, put the caret there.
+ //
+ return this.getStartOffset();
+ }
+
+ for (int i = 0; i < children.length; i++) {
+ BlockBox child = children[i];
+ if (offset <= child.getEndOffset()) {
+ int newOffset = child.getNextLineOffset(context, offset, x - child.getX());
+ if (newOffset < 0 /* && i < children.length-1 */) {
+ return child.getEndOffset() + 1;
+ } else {
+ return newOffset;
+ }
+ }
+ }
+
+ return this.getEndOffset();
+ }
+
+ public BlockBox getParent() {
+ return this.parent;
+ }
+
+ public int getPreviousLineOffset(LayoutContext context, int offset, int x) {
+
+ if (offset == this.getStartOffset()) {
+ return -1;
+ }
+
+ BlockBox[] children = this.getContentChildren();
+
+ if (offset > this.getEndOffset()
+ && children.length > 0
+ && children[children.length-1].getEndOffset() < this.getEndOffset()) {
+ //
+ // If there's an offset after the last child, put the caret there.
+ //
+ return this.getEndOffset();
+ }
+
+ for (int i = children.length; i > 0; i--) {
+ BlockBox child = children[i-1];
+ if (offset >= child.getStartOffset()) {
+ int newOffset = child.getPreviousLineOffset(context, offset, x - child.getX());
+ if (newOffset < 0 && i > 0) {
+ return child.getStartOffset() - 1;
+ } else {
+ return newOffset;
+ }
+ }
+ }
+
+ return this.getStartOffset();
+ }
+
+
+ public int getStartOffset() {
+ Element element = this.getElement();
+ if (element != null) {
+ return element.getStartOffset() + 1;
+ } else if (this.getStartPosition() != null) {
+ return this.getStartPosition().getOffset();
+ } else {
+ throw new IllegalStateException();
+ }
+ }
+
+ public boolean hasContent() {
+ return true;
+ }
+
+ public void invalidate(boolean direct) {
+
+ if (direct) {
+ this.layoutState = LAYOUT_REDO;
+ } else {
+ this.layoutState = LAYOUT_PROPAGATE;
+ }
+
+ if (this.getParent() instanceof AbstractBlockBox) {
+ ((AbstractBlockBox) this.getParent()).invalidate(false);
+ }
+ }
+
+ public boolean isAnonymous() {
+ return this.getElement() == null;
+ }
+
+ /**
+ * Call the given callback for each child matching one of the given
+ * display styles. Any nodes that do not match one of the given display types
+ * cause the onRange callback to be called, with a range covering all such
+ * contiguous nodes.
+ *
+ * @param styleSheet StyleSheet from which to determine display styles.
+ * @param displayStyles Display types to be explicitly recognized.
+ * @param callback DisplayStyleCallback through which the caller is notified
+ * of matching elements and non-matching ranges.
+ */
+ protected void iterateChildrenByDisplayStyle(StyleSheet styleSheet, Set displayStyles, ElementOrRangeCallback callback) {
+ LayoutUtils.iterateChildrenByDisplayStyle(styleSheet, displayStyles, this.findContainingElement(), this.getStartOffset(), this.getEndOffset(), callback);
+ }
+
+ public void paint(LayoutContext context, int x, int y) {
+
+ if (this.skipPaint(context, x, y)) {
+ return;
+ }
+
+ boolean drawBorders = !context.isElementSelected(this.getElement());
+
+ this.drawBox(context, x, y, this.getParent().getWidth(), drawBorders);
+
+ this.paintChildren(context, x, y);
+
+ this.paintSelectionFrame(context, x, y, true);
+ }
+
+ /**
+ * Default implementation. Width is calculated as the parent's width minus
+ * this box's insets. Height is calculated by getEstimatedHeight.
+ */
+ public void setInitialSize(LayoutContext context) {
+ int parentWidth = this.getParent().getWidth();
+ Insets insets = this.getInsets(context, parentWidth);
+ this.setWidth(Math.max(0, parentWidth - insets.getLeft() - insets.getRight()));
+ this.setHeight(this.getEstimatedHeight(context));
+ }
+
+ public int viewToModel(LayoutContext context, int x, int y) {
+
+ Box[] children = this.getChildren();
+
+ if (children == null) {
+ int charCount = this.getEndOffset() - this.getStartOffset() - 1;
+ if (charCount == 0 || this.getHeight() == 0) {
+ return this.getEndOffset();
+ } else {
+ return this.getStartOffset()
+ + charCount * y / this.getHeight();
+ }
+ } else {
+ for (int i = 0; i < children.length; i++) {
+ Box child = children[i];
+ if (!child.hasContent()) {
+ continue;
+ }
+ if (y < child.getY()) {
+ return child.getStartOffset() - 1;
+ } else if (y < child.getY() + child.getHeight()) {
+ return child.viewToModel(context, x - child.getX(), y
+ - child.getY());
+ }
+ }
+ }
+
+ return this.getEndOffset();
+ }
+
+
+ //===================================================== PRIVATE
+
+ private BlockBox parent;
+ private Box[] children;
+
+ /**
+ * Paint a frame that indicates a block element box has been selected.
+ *
+ * @param context LayoutContext to use.
+ * @param x x-coordinate at which to draw
+ * @param y y-coordinate at which to draw.
+ * @param selected
+ */
+ protected void paintSelectionFrame(LayoutContext context, int x, int y, boolean selected) {
+
+ Element element = this.getElement();
+ Element parent = element == null ? null : element.getParent();
+
+ boolean paintFrame = context.isElementSelected(element)
+ && !context.isElementSelected(parent);
+
+ if (!paintFrame) {
+ return;
+ }
+
+ Graphics g = context.getGraphics();
+ ColorResource foreground;
+ ColorResource background;
+
+ if (selected) {
+ foreground = g.getSystemColor(ColorResource.SELECTION_FOREGROUND);
+ background = g.getSystemColor(ColorResource.SELECTION_BACKGROUND);
+ } else {
+ foreground = g.createColor(new Color(0, 0, 0));
+ background = g.createColor(new Color(0xcc, 0xcc, 0xcc));
+ }
+
+ FontMetrics fm = g.getFontMetrics();
+ ColorResource oldColor = g.setColor(background);
+ g.setLineStyle(Graphics.LINE_SOLID);
+ g.setLineWidth(1);
+ int tabWidth = g.stringWidth(this.getElement().getName()) + fm.getLeading();
+ int tabHeight = fm.getHeight();
+ int tabX = x + this.getWidth() - tabWidth;
+ int tabY = y + this.getHeight() - tabHeight;
+ g.drawRect(x, y, this.getWidth(), this.getHeight());
+ g.fillRect(tabX, tabY, tabWidth, tabHeight);
+ g.setColor(foreground);
+ g.drawString(this.getElement().getName(), tabX + fm.getLeading() / 2, tabY);
+
+ g.setColor(oldColor);
+ if (!selected) {
+ foreground.dispose();
+ background.dispose();
+ }
+ }
+
+
+
+ /** Layout is OK */
+ public static final byte LAYOUT_OK = 0;
+
+ /** My layout is OK, but one of my children needs to be laid out */
+ public static final byte LAYOUT_PROPAGATE = 1;
+
+ /** I need to be laid out */
+ public static final byte LAYOUT_REDO = 2;
+
+ private byte layoutState = LAYOUT_REDO;
+
+ public IntRange layout(LayoutContext context, int top, int bottom) {
+
+ int repaintStart = Integer.MAX_VALUE;
+ int repaintEnd = 0;
+ boolean repaintToBottom = false;
+ int originalHeight = this.getHeight();
+
+ if (this.layoutState == LAYOUT_REDO) {
+
+// System.out.println("Redo layout of " + this.getElement().getName());
+
+ List childList = this.createChildren(context);
+ this.children = (BlockBox[]) childList.toArray(new BlockBox[childList.size()]);
+
+ // Even though we position children after layout, we have to
+ // do a preliminary positioning here so we now which ones
+ // overlap our layout band
+ for (int i = 0; i < this.children.length; i++) {
+ BlockBox child = (BlockBox) this.children[i];
+ child.setInitialSize(context);
+ }
+ this.positionChildren(context);
+
+ // repaint everything
+ repaintToBottom = true;
+ repaintStart = 0;
+ }
+
+ Box[] children = this.getChildren();
+ for (int i = 0; i < children.length; i++) {
+ if (children[i] instanceof BlockBox) {
+ BlockBox child = (BlockBox) children[i];
+ if (top <= child.getY() + child.getHeight() &&
+ bottom >= child.getY()) {
+
+ IntRange repaintRange = child.layout(context, top - child.getY(), bottom - child.getY());
+ if (repaintRange != null) {
+ repaintStart = Math.min(repaintStart, repaintRange.getStart() + child.getY());
+ repaintEnd = Math.max(repaintEnd, repaintRange.getEnd() + child.getY());
+ }
+ }
+ }
+ }
+
+ int childRepaintStart = this.positionChildren(context);
+ if (childRepaintStart != -1) {
+ repaintToBottom = true;
+ repaintStart = Math.min(repaintStart, childRepaintStart);
+ }
+
+ this.layoutState = LAYOUT_OK;
+
+ if (repaintToBottom) {
+ repaintEnd = Math.max(originalHeight, this.getHeight());
+ }
+
+ if (repaintStart < repaintEnd) {
+ return new IntRange(repaintStart, repaintEnd);
+ } else {
+ return null;
+ }
+ }
+
+ protected abstract List createChildren(LayoutContext context);
+
+ /**
+ * Creates a list of block boxes for a given document range. beforeInlines
+ * and afterInlines are prepended/appended to the first/last block child,
+ * and each may be null.
+ */
+ protected List createBlockBoxes(LayoutContext context, int startOffset, int endOffset, int width, List beforeInlines, List afterInlines) {
+
+ List blockBoxes = new ArrayList();
+ List pendingInlines = new ArrayList();
+
+ if (beforeInlines != null) {
+ pendingInlines.addAll(beforeInlines);
+ }
+
+ Element element = context.getDocument().findCommonElement(startOffset, endOffset);
+
+ if (startOffset == endOffset) {
+ int relOffset = startOffset - element.getStartOffset();
+ pendingInlines.add(new PlaceholderBox(context, element, relOffset));
+ } else {
+
+ BlockInlineIterator iter = new BlockInlineIterator(context, element, startOffset, endOffset);
+
+ while (true) {
+
+ Object next = iter.next();
+
+ if (next == null) {
+ break;
+ }
+
+ if (next instanceof IntRange) {
+
+ IntRange range = (IntRange) next;
+
+ InlineElementBox.InlineBoxes inlineBoxes =
+ InlineElementBox.createInlineBoxes(context, element, range.getStart(), range.getEnd());
+ pendingInlines.addAll(inlineBoxes.boxes);
+ pendingInlines.add(new PlaceholderBox(context, element, range.getEnd() - element.getStartOffset()));
+
+ } else {
+
+ if (pendingInlines.size() > 0) {
+ blockBoxes.add(ParagraphBox.create(context, element, pendingInlines, width));
+ pendingInlines.clear();
+ }
+
+ if (isTableChild(context, next)) {
+
+ // Consume continguous table children and create an
+ // anonymous table.
+
+ int tableStartOffset = ((Element) next).getStartOffset();
+ int tableEndOffset = -1; // dummy to hide warning
+ while (isTableChild(context, next)) {
+ tableEndOffset = ((Element) next).getEndOffset() + 1;
+ next = iter.next();
+ }
+
+ // add anonymous table
+ blockBoxes.add(new TableBox(context, this, tableStartOffset, tableEndOffset));
+
+ if (next == null) {
+ break;
+ } else {
+ iter.push(next);
+ }
+
+ } else { // next is a block box element
+ Element blockElement = (Element) next;
+ blockBoxes.add(context.getBoxFactory().createBox(context, blockElement, this, width));
+ }
+ }
+ }
+ }
+
+ if (afterInlines != null) {
+ pendingInlines.addAll(afterInlines);
+ }
+
+ if (pendingInlines.size() > 0) {
+ blockBoxes.add(ParagraphBox.create(context, element, pendingInlines, width));
+ pendingInlines.clear();
+ }
+
+ return blockBoxes;
+ }
+
+ private class BlockInlineIterator {
+
+ public BlockInlineIterator(LayoutContext context, Element element, int startOffset, int endOffset) {
+ this.context = context;
+ this.element = element;
+ this.startOffset = startOffset;
+ this.endOffset = endOffset;
+ }
+
+ /**
+ * Returns the next block element or inline range, or null if we're at the end.
+ */
+ public Object next() {
+ if (this.pushStack.size() > 0) {
+ return this.pushStack.removeLast();
+ } else if (startOffset == endOffset) {
+ return null;
+ } else {
+ Element blockElement = findNextBlockElement(this.context, this.element, startOffset, endOffset);
+ if (blockElement == null) {
+ if (startOffset < endOffset) {
+ IntRange result = new IntRange(startOffset, endOffset);
+ startOffset = endOffset;
+ return result;
+ } else {
+ return null;
+ }
+ } else if (blockElement.getStartOffset() > startOffset) {
+ this.pushStack.addLast(blockElement);
+ IntRange result = new IntRange(startOffset, blockElement.getStartOffset());
+ startOffset = blockElement.getEndOffset() + 1;
+ return result;
+ } else {
+ startOffset = blockElement.getEndOffset() + 1;
+ return blockElement;
+ }
+ }
+ }
+
+ public Object peek() {
+ if (this.pushStack.size() == 0) {
+ Object next = next();
+ if (next == null) {
+ return null;
+ } else {
+ push(next);
+ }
+ }
+ return pushStack.getLast();
+ }
+ public void push(Object pushed) {
+ this.pushStack.addLast(pushed);
+ }
+
+ private LayoutContext context;
+ private Element element;
+ private int startOffset;
+ private int endOffset;
+ private LinkedList pushStack = new LinkedList();
+ }
+
+ protected boolean hasChildren() {
+ return this.getChildren() != null && this.getChildren().length > 0;
+ }
+
+ /**
+ * Positions the children of this box. Vertical margins are collapsed here.
+ * Returns the vertical offset of the top of the first child to move,
+ * or -1 if not children were actually moved.
+ */
+ protected int positionChildren(LayoutContext context) {
+
+ int childY = 0;
+ int prevMargin = 0;
+ BlockBox[] children = this.getBlockChildren();
+ int repaintStart = -1;
+
+ Styles styles = null;
+
+ if (!this.isAnonymous()) {
+ styles = context.getStyleSheet().getStyles(this.getElement());
+ }
+
+ if (styles != null && children.length > 0) {
+ if (styles.getBorderTopWidth() + styles.getPaddingTop().get(this.getWidth()) == 0) {
+ // first child's top margin collapses into ours
+ this.marginTop = Math.max(this.marginTop, children[0].getMarginTop());
+ childY -= children[0].getMarginTop();
+ }
+ }
+
+
+ for (int i = 0; i < children.length; i++) {
+
+ Insets insets = children[i].getInsets(context, this.getWidth());
+
+ childY += insets.getTop();
+
+ if (i > 0) {
+ childY -= Math.min(prevMargin,
+ children[i].getMarginTop());
+ }
+
+ if (repaintStart == -1 && children[i].getY() != childY) {
+ repaintStart = Math.min(children[i].getY(), childY);
+ }
+
+ children[i].setX(insets.getLeft());
+ children[i].setY(childY);
+
+ childY += children[i].getHeight() + insets.getBottom();
+ prevMargin = children[i].getMarginBottom();
+ }
+
+ if (styles != null && children.length > 0) {
+ if (styles.getBorderBottomWidth() + styles.getPaddingBottom().get(this.getWidth()) == 0) {
+ // last child's bottom margin collapses into ours
+ this.marginBottom = Math.max(this.marginBottom, prevMargin);
+ childY -= prevMargin;
+ }
+ }
+
+ this.setHeight(childY);
+
+ return repaintStart;
+ }
+
+
+ /**
+ * Sets the layout state of the box.
+ * @param layoutState One of the LAYOUT_* constants
+ */
+ protected void setLayoutState(byte layoutState) {
+ this.layoutState = layoutState;
+ }
+
+
+
+ //========================================================= PRIVATE
+ /** The length, in pixels, of the horizontal caret between block boxes */
+ private static final int H_CARET_LENGTH = 20;
+
+
+ /**
+ * Element with which we are associated. For anonymous boxes, this is null.
+ */
+ private Element element;
+
+ /*
+ * We cache the top and bottom margins, since they may be affected by
+ * our children.
+ */
+ private int marginTop;
+ private int marginBottom;
+
+ /**
+ * Start position of an anonymous box. For non-anonymous boxes, this is null.
+ */
+ private Position startPosition;
+
+ /**
+ * End position of an anonymous box. For non-anonymous boxes, this is null.
+ */
+ private Position endPosition;
+
+ /**
+ * Searches for the next block-formatted child.
+ * @param context LayoutContext to use.
+ * @param element Element within which to search.
+ * @param startOffset The offset at which to start the search.
+ * @param endOffset The offset at which to end the search.
+ */
+ private static Element findNextBlockElement(LayoutContext context, Element element, int startOffset, int endOffset) {
+
+ Element[] children = element.getChildElements();
+ for (int i = 0; i < children.length; i++) {
+ Element child = children[i];
+ if (child.getEndOffset() < startOffset) {
+ continue;
+ } else if (child.getStartOffset() >= endOffset) {
+ break;
+ } else {
+ Styles styles = context.getStyleSheet().getStyles(child);
+ if (!styles.getDisplay().equals(CSS.INLINE)) { // TODO do proper block display determination
+ return child;
+ } else {
+ Element fromChild = findNextBlockElement(context, child, startOffset, endOffset);
+ if (fromChild != null) {
+ return fromChild;
+ }
+ }
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Return the end position of an anonymous box. The default implementation
+ * returns null.
+ */
+ private Position getEndPosition() {
+ return this.endPosition;
+ }
+
+ /**
+ * Return the start position of an anonymous box. The default implementation
+ * returns null.
+ */
+ private Position getStartPosition() {
+ return this.startPosition;
+ }
+
+ private boolean isTableChild(LayoutContext context, Object rangeOrElement) {
+ if (rangeOrElement != null && rangeOrElement instanceof Element) {
+ return LayoutUtils.isTableChild(context.getStyleSheet(), (Element) rangeOrElement);
+ } else {
+ return false;
+ }
+ }
+
+
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/layout/AbstractBox.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/layout/AbstractBox.java
new file mode 100644
index 0000000..9040b70
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/layout/AbstractBox.java
@@ -0,0 +1,415 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.layout;
+
+import org.eclipse.wst.xml.vex.core.internal.core.Caret;
+import org.eclipse.wst.xml.vex.core.internal.core.Color;
+import org.eclipse.wst.xml.vex.core.internal.core.ColorResource;
+import org.eclipse.wst.xml.vex.core.internal.core.Graphics;
+import org.eclipse.wst.xml.vex.core.internal.core.Insets;
+import org.eclipse.wst.xml.vex.core.internal.core.Rectangle;
+import org.eclipse.wst.xml.vex.core.internal.css.CSS;
+import org.eclipse.wst.xml.vex.core.internal.css.Styles;
+import org.eclipse.wst.xml.vex.core.internal.dom.Element;
+
+
+/**
+ * Base implementation of the <code>Box</code> interface, implementing
+ * some common methods.
+ */
+public abstract class AbstractBox implements Box {
+
+ private static final Box[] EMPTY_BOX_ARRAY = new Box[0];
+
+ private int x;
+ private int y;
+ private int width = -1;
+ private int height = -1;
+
+ /**
+ * Class constructor.
+ */
+ public AbstractBox() {
+ }
+
+ /**
+ * Returns true if the given offset is between startOffset and
+ * endOffset, inclusive.
+ * @see org.eclipse.wst.xml.vex.core.internal.layout.Box#containsOffset(int)
+ */
+ public boolean containsOffset(int offset) {
+ return offset >= this.getStartOffset() && offset <= this.getEndOffset();
+ }
+
+ /**
+ * Throws <code>IllegalStateException</code>. Boxes with content must
+ * provide an implementation of this method.
+ * @see org.eclipse.wst.xml.vex.core.internal.layout.Box#getCaret(org.eclipse.wst.xml.vex.core.internal.layout.LayoutContext, int)
+ */
+ public Caret getCaret(LayoutContext context, int offset) {
+ throw new IllegalStateException();
+ }
+
+ /**
+ * Returns an empty array of children.
+ * @see org.eclipse.wst.xml.vex.core.internal.layout.Box#getChildren()
+ */
+ public Box[] getChildren() {
+ return EMPTY_BOX_ARRAY;
+ }
+
+ /**
+ * Returns null. Boxes associated with
+ * elements must provide an implementation of this method.
+ * @see org.eclipse.wst.xml.vex.core.internal.layout.Box#getElement()
+ */
+ public Element getElement() {
+ return null;
+ }
+
+ /**
+ * Throws <code>IllegalStateException</code>. Boxes with content must
+ * provide an implementation of this method.
+ * @see org.eclipse.wst.xml.vex.core.internal.layout.Box#getEndOffset()
+ */
+ public int getEndOffset() {
+ throw new IllegalStateException();
+ }
+
+ /**
+ * Returns the height set with <code>setHeight</code>.
+ * @see org.eclipse.wst.xml.vex.core.internal.layout.Box#getHeight()
+ */
+ public int getHeight() {
+ return this.height;
+ }
+
+ /**
+ * Throws <code>IllegalStateException</code>. Boxes with content must
+ * provide an implementation of this method.
+ * @see org.eclipse.wst.xml.vex.core.internal.layout.Box#getStartOffset()
+ */
+ public int getStartOffset() {
+ throw new IllegalStateException();
+ }
+
+ /**
+ * Returns the insets of this box, which is the sum of the margin, border,
+ * and padding on each side. If no element is associated with this box
+ * returns all zeros.
+ */
+ public Insets getInsets(LayoutContext context, int containerWidth) {
+ Element element = this.getElement();
+ if (element == null) {
+ return Insets.ZERO_INSETS;
+ } else {
+ return getInsets(context.getStyleSheet().getStyles(element), containerWidth);
+ }
+ }
+
+ /**
+ * Returns false. Boxes with content must override this method and return
+ * true, and must provide implementations for the following methods.
+ *
+ * <ul>
+ * <li>{@link Box#getCaretShapes}</li>
+ * <li>{@link Box#getStartOffset}</li>
+ * <li>{@link Box#getEndOffset}</li>
+ * <li>{@link Box#viewToModel}</li>
+ * </ul>
+ * @see org.eclipse.wst.xml.vex.core.internal.layout.Box#hasContent()
+ */
+ public boolean hasContent() {
+ return false;
+ }
+
+ public boolean isAnonymous() {
+ return true;
+ }
+
+ /**
+ * Returns the width set with <code>setWidth</code>.
+ * @see org.eclipse.wst.xml.vex.core.internal.layout.Box#getWidth()
+ */
+ public int getWidth() {
+ return this.width;
+ }
+
+ /**
+ * Returns the value set with <code>setX</code>.
+ * @see org.eclipse.wst.xml.vex.core.internal.layout.Box#getX()
+ */
+ public int getX() {
+ return this.x;
+ }
+
+ /**
+ * Returns the value set with <code>setY</code>.
+ * @see org.eclipse.wst.xml.vex.core.internal.layout.Box#getY()
+ */
+ public int getY() {
+ return this.y;
+ }
+
+ /**
+ * Paint all children of this box.
+ * @see org.eclipse.wst.xml.vex.core.internal.layout.Box#paint(org.eclipse.wst.xml.vex.core.internal.layout.LayoutContext, int, int)
+ */
+ public void paint(LayoutContext context, int x, int y) {
+
+ if (this.skipPaint(context, x, y)) {
+ return;
+ }
+
+ this.paintChildren(context, x, y);
+ }
+
+ /**
+ * Paint the children of this box.
+ * @param context LayoutContext to use.
+ * @param x x-coordinate at which to paint
+ * @param y y-coordinate at which to paint
+ */
+ protected void paintChildren(LayoutContext context, int x, int y) {
+ Box[] children = this.getChildren();
+ for (int i = 0; children != null && i < children.length; i++) {
+ Box child = children[i];
+ child.paint(context, x + child.getX(), y + child.getY());
+ }
+ }
+
+
+ public void setHeight(int height) {
+ this.height = height;
+ }
+
+ public void setWidth(int width) {
+ this.width = width;
+ }
+
+ public void setX(int x) {
+ this.x = x;
+ }
+
+ public void setY(int y) {
+ this.y = y;
+ }
+
+ /**
+ * Returns true if this box is outside the clip
+ * region. Implementations of <code>paint</code> should use this
+ * to avoid unnecessary painting.
+ *
+ * @param context <code>LayoutContext</code> in effect.
+ * @param x the x-coordinate at which the box is being painted
+ * @param y the y-coordinate at which the box is being painted
+ */
+ protected boolean skipPaint(LayoutContext context, int x, int y) {
+ Rectangle clipBounds = context.getGraphics().getClipBounds();
+
+ return clipBounds.getY() + clipBounds.getHeight() <= y
+ || clipBounds.getY() >= y + this.getHeight();
+
+ }
+
+ /**
+ * Throws <code>IllegalStateException</code>. Boxes with content must
+ * provide an implementation of this method.
+ * @see org.eclipse.wst.xml.vex.core.internal.layout.Box#viewToModel(org.eclipse.wst.xml.vex.core.internal.layout.LayoutContext, int, int)
+ */
+ public int viewToModel(LayoutContext context, int x, int y) {
+ throw new IllegalStateException();
+ }
+
+ /**
+ * Draws the background and borders of a CSS-styled box.
+ *
+ * @param context LayoutContext used for drawing.
+ * @param x x-coordinate of the left side of the box
+ * @param y y-coordinate of the top of the box
+ * @param containerWidth width of the containing client area. Used for calculating
+ * padding expressed as a percentage.
+ * @param drawBorders If true, the background is filled and the borders are drawn;
+ * otherwise, just the background is filled. This is handy when removing the
+ * borders when drawing the selection frame.
+ */
+ protected void drawBox(LayoutContext context, int x, int y, int containerWidth, boolean drawBorders) {
+ this.drawBox(context, this.getElement(), x, y, containerWidth, drawBorders);
+ }
+
+ /**
+ * Draws the background and borders of a CSS-styled box.
+ *
+ * @param context LayoutContext used for drawing.
+ * @param element Element to use when determining styles. This is used by
+ * TableBodyBox to specify the corresponding table element.
+ * @param x x-coordinate of the left side of the box
+ * @param y y-coordinate of the top of the box
+ * @param containerWidth width of the containing client area. Used for calculating
+ * padding expressed as a percentage.
+ * @param drawBorders If true, the background is filled and the borders are drawn;
+ * otherwise, just the background is filled. This is handy when removing the
+ * borders when drawing the selection frame.
+ */
+ protected void drawBox(LayoutContext context, Element element, int x, int y, int containerWidth, boolean drawBorders) {
+
+ if (element == null) {
+ return;
+ }
+
+ Graphics g = context.getGraphics();
+ Styles styles = context.getStyleSheet().getStyles(element);
+
+ boolean hasLeft = true;
+ boolean hasRight = true;
+ int left = x - styles.getPaddingLeft().get(containerWidth) - styles.getBorderLeftWidth();
+ int top = y - styles.getPaddingTop().get(containerWidth) - styles.getBorderTopWidth();
+ int right = x + this.getWidth() + styles.getPaddingRight().get(containerWidth) + styles.getBorderRightWidth();
+ int bottom = y + this.getHeight() + styles.getPaddingBottom().get(containerWidth) + styles.getBorderBottomWidth();
+
+ if (this instanceof InlineElementBox) {
+ // TODO fix boxes for inline elements
+ hasLeft = this.getStartOffset() == element.getStartOffset() + 1;
+ hasRight = this.getEndOffset() == element.getEndOffset();
+ if (hasLeft) {
+ //left += styles.getMarginLeft().get(0);
+ }
+ if (hasRight) {
+ //right -= styles.getMarginRight().get(0);
+ }
+ //top = y - styles.getPaddingTop().get(0) - styles.getBorderTopWidth();
+ //bottom = y + box.getHeight() + styles.getPaddingBottom().get(0) + styles.getBorderBottomWidth();
+ }
+
+ Color backgroundColor = styles.getBackgroundColor();
+
+ if (backgroundColor != null) {
+ ColorResource color = g.createColor(backgroundColor);
+ ColorResource oldColor = g.setColor(color);
+ g.fillRect(left, top, right - left, bottom - top);
+ g.setColor(oldColor);
+ color.dispose();
+ }
+
+
+ if (drawBorders) {
+ // Object oldAntiAlias =
+ // g.getRenderingHint(RenderingHints.KEY_ANTIALIASING);
+ //
+ // g.setRenderingHint(
+ // RenderingHints.KEY_ANTIALIASING,
+ // RenderingHints.VALUE_ANTIALIAS_OFF);
+ boolean oldAntiAlias = g.isAntiAliased();
+ g.setAntiAliased(false);
+
+ int bw2 = styles.getBorderBottomWidth() / 2;
+ int lw2 = styles.getBorderLeftWidth() / 2;
+ int rw2 = styles.getBorderRightWidth() / 2;
+ int tw2 = styles.getBorderTopWidth() / 2;
+
+ // Bottom border
+ if (styles.getBorderBottomWidth() > 0) {
+ ColorResource color = g.createColor(styles
+ .getBorderBottomColor());
+ ColorResource oldColor = g.setColor(color);
+ g.setLineStyle(lineStyle(styles.getBorderBottomStyle()));
+ g.setLineWidth(styles.getBorderBottomWidth());
+ g.drawLine(left + bw2, bottom - bw2 - 1, right - bw2, bottom
+ - bw2 - 1);
+ g.setColor(oldColor);
+ color.dispose();
+ }
+
+ // Left border
+ if (hasLeft && styles.getBorderLeftWidth() > 0) {
+ ColorResource color = g
+ .createColor(styles.getBorderLeftColor());
+ ColorResource oldColor = g.setColor(color);
+ g.setLineStyle(lineStyle(styles.getBorderLeftStyle()));
+ g.setLineWidth(styles.getBorderLeftWidth());
+ g.drawLine(left + lw2, top + lw2, left + lw2, bottom - lw2 - 1);
+ g.setColor(oldColor);
+ color.dispose();
+ }
+
+ // Right border
+ if (hasRight && styles.getBorderRightWidth() > 0) {
+ ColorResource color = g.createColor(styles
+ .getBorderRightColor());
+ ColorResource oldColor = g.setColor(color);
+ g.setLineStyle(lineStyle(styles.getBorderRightStyle()));
+ g.setLineWidth(styles.getBorderRightWidth());
+ g.drawLine(right - rw2 - 1, top + rw2, right - rw2 - 1, bottom
+ - rw2 - 1);
+ g.setColor(oldColor);
+ color.dispose();
+ }
+
+ // Top border
+ if (styles.getBorderTopWidth() > 0) {
+ ColorResource color = g.createColor(styles.getBorderTopColor());
+ ColorResource oldColor = g.setColor(color);
+ g.setLineStyle(lineStyle(styles.getBorderTopStyle()));
+ g.setLineWidth(styles.getBorderTopWidth());
+ g.drawLine(left + tw2, top + tw2, right - tw2, top + tw2);
+ g.setColor(oldColor);
+ color.dispose();
+ }
+
+ // g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, oldAntiAlias);
+ g.setAntiAliased(oldAntiAlias);
+
+ }
+ }
+
+ /**
+ * Convert a CSS line style string (e.g. "dotted") to the corresponding
+ * Graphics.LINE_XXX style.
+ */
+ private static int lineStyle(String style) {
+ if (style.equals(CSS.DOTTED)) {
+ return Graphics.LINE_DOT;
+ } else if (style.equals(CSS.DASHED)) {
+ return Graphics.LINE_DASH;
+ } else {
+ return Graphics.LINE_SOLID;
+ }
+
+ }
+
+ /**
+ * Returns the insets for a CSS box with the given styles.
+ * @param styles Styles for the box.
+ * @param containerWidth Content area of the containing box.
+ */
+ public static Insets getInsets(Styles styles, int containerWidth) {
+
+ int top = styles.getMarginTop().get(containerWidth)
+ + styles.getBorderTopWidth()
+ + styles.getPaddingTop().get(containerWidth);
+
+ int left = styles.getMarginLeft().get(containerWidth)
+ + styles.getBorderLeftWidth()
+ + styles.getPaddingLeft().get(containerWidth);
+
+ int bottom = styles.getMarginBottom().get(containerWidth)
+ + styles.getBorderBottomWidth()
+ + styles.getPaddingBottom().get(containerWidth);
+
+ int right = styles.getMarginRight().get(containerWidth)
+ + styles.getBorderRightWidth()
+ + styles.getPaddingRight().get(containerWidth);
+
+ return new Insets(top, left, bottom, right);
+ }
+
+}
+
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/layout/AnonymousBlockBox.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/layout/AnonymousBlockBox.java
new file mode 100644
index 0000000..839bd42
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/layout/AnonymousBlockBox.java
@@ -0,0 +1,34 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.layout;
+
+import java.util.List;
+
+/**
+ * A block box that is not associated with a particular element.
+ */
+public class AnonymousBlockBox extends AbstractBlockBox {
+
+ public AnonymousBlockBox(
+ LayoutContext context,
+ BlockBox parent,
+ int startOffset,
+ int endOffset) {
+
+ super(context, parent, startOffset, endOffset);
+ }
+
+ protected List createChildren(LayoutContext context) {
+ return createBlockBoxes(context, this.getStartOffset(), this.getEndOffset(), this.getWidth(), null, null);
+ }
+
+
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/layout/BlockBox.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/layout/BlockBox.java
new file mode 100644
index 0000000..55c892a
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/layout/BlockBox.java
@@ -0,0 +1,125 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.layout;
+
+import org.eclipse.wst.xml.vex.core.internal.core.IntRange;
+
+
+/**
+ * Represents a block box. Block boxes are stacked one on top of another.
+ */
+public interface BlockBox extends Box {
+
+ /**
+ * Returns the first LineBox contained by this block, or null if the block
+ * contains no lines.
+ */
+ public LineBox getFirstLine();
+
+ /**
+ * Returns the last LineBox contained by this block, or null if the block
+ * contains no lines.
+ */
+ public LineBox getLastLine();
+
+ /**
+ * Returns the offset of the end of the line containing the given offset.
+ * @param offset offset identifying the current line.
+ */
+ public int getLineEndOffset(int offset);
+
+ /**
+ * Returns the offset of the start of the line containing the given offset.
+ * @param offset offset identifying the current line.
+ */
+ public int getLineStartOffset(int offset);
+
+ /**
+ * Returns the bottom margin of this box.
+ */
+ public int getMarginBottom();
+
+ /**
+ * Returns the top margin of this box.
+ */
+ public int getMarginTop();
+
+ /**
+ * Returns the offset on the next line that is closest to the given
+ * x coordinate. The given offset may be before the start of this box
+ * in which case this method should return the offset of the first line
+ * in this box.
+ * @param context LayoutContext used for the layout
+ * @param offset the current offset
+ * @param x the x coordinate
+ */
+ public int getNextLineOffset(LayoutContext context, int offset, int x);
+
+ /**
+ * Returns the offset on the previous line that is closest to the given
+ * x coordinate. The given offset may be after the end of this box
+ * in which case this method should return the offset of the last line
+ * in this box.
+ * @param context LayoutContext used for the layout
+ * @param offset the current offset
+ * @param x the x coordinate
+ */
+ public int getPreviousLineOffset(LayoutContext context, int offset, int x);
+
+ /**
+ * Returns the parent box of this box.
+ */
+ public BlockBox getParent();
+
+ /**
+ * Informs this box that its layout has become invalid, and that it should
+ * re-do it the next time layout is called.
+ * @param direct If true, the box's content has changed and it must re-create
+ * it's children on the next call to layout. Otherwise, it should just
+ * propagate the next layout call to its children.
+ */
+ public void invalidate(boolean direct);
+
+ /**
+ * Layout this box. This method is responsible for the following.
+ *
+ * <ul>
+ * <li>Creating any child boxes.</li>
+ * <li>Calling layout on the child boxes.</li>
+ * <li>Positioning the child boxes (i.e. calling child.setX() and child.setY())</li>
+ * <li>Determining this box's height and width.</li>
+ * </ul>
+ *
+ * <p>Boxes with no children should simply calculate their width and height
+ * here</p>
+ *
+ * <p>This method is passed a vertical range to be layed out. Children
+ * falling outside this range need not be layed out.</p>
+ *
+ * <p>This method returns an IntRange object representing the vertical
+ * range to re-draw due to layout change. Null may be returned if there
+ * were no changes that need to be re-drawn.</p>
+ *
+ * @param context The layout context to be used.
+ * @param top Top of the range to lay out.
+ * @param bottom Bottom of the range to lay out.
+ */
+ public IntRange layout(LayoutContext context, int top, int bottom);
+
+ /**
+ * Sets the initial size of the box.
+ *
+ * @param context LayoutContext to use.
+ */
+ public void setInitialSize(LayoutContext context);
+}
+
+
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/layout/BlockElementBox.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/layout/BlockElementBox.java
new file mode 100644
index 0000000..b4dab38
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/layout/BlockElementBox.java
@@ -0,0 +1,385 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.layout;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.eclipse.wst.xml.vex.core.internal.VEXCorePlugin;
+import org.eclipse.wst.xml.vex.core.internal.core.Drawable;
+import org.eclipse.wst.xml.vex.core.internal.core.Graphics;
+import org.eclipse.wst.xml.vex.core.internal.core.Rectangle;
+import org.eclipse.wst.xml.vex.core.internal.css.CSS;
+import org.eclipse.wst.xml.vex.core.internal.css.StyleSheet;
+import org.eclipse.wst.xml.vex.core.internal.css.Styles;
+import org.eclipse.wst.xml.vex.core.internal.dom.Element;
+
+
+/**
+ * A block box corresponding to a DOM Element. Block boxes lay their
+ * children out stacked top to bottom. Block boxes correspond to the
+ * <code>display: block;</code> CSS property.
+ */
+public class BlockElementBox extends AbstractBlockBox {
+
+ /** hspace btn. list-item bullet and block, as fraction of font-size */
+ static final float BULLET_SPACE = 0.5f;
+
+ /** vspace btn. list-item bullet and baseine, as fraction of font-size */
+ //private static final float BULLET_LIFT = 0.1f;
+
+ /** number of boxes created since VM startup, for profiling */
+ private static int boxCount;
+
+ BlockBox beforeMarker;
+
+ /**
+ * Class constructor. This box's children are not created here but in the first
+ * call to layout. Instead, we estimate the box's height here based on the given width.
+ * @param context LayoutContext used for this layout.
+ * @param parent This box's parent box.
+ * @param element Element to which this box corresponds.
+ */
+ public BlockElementBox(LayoutContext context, BlockBox parent, Element element) {
+ super(context, parent, element);
+ }
+
+ /**
+ * Returns the number of boxes created since VM startup. Used for profiling.
+ */
+ public static int getBoxCount() {
+ return boxCount;
+ }
+
+ public int getEndOffset() {
+ return this.getElement().getEndOffset();
+ }
+
+ public int getStartOffset() {
+ return this.getElement().getStartOffset() + 1;
+ }
+
+ public boolean hasContent() {
+ return true;
+ }
+
+ public void paint(LayoutContext context, int x, int y) {
+
+ super.paint(context, x, y);
+
+ if (this.beforeMarker != null) {
+ this.beforeMarker.paint(
+ context,
+ x + this.beforeMarker.getX(),
+ y + this.beforeMarker.getY());
+ }
+ }
+
+
+ protected int positionChildren(LayoutContext context) {
+
+ int repaintStart = super.positionChildren(context);
+
+ Styles styles = context.getStyleSheet().getStyles(this.getElement());
+ if (this.beforeMarker != null) {
+ int x = - this.beforeMarker.getWidth() - Math.round(BULLET_SPACE * styles.getFontSize());
+ int y = this.getFirstLineTop(context);
+ LineBox firstLine = this.getFirstLine();
+ if (firstLine != null) {
+ y += firstLine.getBaseline() - this.beforeMarker.getFirstLine().getBaseline();
+ }
+
+ this.beforeMarker.setX(x);
+ this.beforeMarker.setY(y);
+ }
+
+ return repaintStart;
+ }
+
+ public String toString() {
+ return "BlockElementBox: <" + this.getElement().getName() + ">"
+ + "[x=" + this.getX()
+ + ",y=" + this.getY()
+ + ",width=" + this.getWidth()
+ + ",height=" + this.getHeight() + "]";
+ }
+
+
+ //===================================================== PRIVATE
+
+
+ /**
+ * Lays out the children as vertically stacked blocks. Runs of
+ * text and inline elements are wrapped in DummyBlockBox's.
+ */
+ protected List createChildren(LayoutContext context) {
+
+ long start = 0;
+ if (VEXCorePlugin.getInstance().isDebugging()) {
+ start = System.currentTimeMillis();
+ }
+
+ Element element = this.getElement();
+ int width = this.getWidth();
+
+ List childList = new ArrayList();
+
+ StyleSheet ss = context.getStyleSheet();
+
+ // element and styles for generated boxes
+ Element genElement;
+ Styles genStyles;
+
+ // :before content
+ List beforeInlines = null;
+ genElement = context.getStyleSheet().getBeforeElement(this.getElement());
+ if (genElement != null) {
+ genStyles = ss.getStyles(genElement);
+ if (genStyles.getDisplay().equals(CSS.INLINE)) {
+ beforeInlines = new ArrayList();
+ beforeInlines.addAll(LayoutUtils.createGeneratedInlines(context, genElement));
+ } else {
+ childList.add(new BlockPseudoElementBox(context, genElement, this, width));
+ }
+ }
+
+ // :after content
+ Box afterBlock = null;
+ List afterInlines = null;
+ genElement = context.getStyleSheet().getAfterElement(this.getElement());
+ if (genElement != null) {
+ genStyles = context.getStyleSheet().getStyles(genElement);
+ if (genStyles.getDisplay().equals(CSS.INLINE)) {
+ afterInlines = new ArrayList();
+ afterInlines.addAll(LayoutUtils.createGeneratedInlines(context, genElement));
+ } else {
+ afterBlock = new BlockPseudoElementBox(context, genElement, this, width);
+ }
+ }
+
+ int startOffset = element.getStartOffset() + 1;
+ int endOffset = element.getEndOffset();
+ childList.addAll(createBlockBoxes(context, startOffset, endOffset, width, beforeInlines, afterInlines));
+
+ if (afterBlock != null) {
+ childList.add(afterBlock);
+ }
+
+ Styles styles = context.getStyleSheet().getStyles(this.getElement());
+ if (styles.getDisplay().equals(CSS.LIST_ITEM)
+ && !styles.getListStyleType().equals(CSS.NONE)) {
+ this.createListMarker(context);
+ }
+
+ if (VEXCorePlugin.getInstance().isDebugging()) {
+ long end = System.currentTimeMillis();
+ if (end - start > 10) {
+ System.out.println("BEB.layout for " + this.getElement().getName() + " took " + (end-start) + "ms");
+ }
+ }
+
+ return childList;
+ }
+
+
+
+
+
+ /**
+ * Creates a marker box for this primary box and puts it in the
+ * beforeMarker field.
+ */
+ private void createListMarker(LayoutContext context) {
+
+ Styles styles = context.getStyleSheet().getStyles(this.getElement());
+
+ InlineBox markerInline;
+ String type =styles.getListStyleType();
+ if (type.equals(CSS.NONE)) {
+ return;
+ } else if (type.equals(CSS.CIRCLE)) {
+ markerInline = createCircleBullet(this.getElement(), styles);
+ } else if (type.equals(CSS.SQUARE)) {
+ markerInline = createSquareBullet(this.getElement(), styles);
+ } else if (isEnumeratedListStyleType(type)) {
+ String item = this.getItemNumberString(type);
+ markerInline = new StaticTextBox(context, this.getElement(), item + ".");
+ } else {
+ markerInline = createDiscBullet(this.getElement(), styles);
+ }
+
+ this.beforeMarker = ParagraphBox.create(
+ context, this.getElement(), new InlineBox[] { markerInline }, Integer.MAX_VALUE);
+
+ }
+
+ /**
+ * Returns a Drawable that draws a circle-style list item bullet.
+ */
+ private static InlineBox createCircleBullet(Element element, Styles styles) {
+ final int size = Math.round(0.5f * styles.getFontSize());
+ final int lift = Math.round(0.1f * styles.getFontSize());
+ Drawable drawable = new Drawable() {
+ public void draw(Graphics g, int x, int y) {
+ g.setLineStyle(Graphics.LINE_SOLID);
+ g.setLineWidth(1);
+ g.drawOval(x, y - size - lift, size, size);
+ }
+ public Rectangle getBounds() {
+ return new Rectangle(0, -size - lift, size, size);
+ }
+ };
+ return new DrawableBox(drawable, element);
+ }
+
+ /**
+ * Returns a Drawable that draws a disc-style list item bullet.
+ */
+ private static InlineBox createDiscBullet(Element element, Styles styles) {
+ final int size = Math.round(0.5f * styles.getFontSize());
+ final int lift = Math.round(0.1f * styles.getFontSize());
+ Drawable drawable = new Drawable() {
+ public void draw(Graphics g, int x, int y) {
+ g.fillOval(x, y - size - lift, size, size);
+ }
+ public Rectangle getBounds() {
+ return new Rectangle(0, -size - lift, size, size);
+ }
+ };
+ return new DrawableBox(drawable, element);
+ }
+
+
+ /**
+ * Returns a Drawable that draws a square-style list item bullet.
+ */
+ private static InlineBox createSquareBullet(Element element, Styles styles) {
+ final int size = Math.round(0.5f * styles.getFontSize());
+ final int lift = Math.round(0.1f * styles.getFontSize());
+ Drawable drawable = new Drawable() {
+ public void draw(Graphics g, int x, int y) {
+ g.setLineStyle(Graphics.LINE_SOLID);
+ g.setLineWidth(1);
+ g.drawRect(x, y - size - lift, size, size);
+ }
+ public Rectangle getBounds() {
+ return new Rectangle(0, -size - lift, size, size);
+ }
+ };
+ return new DrawableBox(drawable, element);
+ }
+
+ /**
+ * Returns the vertical distance from the top of this box to the top
+ * of its first line.
+ */
+ int getFirstLineTop(LayoutContext context) {
+ Styles styles = context.getStyleSheet().getStyles(this.getElement());
+ int top = styles.getBorderTopWidth() + styles.getPaddingTop().get(0);
+ Box[] children = this.getChildren();
+ if (children != null &&
+ children.length > 0 &&
+ children[0] instanceof BlockElementBox) {
+ return top + ((BlockElementBox) children[0]).getFirstLineTop(context);
+ } else {
+ return top;
+ }
+ }
+
+ /**
+ * Returns the item number of this box. The item number indicates the
+ * ordinal number of the corresponding element amongst its siblings
+ * starting with 1.
+ */
+ private int getItemNumber() {
+ Element element = this.getElement();
+ Element parent = element.getParent();
+
+ if (parent == null) {
+ return 1;
+ }
+
+ int item = 1;
+ Element[] children = parent.getChildElements();
+ for (int i = 0; i < children.length; i++) {
+ if (children[i] == element) {
+ return item;
+ }
+ if (children[i].getName().equals(element.getName())) {
+ item++;
+ }
+ }
+
+ throw new IllegalStateException();
+ }
+
+ private String getItemNumberString(String style) {
+ int item = getItemNumber();
+ if (style.equals(CSS.DECIMAL_LEADING_ZERO)) {
+ if (item < 10) {
+ return "0" + Integer.toString(item);
+ } else {
+ return Integer.toString(item);
+ }
+ } else if (style.equals(CSS.LOWER_ALPHA) || style.equals(CSS.LOWER_LATIN)) {
+ return this.getAlpha(item);
+ } else if (style.equals(CSS.LOWER_ROMAN)) {
+ return this.getRoman(item);
+ } else if (style.equals(CSS.UPPER_ALPHA) || style.equals(CSS.UPPER_LATIN)) {
+ return this.getAlpha(item).toUpperCase();
+ } else if (style.equals(CSS.UPPER_ROMAN)) {
+ return this.getRoman(item).toUpperCase();
+ } else {
+ return Integer.toString(item);
+ }
+ }
+
+ private String getAlpha(int n) {
+ final String alpha = "abcdefghijklmnopqrstuvwxyz";
+ return String.valueOf(alpha.charAt((n-1) % 26));
+ }
+
+ private String getRoman(int n) {
+ final String[] ones = { "", "i", "ii", "iii", "iv", "v", "vi", "vii", "viii", "ix" };
+ final String[] tens = { "", "x", "xx", "xxx", "xl", "l", "lx", "lxx", "lxxx", "xc" };
+ final String[] hundreds = { "", "c", "cc", "ccc", "cd", "d", "dc", "dcc", "dccc", "cm" };
+ StringBuffer sb = new StringBuffer();
+ for (int i = 0; i < n/1000; i++) {
+ sb.append("m");
+ }
+ sb.append(hundreds[(n/100) % 10]);
+ sb.append(tens[(n/10) % 10]);
+ sb.append(ones[n % 10]);
+ return sb.toString();
+ }
+
+ private static boolean isEnumeratedListStyleType(String s) {
+ return s.equals(CSS.ARMENIAN)
+ || s.equals(CSS.CJK_IDEOGRAPHIC)
+ || s.equals(CSS.DECIMAL)
+ || s.equals(CSS.DECIMAL_LEADING_ZERO)
+ || s.equals(CSS.GEORGIAN)
+ || s.equals(CSS.HEBREW)
+ || s.equals(CSS.HIRAGANA)
+ || s.equals(CSS.HIRAGANA_IROHA)
+ || s.equals(CSS.KATAKANA)
+ || s.equals(CSS.KATAKANA_IROHA)
+ || s.equals(CSS.LOWER_ALPHA)
+ || s.equals(CSS.LOWER_GREEK)
+ || s.equals(CSS.LOWER_LATIN)
+ || s.equals(CSS.LOWER_ROMAN)
+ || s.equals(CSS.UPPER_ALPHA)
+ || s.equals(CSS.UPPER_LATIN)
+ || s.equals(CSS.UPPER_ROMAN);
+ }
+
+}
+
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/layout/BlockPseudoElementBox.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/layout/BlockPseudoElementBox.java
new file mode 100644
index 0000000..73ad4af
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/layout/BlockPseudoElementBox.java
@@ -0,0 +1,157 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.layout;
+
+import java.util.List;
+
+import org.eclipse.wst.xml.vex.core.internal.core.IntRange;
+import org.eclipse.wst.xml.vex.core.internal.css.Styles;
+import org.eclipse.wst.xml.vex.core.internal.dom.Element;
+
+
+/**
+ * Implements a Block
+ */
+public class BlockPseudoElementBox extends AbstractBox implements BlockBox {
+
+ private Element pseudoElement;
+ private BlockBox parent;
+ private ParagraphBox para;
+
+ private int marginTop;
+ private int marginBottom;
+
+ public BlockPseudoElementBox(LayoutContext context, Element pseudoElement, BlockBox parent, int width) {
+
+ this.pseudoElement = pseudoElement;
+ this.parent = parent;
+
+ Styles styles = context.getStyleSheet().getStyles(pseudoElement);
+
+ this.marginTop = styles.getMarginTop().get(width);
+ this.marginBottom = styles.getMarginBottom().get(width);
+
+ int leftInset = styles.getMarginLeft().get(width)
+ + styles.getBorderLeftWidth()
+ + styles.getPaddingLeft().get(width);
+ int rightInset = styles.getMarginRight().get(width)
+ + styles.getBorderRightWidth()
+ + styles.getPaddingRight().get(width);
+
+ int childWidth = width - leftInset - rightInset;
+ List inlines = LayoutUtils.createGeneratedInlines(context, pseudoElement);
+ this.para = ParagraphBox.create(context, pseudoElement, inlines, childWidth);
+
+ this.para.setX(0);
+ this.para.setY(0);
+ this.setWidth(width - leftInset - rightInset);
+ this.setHeight(this.para.getHeight());
+ }
+
+
+ /**
+ * Provide children for {@link AbstractBox#paint}.
+ * @see org.eclipse.wst.xml.vex.core.internal.layout.Box#getChildren()
+ */
+ public Box[] getChildren() {
+ return new Box[] { this.para };
+ }
+
+ /**
+ * @see org.eclipse.wst.xml.vex.core.internal.layout.Box#getElement()
+ */
+ public Element getElement() {
+ return this.pseudoElement;
+ }
+
+ /**
+ * @see org.eclipse.wst.xml.vex.core.internal.layout.BlockBox#getFirstLine()
+ */
+ public LineBox getFirstLine() {
+ throw new IllegalStateException();
+ }
+
+ /**
+ * @see org.eclipse.wst.xml.vex.core.internal.layout.BlockBox#getLastLine()
+ */
+ public LineBox getLastLine() {
+ throw new IllegalStateException();
+ }
+
+ /**
+ * @see org.eclipse.wst.xml.vex.core.internal.layout.BlockBox#getLineEndOffset(int)
+ */
+ public int getLineEndOffset(int offset) {
+ throw new IllegalStateException();
+ }
+
+ /**
+ * @see org.eclipse.wst.xml.vex.core.internal.layout.BlockBox#getLineStartOffset(int)
+ */
+ public int getLineStartOffset(int offset) {
+ throw new IllegalStateException();
+ }
+
+ public int getMarginBottom() {
+ return this.marginBottom;
+ }
+
+ public int getMarginTop() {
+ return this.marginTop;
+ }
+
+ /**
+ * @see org.eclipse.wst.xml.vex.core.internal.layout.BlockBox#getNextLineOffset(org.eclipse.wst.xml.vex.core.internal.layout.LayoutContext, int, int)
+ */
+ public int getNextLineOffset(LayoutContext context, int offset, int x) {
+ throw new IllegalStateException();
+ }
+
+ /**
+ * Returns this box's parent.
+ */
+ public BlockBox getParent() {
+ return this.parent;
+ }
+
+ /**
+ * @see org.eclipse.wst.xml.vex.core.internal.layout.BlockBox#getPreviousLineOffset(org.eclipse.wst.xml.vex.core.internal.layout.LayoutContext, int, int)
+ */
+ public int getPreviousLineOffset(
+ LayoutContext context,
+ int offset,
+ int x) {
+ throw new IllegalStateException();
+ }
+
+ public IntRange layout(LayoutContext context, int top, int bottom) {
+ return null;
+ }
+
+ public void invalidate(boolean direct) {
+ throw new IllegalStateException("invalidate called on a non-element BlockBox");
+ }
+
+
+ /**
+ * Draw boxes before painting our child.
+ * @see org.eclipse.wst.xml.vex.core.internal.layout.Box#paint(org.eclipse.wst.xml.vex.core.internal.layout.LayoutContext, int, int)
+ */
+ public void paint(LayoutContext context, int x, int y) {
+ this.drawBox(context, x, y, this.getParent().getWidth(), true);
+ super.paint(context, x, y);
+ }
+
+ public void setInitialSize(LayoutContext context) {
+ // NOP - size calculated in the ctor
+ }
+
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/layout/Box.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/layout/Box.java
new file mode 100644
index 0000000..caf8b81
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/layout/Box.java
@@ -0,0 +1,167 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.layout;
+
+import org.eclipse.wst.xml.vex.core.internal.core.Caret;
+import org.eclipse.wst.xml.vex.core.internal.core.Insets;
+import org.eclipse.wst.xml.vex.core.internal.dom.Element;
+
+
+/**
+ * Represents a rectangular area in the layout. The height and width of the box
+ * are measured from the inner edges of the box's padding, as in CSS.
+ * Similarly, the (x, y) position of the box are with
+ * respect to the inner top-left corner of the box's padding, and are relative to
+ * the parent's (x, y) position.
+ */
+public interface Box {
+
+
+ /**
+ * Returns true if this box contains the given offset.
+ * @param offset the offset to test
+ */
+ public boolean containsOffset(int offset);
+
+ /**
+ * Returns a Caret object representing the given offset.
+ *
+ * @param context LayoutContext to be used
+ * @param offset offset for which to retrieve the caret
+ */
+ public Caret getCaret(LayoutContext context, int offset);
+
+ /**
+ * Returns an array of this box's children.
+ */
+ public Box[] getChildren();
+
+ /**
+ * Returns the Element with which this box is associated, or null if
+ * there is no such box. The box may directly represent the Element,
+ * or simply use it for formatting information.
+ */
+ public Element getElement();
+
+ /**
+ * Returns the offset of the end of the content that the box covers.
+ */
+ public int getEndOffset();
+
+ /**
+ * Returns the height of the box. For boxes subject to the CSS box model,
+ * this is the inner height, exclusive of margins, borders, and padding.
+ */
+ public int getHeight();
+
+ /**
+ * Return an Insets object representing the total width of margins,
+ * borders, and padding for this box.
+ * @param context LayoutContext being used for this layout.
+ * @param containerWidth Width of the containing box.
+ */
+ public Insets getInsets(LayoutContext context, int containerWidth);
+
+ /**
+ * Returns the offset of the start of the content that the box covers.
+ */
+ public int getStartOffset();
+
+ /**
+ * Returns the width of the box. For boxes subject to the CSS box model,
+ * this is the inner width, exclusive of margins, borders, and padding.
+ */
+ public int getWidth();
+
+ /**
+ * Returns the x-coordinate of the box, relative to its parent.
+ * For boxes subject to the CSS box model, this is the left edge of
+ * the box's content area.
+ */
+ public int getX();
+
+ /**
+ * Returns the y-coordinate of the box, relative to its parent.
+ * For boxes subject to the CSS box model, this is the top edge of
+ * the box's content area.
+ */
+ public int getY();
+
+ /**
+ * Returns true if this box represents a portion of the XML document's
+ * content. If false is returned, the following methods are not supported
+ * by this box.
+ *
+ * <ul>
+ * <li>getCaretShapes()</li>
+ * <li>getEndOffset()</li>
+ * <li>getStartOffset()</li>
+ * <li>viewToModel()</li>
+ * </ul>
+ */
+ public boolean hasContent();
+
+ /**
+ * Returns true if the box is anonymous, that is, it is not directly
+ * associated with an element.
+ */
+ public boolean isAnonymous();
+
+ /**
+ * Draws the box's content in the given Graphics context.
+ *
+ * @param context <code>LayoutContext</code> containing the
+ * <code>Graphics</code> object into which the box should be
+ * painted
+ * @param x the x-offset at which the box should be painted
+ * @param y the y-offset at which the box should be painted
+ */
+ public void paint(LayoutContext context, int x, int y);
+
+ /**
+ * Sets the height of this box.
+ *
+ * @param height new height of the box
+ */
+ public void setHeight(int height);
+
+ /**
+ * Sets the width of this box.
+ *
+ * @param width new width of the box
+ */
+ public void setWidth(int width);
+
+ /**
+ * Sets the x-coordinate of the top-left corner of the box.
+ * @param x the new x-coordinate
+ */
+ public void setX(int x);
+
+ /**
+ * Sets the y-coordinate of the top-left corner of the box.
+ * @param y the new y-coordinate
+ */
+ public void setY(int y);
+
+ /**
+ * Returns the offset in the content closest to the given view position.
+ *
+ * @param context <code>LayoutContext</code> for this box tree
+ * @param x x offset of the view position for which the model
+ * offset is to be determined.
+ * @param y y offset of the view position for which the model
+ * offset is to be determined.
+ */
+ public int viewToModel(LayoutContext context, int x, int y);
+
+}
+
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/layout/BoxFactory.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/layout/BoxFactory.java
new file mode 100644
index 0000000..5b6727f
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/layout/BoxFactory.java
@@ -0,0 +1,35 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.layout;
+
+import java.io.Serializable;
+
+import org.eclipse.wst.xml.vex.core.internal.dom.Element;
+
+
+
+/**
+ * Interface to an object that creates boxes from elements. Implementations
+ * of this interface must be serializable.
+ */
+public interface BoxFactory extends Serializable {
+
+ /**
+ * Creates a box given an element.
+ * @param context CSS styles for the new element
+ * @param element Element for which the box should be created.
+ * @param parent Parent box for the new box.
+ * @param containerWidth Width of the box to be created.
+ */
+ public Box createBox(LayoutContext context, Element element, BlockBox parent, int containerWidth);
+
+}
+
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/layout/CompositeInlineBox.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/layout/CompositeInlineBox.java
new file mode 100644
index 0000000..fd17191
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/layout/CompositeInlineBox.java
@@ -0,0 +1,198 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.layout;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.LinkedList;
+import java.util.List;
+
+import org.eclipse.wst.xml.vex.core.internal.core.Caret;
+import org.eclipse.wst.xml.vex.core.internal.core.FontMetrics;
+import org.eclipse.wst.xml.vex.core.internal.core.FontResource;
+import org.eclipse.wst.xml.vex.core.internal.core.Graphics;
+import org.eclipse.wst.xml.vex.core.internal.css.Styles;
+
+
+/**
+ * InlineBox consisting of several children. This is the parent class
+ * of InlineElementBox and LineBox, and implements the split method.
+ */
+public abstract class CompositeInlineBox extends AbstractBox implements InlineBox {
+
+ /**
+ * Returns true if any of the children have content.
+ */
+ public boolean hasContent() {
+ Box[] children = this.getChildren();
+ for (int i = 0; i < children.length; i++) {
+ if (children[i].hasContent()) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public boolean isEOL() {
+ Box[] children = this.getChildren();
+ return children.length > 0 && ((InlineBox) children[children.length-1]).isEOL();
+ }
+
+ /**
+ * @see org.eclipse.wst.xml.vex.core.internal.layout.Box#getCaret(org.eclipse.wst.xml.vex.core.internal.layout.LayoutContext, int)
+ */
+ public Caret getCaret(LayoutContext context, int offset) {
+
+ int x = 0;
+ Box[] children = this.getChildren();
+
+ // we want the caret to be to the right of any leading static boxes...
+ int start = 0;
+ while (start < children.length && !children[start].hasContent()) {
+ x += children[start].getWidth();
+ start++;
+ }
+
+ // ...and to the left of any trailing static boxes
+ int end = children.length;
+ while (end < 0 && !children[end - 1].hasContent()) {
+ end--;
+ }
+
+ for (int i = start; i < end; i++) {
+ Box child = children[i];
+ if (child.hasContent()) {
+ if (offset < child.getStartOffset()) {
+ break;
+ } else if (offset <= child.getEndOffset()) {
+ Caret caret = child.getCaret(context, offset);
+ caret.translate(child.getX(), child.getY());
+ return caret;
+ }
+ }
+ x += child.getWidth();
+ }
+
+ Graphics g = context.getGraphics();
+ Styles styles = context.getStyleSheet().getStyles(this.getElement());
+
+ FontResource font = g.createFont(styles.getFont());
+ FontResource oldFont = g.setFont(font);
+ FontMetrics fm = g.getFontMetrics();
+ int height = fm.getAscent() + fm.getDescent();
+ g.setFont(oldFont);
+ font.dispose();
+
+ int lineHeight = styles.getLineHeight();
+ int y = (lineHeight - height) / 2;
+ return new TextCaret(x, y, height);
+ }
+
+
+ /**
+ * @see org.eclipse.wst.xml.vex.core.internal.layout.InlineBox#split(org.eclipse.wst.xml.vex.core.internal.layout.LayoutContext, int, boolean)
+ */
+ public Pair split(LayoutContext context, int maxWidth, boolean force) {
+
+ // list of children that have yet to be added to the left side
+ LinkedList rights = new LinkedList(Arrays.asList(this.getChildren()));
+
+ // pending is a list of inlines we are trying to add to the left side
+ // but which cannot end at a split
+ List pending = new ArrayList();
+
+ // list of inlines that make up the left side
+ List lefts = new ArrayList();
+
+ int remaining = maxWidth;
+ boolean eol = false;
+
+ while (!rights.isEmpty() && remaining >= 0) {
+ InlineBox inline = (InlineBox) rights.removeFirst();
+ InlineBox.Pair pair = inline.split(context, remaining, force && lefts.isEmpty());
+
+ if (pair.getLeft() != null) {
+ lefts.addAll(pending);
+ pending.clear();
+ lefts.add(pair.getLeft());
+ remaining -= pair.getLeft().getWidth();
+ }
+
+ if (pair.getRight() != null) {
+ pending.add(pair.getRight());
+ remaining -= pair.getRight().getWidth();
+ }
+
+ if (pair.getLeft() != null && pair.getLeft().isEOL()) {
+ eol = true;
+ break;
+ }
+
+ }
+
+ if (((force && lefts.isEmpty()) || remaining >= 0) && !eol) {
+ lefts.addAll(pending);
+ } else {
+ rights.addAll(0, pending);
+ }
+
+ InlineBox[] leftKids = (InlineBox[]) lefts.toArray(new InlineBox[lefts.size()]);
+ InlineBox[] rightKids = (InlineBox[]) rights.toArray(new InlineBox[rights.size()]);
+
+ return this.split(context, leftKids, rightKids);
+ }
+
+
+ /**
+ * Creates a Pair of InlineBoxes, each with its own set of children.
+ * @param context LayoutContext used for this layout.
+ * @param lefts Child boxes to be given to the left box.
+ * @param rights Child boxes to be given to the right box.
+ * @return
+ */
+ protected abstract Pair split(LayoutContext context, InlineBox[] lefts, InlineBox[] rights);
+
+ /**
+ * @see org.eclipse.wst.xml.vex.core.internal.layout.Box#viewToModel(org.eclipse.wst.xml.vex.core.internal.layout.LayoutContext, int, int)
+ */
+ public int viewToModel(LayoutContext context, int x, int y) {
+
+ if (!this.hasContent()) {
+ throw new RuntimeException("Oops. Calling viewToModel on a line with no content");
+ }
+
+ Box closestContentChild = null;
+ int delta = Integer.MAX_VALUE;
+ Box[] children = this.getChildren();
+ for (int i = 0; i < children.length; i++) {
+ Box child = children[i];
+ if (child.hasContent()) {
+ int newDelta = 0;
+ if (x < child.getX()) {
+ newDelta = child.getX() - x;
+ } else if (x > child.getX() + child.getWidth()) {
+ newDelta = x - (child.getX() + child.getWidth());
+ }
+ if (newDelta < delta) {
+ delta = newDelta;
+ closestContentChild = child;
+ }
+ }
+ }
+
+ return closestContentChild.viewToModel(
+ context,
+ x - closestContentChild.getX(),
+ y - closestContentChild.getY());
+ }
+
+
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/layout/CssBoxFactory.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/layout/CssBoxFactory.java
new file mode 100644
index 0000000..ae87e42
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/layout/CssBoxFactory.java
@@ -0,0 +1,45 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.layout;
+
+import org.eclipse.wst.xml.vex.core.internal.css.CSS;
+import org.eclipse.wst.xml.vex.core.internal.css.Styles;
+import org.eclipse.wst.xml.vex.core.internal.dom.Element;
+
+
+
+/**
+ * Implementation of the BoxFactory interface that returns boxes that
+ * represent CSS semantics.
+ */
+public class CssBoxFactory implements BoxFactory {
+
+ private static final long serialVersionUID = -6882526795866485074L;
+
+ /**
+ * Class constructor.
+ */
+ public CssBoxFactory() {
+ }
+
+ public Box createBox(LayoutContext context, Element element, BlockBox parent, int containerWidth) {
+ Styles styles = context.getStyleSheet().getStyles(element);
+ if (styles.getDisplay().equals(CSS.TABLE)) {
+ return new TableBox(context, parent, element);
+ } else if (styles.isBlock()) {
+ return new BlockElementBox(context, parent, element);
+ } else {
+ throw new RuntimeException("Unexpected display property: " + styles.getDisplay());
+ }
+ }
+
+}
+
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/layout/DocumentTextBox.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/layout/DocumentTextBox.java
new file mode 100644
index 0000000..4af9251
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/layout/DocumentTextBox.java
@@ -0,0 +1,245 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.layout;
+
+import org.eclipse.wst.xml.vex.core.internal.core.ColorResource;
+import org.eclipse.wst.xml.vex.core.internal.core.FontResource;
+import org.eclipse.wst.xml.vex.core.internal.core.Graphics;
+import org.eclipse.wst.xml.vex.core.internal.css.Styles;
+import org.eclipse.wst.xml.vex.core.internal.dom.Document;
+import org.eclipse.wst.xml.vex.core.internal.dom.Element;
+import org.eclipse.wst.xml.vex.core.internal.dom.Text;
+
+
+/**
+ * A TextBox that gets its text from the document.
+ * Represents text which is editable within the VexWidget.
+ */
+public class DocumentTextBox extends TextBox {
+
+ private int startRelative;
+ private int endRelative;
+
+ /**
+ * Class constructor, accepting a Text object.
+ * @param context LayoutContext in use
+ * @param element Element being used
+ * @param text
+ */
+ public DocumentTextBox(LayoutContext context, Element element, Text text) {
+ this(context, element, text.getStartOffset(), text.getEndOffset());
+ }
+
+ /**
+ * Class constructor.
+ *
+ * @param context LayoutContext used to calculate the box's size.
+ * @param element Element that directly contains the text.
+ * @param startOffset start offset of the text
+ * @param endOffset end offset of the text
+ */
+ public DocumentTextBox(LayoutContext context, Element element, int startOffset, int endOffset) {
+ super(element);
+
+ if (startOffset >= endOffset) {
+ throw new IllegalStateException("DocumentTextBox: startOffset (" + startOffset + ") >= endOffset (" + endOffset + ")");
+ }
+
+ this.startRelative = startOffset - element.getStartOffset();
+ this.endRelative = endOffset - element.getStartOffset();
+ this.calculateSize(context);
+
+ if (this.getText().length() < (endOffset - startOffset)) {
+ throw new IllegalStateException();
+ }
+ }
+
+ /**
+ * @see org.eclipse.wst.xml.vex.core.internal.layout.Box#getEndOffset()
+ */
+ public int getEndOffset() {
+ if (this.endRelative == -1) {
+ return -1;
+ } else {
+ return this.getElement().getStartOffset() + this.endRelative - 1;
+ }
+ }
+
+ /**
+ * @see org.eclipse.wst.xml.vex.core.internal.layout.Box#getStartOffset()
+ */
+ public int getStartOffset() {
+ if (this.startRelative == -1) {
+ return -1;
+ } else {
+ return this.getElement().getStartOffset() + this.startRelative;
+ }
+ }
+
+ /**
+ * @see org.eclipse.wst.xml.vex.core.internal.layout.TextBox#getText()
+ */
+ public String getText() {
+ Document doc = this.getElement().getDocument();
+ return doc.getText(this.getStartOffset(), this.getEndOffset() + 1);
+ }
+
+ /**
+ * @see org.eclipse.wst.xml.vex.core.internal.layout.Box#hasContent()
+ */
+ public boolean hasContent() {
+ return true;
+ }
+
+
+ /**
+ * @see org.eclipse.wst.xml.vex.core.internal.layout.Box#paint(org.eclipse.wst.xml.vex.core.internal.layout.LayoutContext, int, int)
+ */
+ public void paint(LayoutContext context, int x, int y) {
+
+ Styles styles = context.getStyleSheet().getStyles(this.getElement());
+ Graphics g = context.getGraphics();
+
+ FontResource font = g.createFont(styles.getFont());
+ FontResource oldFont = g.setFont(font);
+ ColorResource foreground = g.createColor(styles.getColor());
+ ColorResource oldForeground = g.setColor(foreground);
+// ColorResource background = g.createColor(styles.getBackgroundColor());
+// ColorResource oldBackground = g.setBackgroundColor(background);
+
+ char[] chars = this.getText().toCharArray();
+
+ if (chars.length < this.getEndOffset() - this.getStartOffset()) {
+ throw new IllegalStateException();
+ }
+
+ if (chars.length == 0) {
+ throw new IllegalStateException();
+ }
+
+ int start = 0;
+ int end = chars.length;
+ if (chars[end - 1] == NEWLINE_CHAR) {
+ end--;
+ }
+ int selStart = context.getSelectionStart() - this.getStartOffset();
+ selStart = Math.min(Math.max(selStart, start), end);
+ int selEnd = context.getSelectionEnd() - this.getStartOffset();
+ selEnd = Math.min(Math.max(selEnd, start), end);
+
+ // text before selection
+ if (start < selStart) {
+ g.drawChars(chars, start, selStart - start, x, y);
+ String s = new String(chars, start, selStart - start);
+ paintTextDecoration(context, styles, s, x, y);
+ }
+
+ // text after selection
+ if (selEnd < end) {
+ int x1 = x + g.charsWidth(chars, 0, selEnd);
+ g.drawChars(chars, selEnd, end - selEnd, x1, y);
+ String s = new String(chars, selEnd, end - selEnd);
+ paintTextDecoration(context, styles, s, x1, y);
+ }
+
+ // text within selection
+ if (selStart < selEnd) {
+ String s = new String(chars, selStart, selEnd - selStart);
+ int x1 = x + g.charsWidth(chars, 0, selStart);
+ this.paintSelectedText(context, s, x1, y);
+ paintTextDecoration(context, styles, s, x1, y);
+ }
+
+ g.setFont(oldFont);
+ g.setColor(oldForeground);
+// g.setBackgroundColor(oldBackground);
+ font.dispose();
+ foreground.dispose();
+// background.dispose();
+ }
+
+
+ /**
+ * @see org.eclipse.wst.xml.vex.core.internal.layout.TextBox#splitAt(int)
+ */
+ public Pair splitAt(LayoutContext context, int offset) {
+
+ if (offset < 0 || offset > (this.endRelative - this.startRelative)) {
+ throw new IllegalStateException();
+ }
+
+ int split = this.getStartOffset() + offset;
+
+ DocumentTextBox left;
+ if (offset == 0) {
+ left = null;
+ } else {
+ left = new DocumentTextBox(context, this.getElement(), this.getStartOffset(), split);
+ }
+
+ InlineBox right;
+ if (split == this.getEndOffset() + 1) {
+ right = null;
+ } else {
+ right = new DocumentTextBox(context, this.getElement(), split, this.getEndOffset() + 1);
+ }
+ return new Pair(left, right);
+ }
+
+ /**
+ * @see org.eclipse.wst.xml.vex.core.internal.layout.Box#viewToModel(org.eclipse.wst.xml.vex.core.internal.layout.LayoutContext, int, int)
+ */
+ public int viewToModel(LayoutContext context, int x, int y) {
+
+ Graphics g = context.getGraphics();
+ Styles styles = context.getStyleSheet().getStyles(this.getElement());
+ FontResource font = g.createFont(styles.getFont());
+ FontResource oldFont = g.setFont(font);
+ char[] chars = this.getText().toCharArray();
+
+ if (this.getWidth() <= 0) {
+ return this.getStartOffset();
+ }
+
+ // first, get an estimate based on x / width
+ int offset = (x / this.getWidth()) * chars.length;
+ offset = Math.max(0, offset);
+ offset = Math.min(chars.length, offset);
+
+ int delta = Math.abs(x - g.charsWidth(chars, 0, offset));
+
+ // Search backwards
+ while (offset > 0) {
+ int newDelta = Math.abs(x - g.charsWidth(chars, 0, offset-1));
+ if (newDelta > delta) {
+ break;
+ }
+ delta = newDelta;
+ offset--;
+ }
+
+ // Search forwards
+ while (offset < chars.length-1) {
+ int newDelta = Math.abs(x - g.charsWidth(chars, 0, offset+1));
+ if (newDelta > delta) {
+ break;
+ }
+ delta = newDelta;
+ offset++;
+ }
+
+ g.setFont(oldFont);
+ font.dispose();
+ return this.getStartOffset() + offset;
+ }
+
+
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/layout/DrawableBox.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/layout/DrawableBox.java
new file mode 100644
index 0000000..0853576
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/layout/DrawableBox.java
@@ -0,0 +1,139 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.layout;
+
+import org.eclipse.wst.xml.vex.core.internal.core.ColorResource;
+import org.eclipse.wst.xml.vex.core.internal.core.Drawable;
+import org.eclipse.wst.xml.vex.core.internal.core.FontMetrics;
+import org.eclipse.wst.xml.vex.core.internal.core.FontResource;
+import org.eclipse.wst.xml.vex.core.internal.core.Graphics;
+import org.eclipse.wst.xml.vex.core.internal.core.Rectangle;
+import org.eclipse.wst.xml.vex.core.internal.css.Styles;
+import org.eclipse.wst.xml.vex.core.internal.dom.Element;
+
+
+/**
+ * An inline box that draws a Drawable object. The drawable is drawn relative
+ * to the text baseline, therefore it should draw using mostly negative y-coordinates.
+ */
+public class DrawableBox extends AbstractBox implements InlineBox {
+
+ public static final byte NO_MARKER = 0;
+ public static final byte START_MARKER = 1;
+ public static final byte END_MARKER = 2;
+
+ private Drawable drawable;
+ private Element element;
+ private byte marker;
+
+ /**
+ * Class constructor.
+ * @param drawable Drawable to draw.
+ * @param element Element whose styles determine the color of the drawable.
+ */
+ public DrawableBox(Drawable drawable, Element element) {
+ this(drawable, element, NO_MARKER);
+ }
+
+ /**
+ * Class constructor. This constructor is called when creating a
+ * DrawableBox that represents the start or end marker of an
+ * inline element.
+ * @param drawable Drawable to draw.
+ * @param element Element whose styles determine the color of the drawable.
+ * @param marker which marker should be drawn. Must be one of NO_MARKER,
+ * START_MARKER, or END_MARKER.
+ */
+ public DrawableBox(Drawable drawable, Element element, byte marker) {
+ this.drawable = drawable;
+ this.element = element;
+ this.marker = marker;
+ Rectangle bounds = drawable.getBounds();
+ this.setWidth(bounds.getWidth());
+ this.setHeight(bounds.getHeight());
+ }
+
+ /**
+ * @see org.eclipse.wst.xml.vex.core.internal.layout.InlineBox#getBaseline()
+ */
+ public int getBaseline() {
+ return 0;
+ }
+
+ /**
+ * Returns the element that controls the styling for this text element.
+ */
+ public Element getElement() {
+ return this.element;
+ }
+
+ public boolean isEOL() {
+ return false;
+ }
+
+ /**
+ * @see org.eclipse.wst.xml.vex.core.internal.layout.InlineBox#split(org.eclipse.wst.xml.vex.core.internal.layout.LayoutContext, int, boolean)
+ */
+ public Pair split(LayoutContext context, int maxWidth, boolean force) {
+ return new Pair(null, this);
+ }
+
+ /**
+ * Draw the drawable. The foreground color of the context's Graphics is
+ * set before calling the drawable's draw method.
+ */
+ public void paint(LayoutContext context, int x, int y) {
+
+ Graphics g = context.getGraphics();
+ Styles styles = context.getStyleSheet().getStyles(this.element);
+
+ boolean drawSelected = false;
+ if (this.marker == START_MARKER) {
+ drawSelected =
+ this.getElement().getStartOffset() >= context.getSelectionStart()
+ && this.getElement().getStartOffset() + 1 <= context.getSelectionEnd();
+ } else if (this.marker == END_MARKER) {
+ drawSelected =
+ this.getElement().getEndOffset() >= context.getSelectionStart()
+ && this.getElement().getEndOffset() + 1 <= context.getSelectionEnd();
+ }
+
+ FontResource font = g.createFont(styles.getFont());
+ ColorResource color = g.createColor(styles.getColor());
+
+ FontResource oldFont = g.setFont(font);
+ ColorResource oldColor = g.setColor(color);
+
+ FontMetrics fm = g.getFontMetrics();
+
+ if (drawSelected) {
+ Rectangle bounds = this.drawable.getBounds();
+ g.setColor(g.getSystemColor(ColorResource.SELECTION_BACKGROUND));
+ g.fillRect(x + bounds.getX(), y - fm.getAscent(), bounds.getWidth(), styles.getLineHeight());
+ g.setColor(g.getSystemColor(ColorResource.SELECTION_FOREGROUND));
+ }
+
+ this.drawable.draw(g, x, y);
+
+ g.setFont(oldFont);
+ g.setColor(oldColor);
+ font.dispose();
+ color.dispose();
+ }
+
+ /**
+ * @see java.lang.Object#toString()
+ */
+ public String toString() {
+ return "[shape]";
+ }
+
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/layout/ElementOrRangeCallback.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/layout/ElementOrRangeCallback.java
new file mode 100644
index 0000000..d950b5b
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/layout/ElementOrRangeCallback.java
@@ -0,0 +1,18 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.layout;
+
+import org.eclipse.wst.xml.vex.core.internal.dom.Element;
+
+public interface ElementOrRangeCallback {
+ public void onElement(Element child, String displayStyle);
+ public void onRange(Element parent, int startOffset, int endOffset);
+}
\ No newline at end of file
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/layout/HCaret.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/layout/HCaret.java
new file mode 100644
index 0000000..2dc2df0
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/layout/HCaret.java
@@ -0,0 +1,56 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.layout;
+
+import org.eclipse.wst.xml.vex.core.internal.core.Caret;
+import org.eclipse.wst.xml.vex.core.internal.core.Color;
+import org.eclipse.wst.xml.vex.core.internal.core.ColorResource;
+import org.eclipse.wst.xml.vex.core.internal.core.Graphics;
+import org.eclipse.wst.xml.vex.core.internal.core.Rectangle;
+
+/**
+ * A horizontal caret representing the insertion point between two block boxes.
+ */
+public class HCaret extends Caret {
+
+ private static final int LINE_WIDTH = 2;
+
+ /**
+ * Class constructor.
+ * @param x x-coordinate of the top left corner of the caret
+ * @param y y-coordinate of the top left corner of the caret
+ * @param length Horizontal length of the caret.
+ */
+ public HCaret(int x, int y, int length) {
+ super(x, y);
+ this.length = length;
+ }
+
+ public void draw(Graphics g, Color color) {
+ ColorResource newColor = g.createColor(color);
+ ColorResource oldColor = g.setColor(newColor);
+ g.fillRect(this.getX(), this.getY(), this.length, LINE_WIDTH);
+ g.setColor(oldColor);
+ newColor.dispose();
+ }
+
+ /**
+ * @see org.eclipse.wst.xml.vex.core.internal.core.Caret#getBounds()
+ */
+ public Rectangle getBounds() {
+ return new Rectangle(this.getX(), this.getY(), this.length, LINE_WIDTH);
+ }
+
+
+ //====================================================== PRIVATE
+
+ private int length;
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/layout/InlineBox.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/layout/InlineBox.java
new file mode 100644
index 0000000..3e336a9
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/layout/InlineBox.java
@@ -0,0 +1,86 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.layout;
+
+/**
+ * Represents an inline box. Inline boxes are the children of line boxes.
+ */
+public interface InlineBox extends Box {
+
+ /**
+ * Represents a pair of inline boxes as returned by the <code>split</code>
+ * method.
+ */
+ public class Pair {
+ private InlineBox left;
+ private InlineBox right;
+
+ /**
+ * Class constructor.
+ * @param left box to the left of the split
+ * @param right box to the right of the split
+ */
+ public Pair(InlineBox left, InlineBox right) {
+ this.left = left;
+ this.right = right;
+ }
+
+ /**
+ * Returns the box to the left of the split.
+ */
+ public InlineBox getLeft() {
+ return this.left;
+ }
+
+ /**
+ * Returns the box to the right of the split.
+ */
+ public InlineBox getRight() {
+ return this.right;
+ }
+ }
+
+ /**
+ * Returns the distance from the top of the inline box to the baseline.
+ */
+ public int getBaseline();
+
+ /**
+ * Returns true if this inline box must be the last box on the current line.
+ */
+ public boolean isEOL();
+
+ /**
+ * Splits this inline box into two. If <code>force</code> is false, this
+ * method should find a natural split point (e.g. after a space) and
+ * return two boxes representing a split at that point. The width of the
+ * last box must not exceed <code>maxWidth</code>. If no such natural split
+ * exists, null should be returned as the left box and <code>this</code>
+ * returned as the right box.
+ *
+ * <p>If <code>force</code> is true, it means we are adding the first inline
+ * box to a line, therefore we must return something as the left box.
+ * In some cases, we may find a suboptimal split (e.g. between characters)
+ * that satisfies this. In other cases, <code>this</code> should be returned
+ * as the left box even though it exceeds maxWidth.</p>
+ *
+ * <p>If the entire box fits within <code>maxWidth</code>, it should only
+ * be returned as the left box if it can end a line; otherwise, it should
+ * be returned as the right box. Most implementations <i>cannot</i> end a line
+ * (one notable exception being a text box ending in whitespace) and should
+ * therefore return themselves as the right box.</p>
+ *
+ * @param context the layout context to be used.
+ * @param maxWidth Maximum width of the left part of the box.
+ * @param force if true, force a suboptimal split
+ */
+ public Pair split(LayoutContext context, int maxWidth, boolean force);
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/layout/InlineElementBox.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/layout/InlineElementBox.java
new file mode 100644
index 0000000..6a5346d
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/layout/InlineElementBox.java
@@ -0,0 +1,369 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.layout;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.eclipse.wst.xml.vex.core.internal.core.Drawable;
+import org.eclipse.wst.xml.vex.core.internal.core.FontMetrics;
+import org.eclipse.wst.xml.vex.core.internal.core.FontResource;
+import org.eclipse.wst.xml.vex.core.internal.core.Graphics;
+import org.eclipse.wst.xml.vex.core.internal.core.Rectangle;
+import org.eclipse.wst.xml.vex.core.internal.css.Styles;
+import org.eclipse.wst.xml.vex.core.internal.dom.Element;
+import org.eclipse.wst.xml.vex.core.internal.dom.Node;
+import org.eclipse.wst.xml.vex.core.internal.dom.Text;
+
+
+/**
+ * An inline box that represents an inline element. This box is responsible
+ * for creating and laying out its child boxes.
+ */
+public class InlineElementBox extends CompositeInlineBox {
+
+ private Element element;
+ private InlineBox[] children;
+ private InlineBox firstContentChild = null;
+ private InlineBox lastContentChild = null;
+ private int baseline;
+ private int halfLeading;
+
+
+
+ /**
+ * Class constructor, called by the createInlineBoxes static factory method.
+ * @param context LayoutContext to use.
+ * @param element Element that generated this box
+ * @param startOffset Start offset of the range being rendered, which may be arbitrarily
+ * before or inside the element.
+ * @param endOffset End offset of the range being rendered, which may be arbitrarily
+ * after or inside the element.
+ */
+ private InlineElementBox(LayoutContext context, Element element, int startOffset, int endOffset) {
+
+ this.element = element;
+
+ List childList = new ArrayList();
+
+ Styles styles = context.getStyleSheet().getStyles(element);
+
+ if (startOffset <= element.getStartOffset()) {
+
+ // space for the left margin/border/padding
+ int space = styles.getMarginLeft().get(0)
+ + styles.getBorderLeftWidth()
+ + styles.getPaddingLeft().get(0);
+
+ if (space > 0) {
+ childList.add(new SpaceBox(space, 1));
+ }
+
+ // :before content
+ Element beforeElement = context.getStyleSheet().getBeforeElement(element);
+ if (beforeElement != null) {
+ childList.addAll(LayoutUtils.createGeneratedInlines(context, beforeElement));
+ }
+
+ // left marker
+ childList.add(createLeftMarker(element, styles));
+ }
+
+ InlineBoxes inlines = createInlineBoxes(context, element, startOffset, endOffset);
+ childList.addAll(inlines.boxes);
+ this.firstContentChild = inlines.firstContentBox;
+ this.lastContentChild = inlines.lastContentBox;
+
+ if (endOffset > element.getEndOffset()) {
+
+ childList.add(new PlaceholderBox(context, element, element.getEndOffset() - element.getStartOffset()));
+
+ // trailing marker
+ childList.add(createRightMarker(element, styles));
+
+ // :after content
+ Element afterElement = context.getStyleSheet().getAfterElement(element);
+ if (afterElement != null) {
+ childList.addAll(LayoutUtils.createGeneratedInlines(context, afterElement));
+ }
+
+ // space for the right margin/border/padding
+ int space = styles.getMarginRight().get(0)
+ + styles.getBorderRightWidth()
+ + styles.getPaddingRight().get(0);
+
+ if (space > 0) {
+ childList.add(new SpaceBox(space, 1));
+ }
+ }
+
+ this.children = (InlineBox[]) childList.toArray(new InlineBox[childList.size()]);
+ this.layout(context);
+ }
+
+
+
+
+
+ /**
+ * Class constructor. This constructor is called by the split method.
+ * @param context LayoutContext used for the layout.
+ * @param element Element to which this box applies.
+ * @param children Child boxes.
+ */
+ private InlineElementBox(LayoutContext context, Element element, InlineBox[] children) {
+ this.element = element;
+ this.children = children;
+ this.layout(context);
+ for (int i = 0; i < children.length; i++) {
+ InlineBox child = children[i];
+ if (child.hasContent()) {
+ if (this.firstContentChild == null) {
+ this.firstContentChild = child;
+ }
+ this.lastContentChild = child;
+ }
+ }
+ }
+
+ /**
+ * @see org.eclipse.wst.xml.vex.core.internal.layout.InlineBox#getBaseline()
+ */
+ public int getBaseline() {
+ return this.baseline;
+ }
+
+ /**
+ * @see org.eclipse.wst.xml.vex.core.internal.layout.Box#getChildren()
+ */
+ public Box[] getChildren() {
+ return this.children;
+ }
+
+ /**
+ * Returns the element associated with this box.
+ */
+ public Element getElement() {
+ return this.element;
+ }
+
+ /**
+ * @see org.eclipse.wst.xml.vex.core.internal.layout.Box#getEndOffset()
+ */
+ public int getEndOffset() {
+ if (this.lastContentChild == null) {
+ return this.getElement().getEndOffset();
+ } else {
+ return this.lastContentChild.getEndOffset();
+ }
+ }
+
+ /**
+ * @see org.eclipse.wst.xml.vex.core.internal.layout.Box#getStartOffset()
+ */
+ public int getStartOffset() {
+ if (this.firstContentChild == null) {
+ return this.getElement().getStartOffset();
+ } else {
+ return this.firstContentChild.getStartOffset();
+ }
+ }
+
+ /**
+ * Override to paint background and borders.
+ * @see org.eclipse.wst.xml.vex.core.internal.layout.AbstractBox#paint(org.eclipse.wst.xml.vex.core.internal.layout.LayoutContext, int, int)
+ */
+ public void paint(LayoutContext context, int x, int y) {
+ this.drawBox(context, x, y, 0, true); // TODO CSS violation
+ super.paint(context, x, y);
+ }
+
+
+
+ public Pair split(LayoutContext context, InlineBox[] lefts, InlineBox[] rights) {
+
+ InlineElementBox left = null;
+ InlineElementBox right = null;
+
+ if (lefts.length > 0 || rights.length == 0) {
+ left = new InlineElementBox(context, this.getElement(), lefts);
+ }
+
+ if (rights.length > 0) {
+ right = new InlineElementBox(context, this.getElement(), rights);
+ }
+
+ return new Pair(left, right);
+ }
+
+ public String toString() {
+ StringBuffer sb = new StringBuffer();
+ if (this.getStartOffset() == this.getElement().getStartOffset() + 1) {
+ sb.append("<");
+ sb.append(this.getElement().getName());
+ sb.append(">");
+ }
+ Box[] children = this.getChildren();
+ for (int i = 0; i < children.length; i++) {
+ sb.append(children[i]);
+ }
+ if (this.getEndOffset() == this.getElement().getEndOffset()) {
+ sb.append("</");
+ sb.append(this.getElement().getName());
+ sb.append(">");
+ }
+ return sb.toString();
+ }
+
+ /**
+ * Holds the results of the createInlineBoxes method.
+ */
+ static class InlineBoxes {
+
+ /** List of generated boxes */
+ public List boxes = new ArrayList();
+
+ /** First generated box that has content */
+ public InlineBox firstContentBox;
+
+ /** Last generated box that has content */
+ public InlineBox lastContentBox;
+ }
+
+ /**
+ * Creates a list of inline boxes given a range of offsets. This method is
+ * used when creating both ParagraphBoxes and InlineElementBoxes.
+ * @param context LayoutContext to be used.
+ * @param containingElement Element containing both offsets
+ * @param startOffset The start of the range to convert to inline boxes.
+ * @param endOffset The end of the range to convert to inline boxes.
+ * @return
+ */
+ static InlineBoxes createInlineBoxes(LayoutContext context, Element containingElement, int startOffset, int endOffset) {
+
+ InlineBoxes result = new InlineBoxes();
+
+ Node[] nodes = containingElement.getChildNodes();
+ for (int i = 0; i < nodes.length; i++) {
+
+ Node node = nodes[i];
+ InlineBox child;
+
+ if (node.getStartOffset() >= endOffset) {
+ break;
+ } else if (node instanceof Text) {
+
+ // This check is different for Text and Element, so we have to
+ // do it here and below, too.
+ if (node.getEndOffset() <= startOffset) {
+ continue;
+ }
+
+ int start = Math.max(startOffset, node.getStartOffset());
+ int end = Math.min(endOffset, node.getEndOffset());
+ child = new DocumentTextBox(context, containingElement, start, end);
+
+ } else {
+
+ if (node.getEndOffset() < startOffset) {
+ continue;
+ }
+
+ Element childElement = (Element) node;
+ InlineBox placeholder = new PlaceholderBox(context, containingElement, childElement.getStartOffset() - containingElement.getStartOffset());
+ result.boxes.add(placeholder);
+ if (result.firstContentBox == null) {
+ result.firstContentBox = placeholder;
+ }
+ child = new InlineElementBox(context, childElement, startOffset, endOffset);
+ }
+
+ if (result.firstContentBox == null) {
+ result.firstContentBox = child;
+ }
+
+ result.lastContentBox = child;
+
+ result.boxes.add(child);
+ }
+
+ return result;
+ }
+
+ //========================================================== PRIVATE
+
+
+ private static InlineBox createLeftMarker(Element element, Styles styles) {
+ final int size = Math.round(0.5f * styles.getFontSize());
+ final int lift = Math.round(0.1f * styles.getFontSize());
+ Drawable drawable = new Drawable() {
+ public void draw(Graphics g, int x, int y) {
+ g.setLineStyle(Graphics.LINE_SOLID);
+ g.setLineWidth(1);
+ y -= lift;
+ g.drawLine(x, y - size, x, y);
+ g.drawLine(x, y, x + size - 1, y - size/2);
+ g.drawLine(x + size - 1, y - size/2, x, y - size);
+ }
+ public Rectangle getBounds() {
+ return new Rectangle(0, -size, size, size);
+ }
+ };
+ return new DrawableBox(drawable, element, DrawableBox.START_MARKER);
+ }
+
+ private static InlineBox createRightMarker(Element element, Styles styles) {
+ final int size = Math.round(0.5f * styles.getFontSize());
+ final int lift = Math.round(0.1f * styles.getFontSize());
+ Drawable drawable = new Drawable() {
+ public void draw(Graphics g, int x, int y) {
+ g.setLineStyle(Graphics.LINE_SOLID);
+ g.setLineWidth(1);
+ y -= lift;
+ g.drawLine(x + size - 1, y - size, x + size - 1, y);
+ g.drawLine(x + size - 1, y, x, y - size/2);
+ g.drawLine(x, y - size/2, x + size - 1, y - size);
+ }
+ public Rectangle getBounds() {
+ return new Rectangle(0, -size, size, size);
+ }
+ };
+ return new DrawableBox(drawable, element, DrawableBox.END_MARKER);
+ }
+
+
+ private void layout(LayoutContext context) {
+ Graphics g = context.getGraphics();
+ Styles styles = context.getStyleSheet().getStyles(element);
+ FontResource font = g.createFont(styles.getFont());
+ FontResource oldFont = g.setFont(font);
+ FontMetrics fm = g.getFontMetrics();
+ this.setHeight(styles.getLineHeight());
+ this.halfLeading = (styles.getLineHeight() - fm.getAscent() - fm.getDescent()) / 2;
+ this.baseline = this.halfLeading + fm.getAscent();
+ g.setFont(oldFont);
+ font.dispose();
+
+ int x = 0;
+ for (int i = 0; i < this.children.length; i++) {
+ InlineBox child = this.children[i];
+ // TODO: honour the child's vertical-align property
+ child.setX(x);
+ child.setY(this.baseline - child.getBaseline());
+ x += child.getWidth();
+ }
+
+ this.setWidth(x);
+ }
+
+
+
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/layout/LayoutContext.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/layout/LayoutContext.java
new file mode 100644
index 0000000..77e43f3
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/layout/LayoutContext.java
@@ -0,0 +1,157 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.layout;
+
+import org.eclipse.wst.xml.vex.core.internal.core.Graphics;
+import org.eclipse.wst.xml.vex.core.internal.css.StyleSheet;
+import org.eclipse.wst.xml.vex.core.internal.dom.Document;
+import org.eclipse.wst.xml.vex.core.internal.dom.Element;
+
+
+/**
+ * Encapsulation of all the resources needed to create a box
+ * tree. Most operations on a box tree, such as creating the tree,
+ * painting the tree, and converting between spatial and model
+ * coordinates, require the context.
+ */
+public class LayoutContext {
+
+ private BoxFactory boxFactory;
+ private Document document;
+ private Graphics graphics;
+ private StyleSheet styleSheet;
+ private int selectionStart;
+ private int selectionEnd;
+ private long startTime = System.currentTimeMillis();
+
+ /**
+ * Class constructor.
+ */
+ public LayoutContext() {
+ }
+
+ /**
+ * Returns the BoxFactory used to generate boxes for the layout.
+ */
+ public BoxFactory getBoxFactory() {
+ return boxFactory;
+ }
+
+ /**
+ * Returns the document being layed out.
+ */
+ public Document getDocument() {
+ return document;
+ }
+
+ /**
+ * Returns the <code>Graphics</code> object used for layout. Box paint
+ * methods use this graphics for painting.
+ */
+ public Graphics getGraphics() {
+ return this.graphics;
+ }
+
+ /**
+ * Returns the time the layout was started. Actually, it's the time since
+ * this context was created, as returned by System.currentTimeMills().
+ */
+ public long getStartTime() {
+ return startTime;
+ }
+
+ /**
+ * Returns the <code>StyleSheet</code> used for this layout.
+ */
+ public StyleSheet getStyleSheet() {
+ return this.styleSheet;
+ }
+
+
+ /**
+ * Helper method that returns true if the given element is in the
+ * selected range.
+ * @param element Element to test. May be null, in which case this method
+ * returns false.
+ */
+ public boolean isElementSelected(Element element) {
+ return element != null
+ && element.getStartOffset() >= this.getSelectionStart()
+ && element.getEndOffset() + 1 <= this.getSelectionEnd();
+ }
+
+ /**
+ * Resets the start time to currentTimeMillis.
+ */
+ public void resetStartTime() {
+ this.startTime = System.currentTimeMillis();
+ }
+
+ /**
+ * Sets the BoxFactory used to generate boxes for this layout.
+ */
+ public void setBoxFactory(BoxFactory factory) {
+ boxFactory = factory;
+ }
+
+ /**
+ * Sets the document being layed out.
+ */
+ public void setDocument(Document document) {
+ this.document = document;
+ }
+
+ /**
+ * Sets the Graphics object used for this layout.
+ */
+ public void setGraphics(Graphics graphics) {
+ this.graphics = graphics;
+ }
+
+ /**
+ * Sets the stylesheet used for this layout.
+ */
+ public void setStyleSheet(StyleSheet sheet) {
+ styleSheet = sheet;
+ }
+
+ /**
+ * Returns the offset where the current selection ends.
+ */
+ public int getSelectionEnd() {
+ return selectionEnd;
+ }
+
+ /**
+ * Returns the offset where the current selection starts.
+ */
+ public int getSelectionStart() {
+ return selectionStart;
+ }
+
+ /**
+ * Sets the offset where the current selection ends.
+ * @param i the new value for selectionEnd
+ */
+ public void setSelectionEnd(int i) {
+ selectionEnd = i;
+ }
+
+ /**
+ * Sets the offset where the current selection starts.
+ * @param i the new value for selectionStart
+ */
+ public void setSelectionStart(int i) {
+ selectionStart = i;
+ }
+
+}
+
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/layout/LayoutUtils.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/layout/LayoutUtils.java
new file mode 100644
index 0000000..db6188a
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/layout/LayoutUtils.java
@@ -0,0 +1,230 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.layout;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+
+import org.eclipse.wst.xml.vex.core.internal.core.IntRange;
+import org.eclipse.wst.xml.vex.core.internal.css.CSS;
+import org.eclipse.wst.xml.vex.core.internal.css.StyleSheet;
+import org.eclipse.wst.xml.vex.core.internal.css.Styles;
+import org.eclipse.wst.xml.vex.core.internal.dom.Element;
+import org.eclipse.wst.xml.vex.core.internal.dom.Node;
+
+
+/**
+ * Tools for layout and rendering of CSS-styled boxes
+ */
+public class LayoutUtils {
+
+ /**
+ * Create a List of generated inline boxes for the given pseudo-element.
+ * @param context LayoutContext in use
+ * @param pseudoElement Element representing the generated content.
+ */
+ public static List createGeneratedInlines(LayoutContext context, Element pseudoElement) {
+ String text = getGeneratedContent(context, pseudoElement);
+ List list = new ArrayList();
+ if (text.length() > 0) {
+ list.add(new StaticTextBox(context, pseudoElement, text));
+ }
+ return list;
+ }
+
+ /**
+ * Returns true if the given offset falls within the given element or range.
+ *
+ * @param elementOrRange Element or IntRange object representing a range
+ * of offsets.
+ * @param offset Offset to test.
+ */
+ public static boolean elementOrRangeContains(Object elementOrRange, int offset) {
+ if (elementOrRange instanceof Element) {
+ Element element = (Element) elementOrRange;
+ return offset > element.getStartOffset() && offset <= element.getEndOffset();
+ } else {
+ IntRange range = (IntRange) elementOrRange;
+ return offset >= range.getStart() && offset <= range.getEnd();
+ }
+ }
+
+ /**
+ * Creates a string representing the generated content for the given
+ * pseudo-element.
+ * @param context LayoutContext in use
+ * @param pseudoElement PseudoElement for which the generated content
+ * is to be returned.
+ */
+ private static String getGeneratedContent(LayoutContext context, Element pseudoElement) {
+ Styles styles = context.getStyleSheet().getStyles(pseudoElement);
+ List content = styles.getContent();
+ StringBuffer sb = new StringBuffer();
+ for (Iterator it = content.iterator(); it.hasNext(); ) {
+ sb.append((String) it.next()); // TODO: change to ContentPart
+ }
+ return sb.toString();
+ }
+
+ /**
+ * Call the given callback for each child matching one of the given
+ * display styles. Any nodes that do not match one of the given display types
+ * cause the onRange callback to be called, with a range covering all such
+ * contiguous nodes.
+ *
+ * @param context LayoutContext to use.
+ * @param displayStyles Display types to be explicitly recognized.
+ * @param element Element containing the children over which to iterate.
+ * @param startOffset Starting offset of the range containing nodes in which we're interested.
+ * @param endOffset Ending offset of the range containing nodes in which we're interested.
+ * @param callback DisplayStyleCallback through which the caller is notified
+ * of matching elements and non-matching ranges.
+ */
+ public static void iterateChildrenByDisplayStyle(StyleSheet styleSheet, Set displayStyles, Element element, int startOffset, int endOffset, ElementOrRangeCallback callback) {
+
+ List nonMatching = new ArrayList();
+
+ Node[] nodes = element.getChildNodes();
+ for (int i = 0; i < nodes.length; i++) {
+ if (nodes[i].getEndOffset() <= startOffset) {
+ continue;
+ } else if (nodes[i].getStartOffset() >= endOffset) {
+ break;
+ } else {
+ Node node = nodes[i];
+
+ if (node instanceof Element) {
+ Element childElement = (Element) node;
+ String display = styleSheet.getStyles(childElement).getDisplay();
+ if (displayStyles.contains(display)) {
+ if (nonMatching.size() > 0) {
+ Node firstNode = (Node) nonMatching.get(0);
+ Node lastNode = (Node) nonMatching.get(nonMatching.size() - 1);
+ if (lastNode instanceof Element) {
+ callback.onRange(element, firstNode.getStartOffset(), lastNode.getEndOffset() + 1);
+ } else {
+ callback.onRange(element, firstNode.getStartOffset(), lastNode.getEndOffset());
+ }
+ nonMatching.clear();
+ }
+ callback.onElement(childElement, display);
+ } else {
+ nonMatching.add(node);
+ }
+ } else {
+ nonMatching.add(node);
+ }
+ }
+ }
+
+ if (nonMatching.size() > 0) {
+ Node firstNode = (Node) nonMatching.get(0);
+ Node lastNode = (Node) nonMatching.get(nonMatching.size() - 1);
+ if (lastNode instanceof Element) {
+ callback.onRange(element, firstNode.getStartOffset(), lastNode.getEndOffset() + 1);
+ } else {
+ callback.onRange(element, firstNode.getStartOffset(), lastNode.getEndOffset());
+ }
+ }
+ }
+
+ /**
+ * Call the given callback for each child matching one of the given
+ * display styles. Any nodes that do not match one of the given display types
+ * cause the onRange callback to be called, with a range covering all such
+ * contiguous nodes.
+ *
+ * @param context LayoutContext to use.
+ * @param displayStyles Display types to be explicitly recognized.
+ * @param element Element containing the children over which to iterate.
+ * @param callback DisplayStyleCallback through which the caller is notified
+ * of matching elements and non-matching ranges.
+ */
+ public static void iterateChildrenByDisplayStyle(StyleSheet styleSheet, Set displayStyles, Element element, ElementOrRangeCallback callback) {
+ iterateChildrenByDisplayStyle(styleSheet, displayStyles, element, element.getStartOffset() + 1, element.getEndOffset(), callback);
+ }
+
+ /**
+ * Returns true if the given styles represent an element that can be
+ * the child of a table element.
+ *
+ * @param styleSheet StyleSheet to use.
+ * @param element Element to test.
+ */
+ public static boolean isTableChild(StyleSheet styleSheet, Element element) {
+ String display = styleSheet.getStyles(element).getDisplay();
+ return TABLE_CHILD_STYLES.contains(display);
+ }
+
+ public static void iterateTableRows(final StyleSheet styleSheet, final Element element, int startOffset, int endOffset, final ElementOrRangeCallback callback) {
+
+ iterateChildrenByDisplayStyle(styleSheet, nonRowStyles, element, startOffset, endOffset, new ElementOrRangeCallback() {
+ public void onElement(Element child, String displayStyle) {
+ if (displayStyle.equals(CSS.TABLE_ROW_GROUP)
+ || displayStyle.equals(CSS.TABLE_HEADER_GROUP)
+ || displayStyle.equals(CSS.TABLE_FOOTER_GROUP)) {
+
+ // iterate rows in group
+ iterateChildrenByDisplayStyle(styleSheet, rowStyles, child, child.getStartOffset() + 1, child.getEndOffset(), callback);
+ } else {
+ // other element's can't contain rows
+ }
+ }
+ public void onRange(Element parent, int startOffset, int endOffset) {
+ // iterate over rows in range
+ iterateChildrenByDisplayStyle(styleSheet, rowStyles, element, startOffset, endOffset, callback);
+ }
+ });
+
+ }
+
+ public static void iterateTableCells(StyleSheet styleSheet, Element element, int startOffset, int endOffset, final ElementOrRangeCallback callback) {
+ iterateChildrenByDisplayStyle(styleSheet, cellStyles, element, startOffset, endOffset, callback);
+ }
+
+ public static void iterateTableCells(StyleSheet styleSheet, Element element, final ElementOrRangeCallback callback) {
+ iterateChildrenByDisplayStyle(styleSheet, cellStyles, element, element.getStartOffset(), element.getEndOffset(), callback);
+ }
+
+ /**
+ * Set of CSS display values that represent elements that can be children
+ * of table elements.
+ */
+ public static Set TABLE_CHILD_STYLES = new HashSet();
+
+ private static Set nonRowStyles = new HashSet();
+ private static Set rowStyles = new HashSet();
+ private static Set cellStyles = new HashSet();
+
+
+ static {
+ nonRowStyles.add(CSS.TABLE_CAPTION);
+ nonRowStyles.add(CSS.TABLE_COLUMN);
+ nonRowStyles.add(CSS.TABLE_COLUMN_GROUP);
+ nonRowStyles.add(CSS.TABLE_ROW_GROUP);
+ nonRowStyles.add(CSS.TABLE_HEADER_GROUP);
+ nonRowStyles.add(CSS.TABLE_FOOTER_GROUP);
+
+ rowStyles.add(CSS.TABLE_ROW);
+
+ cellStyles.add(CSS.TABLE_CELL);
+
+ TABLE_CHILD_STYLES.addAll(nonRowStyles);
+ TABLE_CHILD_STYLES.addAll(rowStyles);
+ TABLE_CHILD_STYLES.addAll(cellStyles);
+ }
+
+
+
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/layout/LineBox.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/layout/LineBox.java
new file mode 100644
index 0000000..975a563
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/layout/LineBox.java
@@ -0,0 +1,133 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.layout;
+
+import org.eclipse.wst.xml.vex.core.internal.dom.Element;
+
+/**
+ * Represents a line of text and inline images.
+ */
+public class LineBox extends CompositeInlineBox {
+
+ private Element element;
+ private InlineBox[] children;
+ private InlineBox firstContentChild = null;
+ private InlineBox lastContentChild = null;
+ private int baseline;
+
+ /**
+ * Class constructor.
+ *
+ * @param context LayoutContext for this layout.
+ * @param children InlineBoxes that make up this line.
+ */
+ public LineBox(LayoutContext context, Element element, InlineBox[] children) {
+
+ this.element = element;
+ this.children = children;
+
+ int height = 0;
+ int x = 0;
+ this.baseline = 0;
+ for (int i = 0; i < children.length; i++) {
+ InlineBox child = children[i];
+ child.setX(x);
+ child.setY(0); // TODO: do proper vertical alignment
+ this.baseline = Math.max(this.baseline, child.getBaseline());
+ x += child.getWidth();
+ height = Math.max(height, child.getHeight());
+ if (child.hasContent()) {
+ if (this.firstContentChild == null) {
+ this.firstContentChild = child;
+ }
+ this.lastContentChild = child;
+ }
+ }
+
+ this.setHeight(height);
+ this.setWidth(x);
+ }
+
+ /**
+ * @see org.eclipse.wst.xml.vex.core.internal.layout.InlineBox#getBaseline()
+ */
+ public int getBaseline() {
+ return this.baseline;
+ }
+
+ public Box[] getChildren() {
+ return this.children;
+ }
+
+ /**
+ * @see org.eclipse.wst.xml.vex.core.internal.layout.Box#getElement()
+ */
+ public Element getElement() {
+ return this.element;
+ }
+
+ /**
+ * @see org.eclipse.wst.xml.vex.core.internal.layout.Box#getEndOffset()
+ */
+ public int getEndOffset() {
+ return this.lastContentChild.getEndOffset();
+ }
+
+ /**
+ * @see org.eclipse.wst.xml.vex.core.internal.layout.Box#getStartOffset()
+ */
+ public int getStartOffset() {
+ return this.firstContentChild.getStartOffset();
+ }
+
+ /**
+ * @see org.eclipse.wst.xml.vex.core.internal.layout.Box#hasContent()
+ */
+ public boolean hasContent() {
+ return this.firstContentChild != null;
+ }
+
+ /**
+ * @see org.eclipse.wst.xml.vex.core.internal.layout.CompositeInlineBox#split(org.eclipse.wst.xml.vex.core.internal.layout.LayoutContext, org.eclipse.wst.xml.vex.core.internal.layout.InlineBox[], org.eclipse.wst.xml.vex.core.internal.layout.InlineBox[])
+ */
+ public Pair split(LayoutContext context, InlineBox[] lefts, InlineBox[] rights) {
+
+ LineBox left = null;
+ LineBox right = null;
+
+ if (lefts.length > 0) {
+ left = new LineBox(context, this.getElement(), lefts);
+ }
+
+ if (rights.length > 0) {
+ right = new LineBox(context, this.getElement(), rights);
+ }
+
+ return new Pair(left, right);
+ }
+
+
+ /**
+ * @see java.lang.Object#toString()
+ */
+ public String toString() {
+ Box[] children = this.getChildren();
+ StringBuffer sb = new StringBuffer();
+ for (int i = 0; i < children.length; i++) {
+ sb.append(children[i]);
+ }
+ return sb.toString();
+ }
+
+ //========================================================== PRIVATE
+
+
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/layout/ParagraphBox.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/layout/ParagraphBox.java
new file mode 100644
index 0000000..227de34
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/layout/ParagraphBox.java
@@ -0,0 +1,299 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.layout;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+import org.eclipse.wst.xml.vex.core.internal.core.Caret;
+import org.eclipse.wst.xml.vex.core.internal.core.IntRange;
+import org.eclipse.wst.xml.vex.core.internal.css.CSS;
+import org.eclipse.wst.xml.vex.core.internal.css.Styles;
+import org.eclipse.wst.xml.vex.core.internal.dom.Element;
+
+
+/**
+ * A box that wraps inline content into a paragraph.
+ */
+public class ParagraphBox extends AbstractBox implements BlockBox {
+
+ private LineBox[] children;
+ private LineBox firstContentLine;
+ private LineBox lastContentLine;
+
+ /**
+ * Class constructor.
+ *
+ * @param children Line boxes that comprise the paragraph.
+ */
+ private ParagraphBox(LineBox[] children) {
+ this.children = children;
+ for (int i = 0; i < children.length; i++) {
+ if (children[i].hasContent()) {
+ if (this.firstContentLine == null) {
+ this.firstContentLine = children[i];
+ }
+ this.lastContentLine = children[i];
+ }
+ }
+ }
+
+ /**
+ * Create a paragraph by word-wrapping a list of inline boxes.
+ * @param context LayoutContext used for this layout.
+ * @param element Element that controls the styling for this paragraph.
+ * @param inlines List of InlineBox objects to be wrapped
+ * @param width width to which the paragraph is to be wrapped
+ * @return
+ */
+ public static ParagraphBox create(LayoutContext context, Element element, List inlines, int width) {
+ InlineBox[] array = (InlineBox[]) inlines.toArray(new InlineBox[inlines.size()]);
+ return create(context, element, array, width);
+ }
+
+ /**
+ * Create a paragraph by word-wrapping a list of inline boxes.
+ * @param context LayoutContext used for this layout
+ * @param element Element that controls the styling of this paragraph,
+ * in particular text alignment.
+ * @param inlines Array of InlineBox objects to be wrapped.
+ * @param width width to which the paragraph is to be wrapped.
+ */
+ public static ParagraphBox create(LayoutContext context, Element element, InlineBox[] inlines, int width) {
+
+ // lines is the list of LineBoxes we are creating
+ List lines = new ArrayList();
+
+ InlineBox right = new LineBox(context, element, inlines);
+
+ while (right != null) {
+ InlineBox.Pair pair = right.split(context, width, true);
+ lines.add(pair.getLeft());
+ right = pair.getRight();
+ }
+
+ Styles styles = context.getStyleSheet().getStyles(element);
+ String textAlign = styles.getTextAlign();
+
+ // y-offset of the next line
+ int y = 0;
+
+ int actualWidth = 0;
+
+ for (Iterator it = lines.iterator(); it.hasNext(); ) {
+
+ LineBox lineBox = (LineBox) it.next();
+
+ int x;
+ if (textAlign.equals(CSS.RIGHT)) {
+ x = width - lineBox.getWidth();
+ } else if (textAlign.equals(CSS.CENTER)) {
+ x = (width - lineBox.getWidth()) / 2;
+ } else {
+ x = 0;
+ }
+
+ lineBox.setX(x);
+ lineBox.setY(y);
+
+ y += lineBox.getHeight();
+ actualWidth = Math.max(actualWidth, lineBox.getWidth());
+ }
+
+ LineBox[] children = (LineBox[]) lines.toArray(new LineBox[lines.size()]);
+
+ ParagraphBox para = new ParagraphBox(children);
+ para.setWidth(actualWidth);
+ para.setHeight(y);
+
+ // BlockElementBox uses a scaling factor to estimate box height based
+ // on font size, layout width, and character count, as follows.
+ //
+ // estHeight = factor * fontSize * fontSize * charCount / width
+ //
+ // This bit reports the actual factor that would correctly estimate
+ // the height of a BlockElementBox containing only this paragraph.
+ //
+ // factor = estHeight * width / (fontSize * fontSize * charCount)
+ //
+ /*
+ Box firstContentBox = null;
+ for (int i = 0; i < inlines.length; i++) {
+ Box box = inlines[i];
+ if (box.hasContent()) {
+ firstContentBox = box;
+ break;
+ }
+ }
+
+ if (firstContentBox != null) {
+ float fontSize = styles.getFontSize();
+ int charCount = lastContentBox.getEndOffset() - firstContentBox.getStartOffset();
+ float factor = para.getHeight() * para.getWidth() / (fontSize * fontSize * charCount);
+ System.out.println("Actual factor is " + factor);
+ }
+ */
+
+ return para;
+ }
+
+
+ /**
+ * @see org.eclipse.wst.xml.vex.core.internal.layout.Box#getCaret(org.eclipse.wst.xml.vex.core.internal.layout.LayoutContext, int)
+ */
+ public Caret getCaret(LayoutContext context, int offset) {
+
+ LineBox line = this.getLineAt(offset);
+ Caret caret = line.getCaret(context, offset);
+ caret.translate(line.getX(), line.getY());
+ return caret;
+
+ }
+
+ public Box[] getChildren() {
+ return this.children;
+ }
+
+ public int getEndOffset() {
+ return this.lastContentLine.getEndOffset();
+ }
+
+ /**
+ * @see org.eclipse.wst.xml.vex.core.internal.layout.BlockBox#getFirstLine()
+ */
+ public LineBox getFirstLine() {
+ if (this.children.length == 0) {
+ return null;
+ } else {
+ return this.children[0];
+ }
+ }
+
+ /**
+ * @see org.eclipse.wst.xml.vex.core.internal.layout.BlockBox#getLastLine()
+ */
+ public LineBox getLastLine() {
+ if (this.children.length == 0) {
+ return null;
+ } else {
+ return this.children[this.children.length - 1];
+ }
+ }
+
+ /**
+ * Returns the LineBox at the given offset.
+ * @param offset the offset to check.
+ */
+ public LineBox getLineAt(int offset) {
+ LineBox[] children = this.children;
+ for (int i = 0; i < children.length; i++) {
+ if (children[i].hasContent() && offset <= children[i].getEndOffset()) {
+ return children[i];
+ }
+ }
+ return this.lastContentLine;
+ }
+
+ public int getLineEndOffset(int offset) {
+ return this.getLineAt(offset).getEndOffset();
+ }
+
+ public int getLineStartOffset(int offset) {
+ return this.getLineAt(offset).getStartOffset();
+ }
+
+ public int getMarginBottom() {
+ return 0;
+ }
+
+ public int getMarginTop() {
+ return 0;
+ }
+
+ public int getNextLineOffset(LayoutContext context, int offset, int x) {
+ LineBox nextLine = null;
+ LineBox[] children = this.children;
+ for (int i = 0; i < children.length; i++) {
+ if (children[i].hasContent() && children[i].getStartOffset() > offset) {
+ nextLine = children[i];
+ break;
+ }
+ }
+ if (nextLine == null) {
+ //return this.getEndOffset() + 1;
+ return -1;
+ } else {
+ return nextLine.viewToModel(context, x - nextLine.getX(), 0);
+ }
+ }
+
+ public BlockBox getParent() {
+ throw new IllegalStateException("ParagraphBox does not currently track parent");
+ }
+
+ public int getPreviousLineOffset(LayoutContext context, int offset, int x) {
+ LineBox prevLine = null;
+ LineBox[] children = this.children;
+ for (int i = children.length - 1; i >= 0; i--) {
+ if (children[i].hasContent() && children[i].getEndOffset() < offset) {
+ prevLine = children[i];
+ break;
+ }
+ }
+ if (prevLine == null) {
+ //return this.getStartOffset() - 1;
+ return -1;
+ } else {
+ return prevLine.viewToModel(context, x - prevLine.getX(), 0);
+ }
+ }
+
+ public int getStartOffset() {
+ return this.firstContentLine.getStartOffset();
+ }
+
+ public boolean hasContent() {
+ return this.firstContentLine != null;
+ }
+
+ public IntRange layout(LayoutContext context, int top, int bottom) {
+ return null;
+ }
+
+ public void invalidate(boolean direct) {
+ throw new IllegalStateException("invalidate called on a non-element BlockBox");
+ }
+
+ public void setInitialSize(LayoutContext context) {
+ // NOP - size calculated in factory method
+ }
+
+ public String toString() {
+ return "ParagraphBox";
+ }
+
+ public int viewToModel(LayoutContext context, int x, int y) {
+
+ LineBox[] children = this.children;
+ for (int i = 0; i < children.length; i++) {
+ Box child = children[i];
+ if (child.hasContent() && y <= child.getY() + child.getHeight()) {
+ return child.viewToModel(context, x - child.getX(), y - child.getY());
+ }
+ }
+ throw new RuntimeException("No line at (" + x + ", " + y + ")");
+ }
+
+ //===================================================== PRIVATE
+
+}
+
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/layout/PlaceholderBox.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/layout/PlaceholderBox.java
new file mode 100644
index 0000000..cb670d4
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/layout/PlaceholderBox.java
@@ -0,0 +1,130 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.layout;
+
+import org.eclipse.wst.xml.vex.core.internal.core.Caret;
+import org.eclipse.wst.xml.vex.core.internal.core.FontMetrics;
+import org.eclipse.wst.xml.vex.core.internal.core.FontResource;
+import org.eclipse.wst.xml.vex.core.internal.core.Graphics;
+import org.eclipse.wst.xml.vex.core.internal.css.Styles;
+import org.eclipse.wst.xml.vex.core.internal.dom.Element;
+
+
+/**
+ * A zero-width box that represents a single offset in the document.
+ */
+public class PlaceholderBox extends AbstractBox implements InlineBox {
+
+ private Element element;
+ private int relOffset;
+ private int textTop;
+ private int baseline;
+
+ /**
+ * Class constructor.
+ * @param context LayoutContext in effect.
+ * @param element Element containing this placeholder. the element is used
+ * both to determine the size of the box and its caret, but also as a base
+ * point for relOffset.
+ * @param relOffset Offset of the placeholder, relative to the start of
+ * the element.
+ */
+ public PlaceholderBox(LayoutContext context, Element element, int relOffset) {
+
+ this.element = element;
+ this.relOffset = relOffset;
+
+ this.setWidth(0);
+
+ Graphics g = context.getGraphics();
+ Styles styles = context.getStyleSheet().getStyles(element);
+ FontResource font = g.createFont(styles.getFont());
+ FontResource oldFont = g.setFont(font);
+ FontMetrics fm = g.getFontMetrics();
+ int height = fm.getAscent() + fm.getDescent();
+
+ int lineHeight = styles.getLineHeight();
+ this.textTop = (lineHeight - height) / 2;
+
+ this.baseline = this.textTop + fm.getAscent();
+ this.setHeight(lineHeight);
+ g.setFont(oldFont);
+ font.dispose();
+ }
+
+ /**
+ * @see org.eclipse.wst.xml.vex.core.internal.layout.InlineBox#getBaseline()
+ */
+ public int getBaseline() {
+ return this.baseline;
+ }
+
+ /**
+ * @see org.eclipse.wst.xml.vex.core.internal.layout.InlineBox#split(org.eclipse.wst.xml.vex.core.internal.layout.LayoutContext, int, boolean)
+ */
+ public Pair split(LayoutContext context, int maxWidth, boolean force) {
+ return new Pair(null, this);
+ }
+
+ /**
+ * @see org.eclipse.wst.xml.vex.core.internal.layout.Box#getCaret(org.eclipse.wst.xml.vex.core.internal.layout.LayoutContext, int)
+ */
+ public Caret getCaret(LayoutContext context, int offset) {
+ return new TextCaret(0, this.textTop, this.baseline - this.textTop);
+ }
+
+ /**
+ * @see org.eclipse.wst.xml.vex.core.internal.layout.Box#getElement()
+ */
+ public Element getElement() {
+ return this.element;
+ }
+
+ /**
+ * @see org.eclipse.wst.xml.vex.core.internal.layout.Box#getEndOffset()
+ */
+ public int getEndOffset() {
+ return this.element.getStartOffset() + this.relOffset;
+ }
+
+ /**
+ * @see org.eclipse.wst.xml.vex.core.internal.layout.Box#getStartOffset()
+ */
+ public int getStartOffset() {
+ return this.element.getStartOffset() + this.relOffset;
+ }
+
+ /**
+ * @see org.eclipse.wst.xml.vex.core.internal.layout.Box#hasContent()
+ */
+ public boolean hasContent() {
+ return true;
+ }
+
+ public boolean isEOL() {
+ return false;
+ }
+
+ /**
+ * @see java.lang.Object#toString()
+ */
+ public String toString() {
+ return "[placeholder(" + this.getStartOffset() + ")]";
+ }
+ /**
+ * @see org.eclipse.wst.xml.vex.core.internal.layout.Box#viewToModel(org.eclipse.wst.xml.vex.core.internal.layout.LayoutContext, int, int)
+ */
+ public int viewToModel(LayoutContext context, int x, int y) {
+ return this.getStartOffset();
+ }
+
+
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/layout/RootBox.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/layout/RootBox.java
new file mode 100644
index 0000000..e11f631
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/layout/RootBox.java
@@ -0,0 +1,180 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.layout;
+
+import org.eclipse.wst.xml.vex.core.internal.VEXCorePlugin;
+import org.eclipse.wst.xml.vex.core.internal.core.Caret;
+import org.eclipse.wst.xml.vex.core.internal.core.Insets;
+import org.eclipse.wst.xml.vex.core.internal.core.IntRange;
+import org.eclipse.wst.xml.vex.core.internal.core.Rectangle;
+import org.eclipse.wst.xml.vex.core.internal.dom.Element;
+
+
+/**
+ * A wrapper for the top level <code>BlockElementBox</code> that applies
+ * its margins.
+ */
+public class RootBox extends AbstractBox implements BlockBox {
+
+ private Element element;
+ private BlockElementBox childBox;
+ private Box[] children = new Box[1];
+
+ /**
+ * Class constructor.
+ * @param context LayoutContext used to create children.
+ * @param element Element associated with this box.
+ * @param width width of this box
+ */
+ public RootBox(LayoutContext context, Element element, int width) {
+ this.element = element;
+ this.setWidth(width);
+
+ this.childBox = new BlockElementBox(context, this, this.element);
+
+ Insets insets = this.getInsets(context, this.getWidth());
+ this.childBox.setX(insets.getLeft());
+ this.childBox.setY(insets.getTop());
+ this.childBox.setInitialSize(context);
+ this.children[0] = this.childBox;
+ }
+
+ /**
+ * @see org.eclipse.wst.xml.vex.core.internal.layout.Box#getCaret(org.eclipse.wst.xml.vex.core.internal.layout.LayoutContext, int)
+ */
+ public Caret getCaret(LayoutContext context, int offset) {
+ Caret caret = this.childBox.getCaret(context, offset);
+ caret.translate(this.childBox.getX(), this.childBox.getY());
+ return caret;
+ }
+
+ public Box[] getChildren() {
+ return this.children;
+ }
+
+ /**
+ * @see org.eclipse.wst.xml.vex.core.internal.layout.Box#getElement()
+ */
+ public Element getElement() {
+ return this.element;
+ }
+
+ /**
+ * @see org.eclipse.wst.xml.vex.core.internal.layout.Box#getEndOffset()
+ */
+ public int getEndOffset() {
+ return this.childBox.getEndOffset();
+ }
+
+ /**
+ * @see org.eclipse.wst.xml.vex.core.internal.layout.BlockBox#getFirstLine()
+ */
+ public LineBox getFirstLine() {
+ return this.childBox.getFirstLine();
+ }
+
+ /**
+ * @see org.eclipse.wst.xml.vex.core.internal.layout.BlockBox#getLastLine()
+ */
+ public LineBox getLastLine() {
+ return this.childBox.getLastLine();
+ }
+
+ public int getLineEndOffset(int offset) {
+ return this.childBox.getLineEndOffset(offset);
+ }
+
+ public int getLineStartOffset(int offset) {
+ return this.childBox.getLineStartOffset(offset);
+ }
+
+ public int getMarginBottom() {
+ return 0;
+ }
+
+ public int getMarginTop() {
+ return 0;
+ }
+
+ public int getNextLineOffset(LayoutContext context, int offset, int x) {
+ return childBox.getNextLineOffset(context, offset, x - childBox.getX());
+ }
+
+ public BlockBox getParent() {
+ throw new IllegalStateException("RootBox does not have a parent");
+ }
+
+ public int getPreviousLineOffset(LayoutContext context, int offset, int x) {
+ return childBox.getPreviousLineOffset(context, offset, x - childBox.getX());
+ }
+
+ /**
+ * @see org.eclipse.wst.xml.vex.core.internal.layout.Box#getStartOffset()
+ */
+ public int getStartOffset() {
+ return this.childBox.getStartOffset();
+ }
+
+ public void invalidate(boolean direct) {
+ // do nothing. layout is always propagated to our child box.
+ }
+
+ public IntRange layout(LayoutContext context, int top, int bottom) {
+
+ Insets insets = this.getInsets(context, this.getWidth());
+
+ long start = 0;
+ if (VEXCorePlugin.getInstance().isDebugging()) {
+ start = System.currentTimeMillis();
+ }
+
+ IntRange repaintRange = this.childBox.layout(context, top - insets.getTop(), bottom - insets.getBottom());
+
+
+ if (VEXCorePlugin.getInstance().isDebugging()) {
+ long end = System.currentTimeMillis();
+ if (end - start > 50) {
+ System.out.println("RootBox.layout took " + (end - start) + "ms");
+ }
+ }
+
+ this.setHeight(this.childBox.getHeight() + insets.getTop() + insets.getBottom());
+
+ if (repaintRange != null) {
+ return new IntRange(repaintRange.getStart() + this.childBox.getY(), repaintRange.getEnd() + this.childBox.getY());
+ } else {
+ return null;
+ }
+ }
+
+ /* (non-Javadoc)
+ * @see net.sf.vex.layout.ContentBox#viewToModel(net.sf.vex.layout.LayoutContext, int, int)
+ */
+ public int viewToModel(LayoutContext context, int x, int y) {
+ return this.childBox.viewToModel(
+ context, x - this.childBox.getX(), y - this.childBox.getY());
+ }
+
+ public void paint(LayoutContext context, int x, int y) {
+ Rectangle r = context.getGraphics().getClipBounds();
+ long start = System.currentTimeMillis();
+ super.paint(context, x, y);
+ long end = System.currentTimeMillis();
+ if (end - start > 50) {
+ System.out.println("RootBox.paint " + r.getHeight() + " pixel rows in " + (end - start) + "ms");
+ }
+ }
+
+ public void setInitialSize(LayoutContext context) {
+ throw new IllegalStateException();
+ }
+
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/layout/SpaceBox.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/layout/SpaceBox.java
new file mode 100644
index 0000000..8ada2a4
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/layout/SpaceBox.java
@@ -0,0 +1,54 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.layout;
+
+
+/**
+ * An empty inline box that simply takes up space.
+ */
+public class SpaceBox extends AbstractBox implements InlineBox {
+
+ /**
+ * Class constructor.
+ * @param width width of the box
+ * @param height height of the box
+ */
+ public SpaceBox(int width, int height) {
+ this.setWidth(width);
+ this.setHeight(height);
+ }
+
+ /**
+ * @see org.eclipse.wst.xml.vex.core.internal.layout.InlineBox#getBaseline()
+ */
+ public int getBaseline() {
+ return this.getHeight();
+ }
+
+ public boolean isEOL() {
+ return false;
+ }
+
+ /**
+ * @see org.eclipse.wst.xml.vex.core.internal.layout.InlineBox#split(org.eclipse.wst.xml.vex.core.internal.layout.LayoutContext, int, boolean)
+ */
+ public Pair split(LayoutContext context, int maxWidth, boolean force) {
+ return new Pair(null, this);
+ }
+
+ /**
+ * @see java.lang.Object#toString()
+ */
+ public String toString() {
+ return "[spacer]";
+ }
+
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/layout/StaticTextBox.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/layout/StaticTextBox.java
new file mode 100644
index 0000000..9d765dd
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/layout/StaticTextBox.java
@@ -0,0 +1,142 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.layout;
+
+import org.eclipse.wst.xml.vex.core.internal.core.ColorResource;
+import org.eclipse.wst.xml.vex.core.internal.core.FontResource;
+import org.eclipse.wst.xml.vex.core.internal.core.Graphics;
+import org.eclipse.wst.xml.vex.core.internal.css.Styles;
+import org.eclipse.wst.xml.vex.core.internal.dom.Element;
+
+
+/**
+ * A TextBox representing a static string.
+ * Represents text which is not editable within the VexWidget, such as enumerated list markers.
+ */
+public class StaticTextBox extends TextBox {
+
+ public static final byte NO_MARKER = 0;
+ public static final byte START_MARKER = 1;
+ public static final byte END_MARKER = 2;
+
+ private String text;
+ private byte marker;
+
+ /**
+ * Class constructor.
+ *
+ * @param context LayoutContext used to calculate the box's size.
+ * @param element Element used to style the text.
+ * @param text Static text to display
+ */
+ public StaticTextBox(LayoutContext context, Element element, String text) {
+ this(context, element, text, NO_MARKER);
+ if (text.length() == 0) {
+ throw new IllegalArgumentException("StaticTextBox cannot have an empty text string.");
+ }
+ }
+
+ /**
+ * Class constructor. This constructor is used when generating a static
+ * text box representing a marker for the start or end of an inline element.
+ * If the selection spans the related marker, the text is drawn in the
+ * platform's text selection colours.
+ *
+ * @param context LayoutContext used to calculate the box's size
+ * @param element Element used to style the text
+ * @param text Static text to display
+ * @param marker START_MARKER or END_MARKER, depending on whether the
+ * text represents the start sentinel or the end sentinel of the element
+ */
+ public StaticTextBox(LayoutContext context, Element element, String text, byte marker) {
+ super(element);
+ this.text = text;
+ this.marker = marker;
+ this.calculateSize(context);
+ }
+
+ /**
+ * @see org.eclipse.wst.xml.vex.core.internal.layout.TextBox#getText()
+ */
+ public String getText() {
+ return this.text;
+ }
+
+ /**
+ * @see org.eclipse.wst.xml.vex.core.internal.layout.Box#hasContent()
+ */
+ public boolean hasContent() {
+ return false;
+ }
+
+ /**
+ * @see org.eclipse.wst.xml.vex.core.internal.layout.Box#paint(org.eclipse.wst.xml.vex.core.internal.layout.LayoutContext, int, int)
+ */
+ public void paint(LayoutContext context, int x, int y) {
+
+ Styles styles = context.getStyleSheet().getStyles(this.getElement());
+ Graphics g = context.getGraphics();
+
+ boolean drawSelected = false;
+ if (this.marker == START_MARKER) {
+ drawSelected =
+ this.getElement().getStartOffset() >= context.getSelectionStart()
+ && this.getElement().getStartOffset() + 1 <= context.getSelectionEnd();
+ } else if (this.marker == END_MARKER) {
+ drawSelected =
+ this.getElement().getEndOffset() >= context.getSelectionStart()
+ && this.getElement().getEndOffset() + 1 <= context.getSelectionEnd();
+ }
+
+ FontResource font = g.createFont(styles.getFont());
+ ColorResource color = g.createColor(styles.getColor());
+
+ FontResource oldFont = g.setFont(font);
+ ColorResource oldColor = g.setColor(color);
+
+ if (drawSelected) {
+ this.paintSelectedText(context, this.getText(), x, y);
+ } else {
+ g.drawString(this.getText(), x, y);
+ }
+ paintTextDecoration(context, styles, this.getText(), x, y);
+
+ g.setFont(oldFont);
+ g.setColor(oldColor);
+ font.dispose();
+ color.dispose();
+ }
+
+
+
+
+ /**
+ * @see org.eclipse.wst.xml.vex.core.internal.layout.TextBox#splitAt(int)
+ */
+ public Pair splitAt(LayoutContext context, int offset) {
+
+ StaticTextBox left;
+ if (offset == 0) {
+ left = null;
+ } else {
+ left = new StaticTextBox(context, this.getElement(), this.getText().substring(0, offset), this.marker);
+ }
+
+ StaticTextBox right;
+ if (offset == this.getText().length()) {
+ right = null;
+ } else {
+ right = new StaticTextBox(context, this.getElement(), this.getText().substring(offset), this.marker);
+ }
+ return new Pair(left, right);
+ }
+
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/layout/TableBodyBox.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/layout/TableBodyBox.java
new file mode 100644
index 0000000..b31cfe6
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/layout/TableBodyBox.java
@@ -0,0 +1,84 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.layout;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import org.eclipse.wst.xml.vex.core.internal.core.Insets;
+import org.eclipse.wst.xml.vex.core.internal.css.CSS;
+import org.eclipse.wst.xml.vex.core.internal.css.Styles;
+import org.eclipse.wst.xml.vex.core.internal.dom.Element;
+
+
+/**
+ * An anonymous box that contains the table row groups for a table. This box
+ * is generated by a TableBox and assumes the margins and borders of the
+ * table element.
+ */
+public class TableBodyBox extends AbstractBlockBox {
+
+ public TableBodyBox(LayoutContext context, TableBox parent, int startOffset, int endOffset) {
+ super(context, parent, startOffset, endOffset);
+ }
+
+ protected List createChildren(final LayoutContext context) {
+ // TODO Auto-generated method stub
+
+ // Walk children:
+ // each table-*-group gets a non-anonymous TableRowGroupBox
+ // runs of others get anonymous TableRowGroupBox
+
+ final List children = new ArrayList();
+
+ this.iterateChildrenByDisplayStyle(context.getStyleSheet(), childDisplayStyles, new ElementOrRangeCallback() {
+ public void onElement(Element child, String displayStyle) {
+ children.add(new TableRowGroupBox(context, TableBodyBox.this, child));
+ }
+ public void onRange(Element parent, int startOffset, int endOffset) {
+ children.add(new TableRowGroupBox(context, TableBodyBox.this, startOffset, endOffset));
+ }
+ });
+
+ return children;
+ }
+
+ /**
+ * Return the insets of the parent box.
+ */
+ public Insets getInsets(LayoutContext context, int containerWidth) {
+ if (this.getParent().getElement() != null) {
+ Styles styles = context.getStyleSheet().getStyles(this.getParent().getElement());
+ return AbstractBox.getInsets(styles, containerWidth);
+ } else {
+ return Insets.ZERO_INSETS;
+ }
+ }
+
+ public void paint(LayoutContext context, int x, int y) {
+ this.drawBox(context, this.getParent().getElement(), x, y, this.getParent().getWidth(), true);
+ this.paintChildren(context, x, y);
+ }
+
+
+ //======================================================== PRIVATE
+
+ private static Set childDisplayStyles = new HashSet();
+
+ static {
+ childDisplayStyles.add(CSS.TABLE_ROW_GROUP);
+ childDisplayStyles.add(CSS.TABLE_HEADER_GROUP);
+ childDisplayStyles.add(CSS.TABLE_FOOTER_GROUP);
+ }
+
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/layout/TableBox.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/layout/TableBox.java
new file mode 100644
index 0000000..dae6cc7
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/layout/TableBox.java
@@ -0,0 +1,224 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.layout;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import org.eclipse.wst.xml.vex.core.internal.core.Insets;
+import org.eclipse.wst.xml.vex.core.internal.core.IntRange;
+import org.eclipse.wst.xml.vex.core.internal.css.CSS;
+import org.eclipse.wst.xml.vex.core.internal.css.StyleSheet;
+import org.eclipse.wst.xml.vex.core.internal.css.Styles;
+import org.eclipse.wst.xml.vex.core.internal.dom.Element;
+
+
+/**
+ * Box that lays out a table.
+ */
+public class TableBox extends AbstractBlockBox {
+
+ /**
+ * Class constructor.
+ * @param element Element represented by this box.
+ */
+ public TableBox(LayoutContext context, BlockBox parent, Element element) {
+ super(context, parent, element);
+ }
+
+ public TableBox(LayoutContext context, BlockBox parent, int startOffset, int endOffset) {
+ super(context, parent, startOffset, endOffset);
+ }
+
+ protected List createChildren(final LayoutContext context) {
+
+ // Walk children:
+ // each table-caption gets a BEB
+ // each table-column gets a TableColumnBox
+ // each table-column-group gets a TableColumnGroupBox
+ // runs of others get TableBodyBox
+
+ final List children = new ArrayList();
+
+ this.iterateChildrenByDisplayStyle(context.getStyleSheet(), captionOrColumnStyles, new ElementOrRangeCallback() {
+ public void onElement(Element child, String displayStyle) {
+ children.add(new BlockElementBox(context, TableBox.this, child));
+ }
+ public void onRange(Element parent, int startOffset, int endOffset) {
+ children.add(new TableBodyBox(context, TableBox.this, startOffset, endOffset));
+ }
+ });
+
+ return children;
+ }
+
+ /**
+ * Returns an array of widths of the table columns. These widths do not
+ * include column spacing.
+ */
+ public int[] getColumnWidths() {
+ return this.columnWidths;
+ }
+
+ public int getHorizonalSpacing() {
+ return this.horizonalSpacing;
+ }
+
+ public Insets getInsets(LayoutContext context, int containerWidth) {
+ return new Insets(this.getMarginTop(), 0, this.getMarginBottom(), 0);
+ }
+
+ public int getVerticalSpacing() {
+ return this.verticalSpacing;
+ }
+
+ public IntRange layout(LayoutContext context, int top, int bottom) {
+
+ // TODO Only compute columns widths (a) if re-laying out the whole box
+ // or (b) if the invalid child row now has more columns than us
+ // or (c) if the invalid child row has < current column count and it
+ // used to be the only one with a valid child row.
+
+ int newColCount = this.computeColumnCount(context);
+ if (this.columnWidths == null || newColCount != this.columnWidths.length) {
+ this.setLayoutState(LAYOUT_REDO);
+ }
+
+ if (this.getLayoutState() == LAYOUT_REDO) {
+ this.computeColumnWidths(context, newColCount);
+ }
+
+ return super.layout(context, top, bottom);
+ }
+
+
+ public void paint(LayoutContext context, int x, int y) {
+
+ if (this.skipPaint(context, x, y)) {
+ return;
+ }
+
+ this.paintChildren(context, x, y);
+
+ this.paintSelectionFrame(context, x, y, true);
+ }
+
+ //============================================================ PRIVATE
+
+ private static Set captionOrColumnStyles = new HashSet();
+
+ static {
+ captionOrColumnStyles.add(CSS.TABLE_CAPTION);
+ captionOrColumnStyles.add(CSS.TABLE_COLUMN);
+ captionOrColumnStyles.add(CSS.TABLE_COLUMN_GROUP);
+ }
+
+
+ private int[] columnWidths;
+ private int horizonalSpacing;
+ private int verticalSpacing;
+
+ private static class CountingCallback implements ElementOrRangeCallback {
+
+ public int getCount() {
+ return this.count;
+ }
+
+ public void reset() {
+ this.count = 0;
+ }
+
+ public void onElement(Element child, String displayStyle) {
+ this.count++;
+ }
+
+ public void onRange(Element parent, int startOffset, int endOffset) {
+ this.count++;
+ }
+
+ private int count;
+ }
+
+ /**
+ * Performs a quick count of this table's columns. If the count has changed, we
+ * must re-layout the entire table.
+ */
+ private int computeColumnCount(LayoutContext context) {
+
+ Element tableElement = this.findContainingElement();
+ final int[] columnCounts = new int[1]; // work around Java's insistence on final
+ columnCounts[0] = 0;
+ final StyleSheet styleSheet = context.getStyleSheet();
+ final CountingCallback callback = new CountingCallback();
+ LayoutUtils.iterateTableRows(styleSheet, tableElement, this.getStartOffset(), this.getEndOffset(), new ElementOrRangeCallback() {
+ public void onElement(Element child, String displayStyle) {
+ LayoutUtils.iterateTableCells(styleSheet, child, callback);
+ columnCounts[0] = Math.max(columnCounts[0], callback.getCount());
+ callback.reset();
+ }
+ public void onRange(Element parent, int startOffset, int endOffset) {
+ LayoutUtils.iterateTableCells(styleSheet, parent, startOffset, endOffset, callback);
+ columnCounts[0] = Math.max(columnCounts[0], callback.getCount());
+ callback.reset();
+ }
+
+ });
+
+ return columnCounts[0];
+ }
+
+ private void computeColumnWidths(final LayoutContext context, int columnCount) {
+
+ this.columnWidths = new int[columnCount];
+
+ if (columnCount == 0) {
+ return;
+ }
+
+
+ this.horizonalSpacing = 0;
+ this.verticalSpacing = 0;
+ int myWidth = this.getWidth();
+ int availableWidth = myWidth;
+
+ if (!this.isAnonymous()) {
+ Styles styles = context.getStyleSheet().getStyles(this.getElement());
+ this.horizonalSpacing = styles.getBorderSpacing().getHorizontal();
+ this.verticalSpacing = styles.getBorderSpacing().getVertical();
+
+ // width available for columns
+ // Since we apply margins/borders/padding to the TableBodyBox, they're
+ // not reflected in the width of this box. Thus, we subtract them here
+ availableWidth -=
+ + styles.getMarginLeft().get(myWidth)
+ + styles.getBorderLeftWidth()
+ + styles.getPaddingLeft().get(myWidth)
+ + styles.getPaddingRight().get(myWidth)
+ + styles.getBorderRightWidth()
+ + styles.getMarginRight().get(myWidth);
+ }
+
+ int totalColumnWidth = this.horizonalSpacing;
+ int columnWidth = (availableWidth - this.horizonalSpacing * (columnCount + 1)) / columnCount;
+ for (int i = 0; i < this.columnWidths.length - 1; i++) {
+ System.err.print(" " + columnWidth);
+ this.columnWidths[i] = columnWidth;
+ totalColumnWidth += columnWidth + this.horizonalSpacing;
+ }
+
+ // Due to rounding errors in the expression above, we calculate the
+ // width of the last column separately, to make it exact.
+ this.columnWidths[this.columnWidths.length - 1] = availableWidth - totalColumnWidth - this.horizonalSpacing;
+
+ }
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/layout/TableCellBox.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/layout/TableCellBox.java
new file mode 100644
index 0000000..583c9ac
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/layout/TableCellBox.java
@@ -0,0 +1,61 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.layout;
+
+import java.util.List;
+
+import org.eclipse.wst.xml.vex.core.internal.css.Styles;
+import org.eclipse.wst.xml.vex.core.internal.dom.Element;
+
+
+/**
+ * Represents an element with display:table-cell, or a generated, anonymous
+ * table cell.
+ */
+public class TableCellBox extends AbstractBlockBox {
+
+ /**
+ * Class constructor for non-anonymous table cells.
+ *
+ * @param context LayoutContext to use.
+ * @param parent Parent box.
+ * @param element Element with which this box is associated.
+ */
+ public TableCellBox(LayoutContext context, BlockBox parent, Element element, int width) {
+ super(context, parent, element);
+ Styles styles = context.getStyleSheet().getStyles(element);
+ this.setWidth(width
+ - styles.getBorderLeftWidth()
+ - styles.getPaddingLeft().get(parent.getWidth())
+ - styles.getPaddingRight().get(parent.getWidth())
+ - styles.getBorderRightWidth());
+ }
+
+ public TableCellBox(LayoutContext context, BlockBox parent, int startOffset, int endOffset, int width) {
+ super(context, parent, startOffset, endOffset);
+ this.setWidth(width);
+ }
+
+ protected List createChildren(LayoutContext context) {
+ return this.createBlockBoxes(context, this.getStartOffset(), this.getEndOffset(), this.getWidth(), null, null);
+ }
+
+ public void setInitialSize(LayoutContext context) {
+ // we've already set width in the ctor
+ // override to avoid setting width again
+ this.setHeight(this.getEstimatedHeight(context));
+ }
+
+
+
+ //======================================================= PRIVATE
+
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/layout/TableRowBox.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/layout/TableRowBox.java
new file mode 100644
index 0000000..c0f5036
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/layout/TableRowBox.java
@@ -0,0 +1,242 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.layout;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.eclipse.wst.xml.vex.core.internal.core.Caret;
+import org.eclipse.wst.xml.vex.core.internal.core.Insets;
+import org.eclipse.wst.xml.vex.core.internal.dom.Element;
+
+
+/**
+ * Box representing a row in a table.
+ */
+public class TableRowBox extends AbstractBlockBox {
+
+ public TableRowBox(LayoutContext context, TableRowGroupBox parent, Element element) {
+ super(context, parent, element);
+ }
+
+
+ public TableRowBox(LayoutContext context, BlockBox parent, int startOffset, int endOffset) {
+ super(context, parent, startOffset, endOffset);
+ }
+
+
+ protected List createChildren(final LayoutContext context) {
+
+ final List children = new ArrayList();
+
+ Element element = this.findContainingElement();
+ final int[] widths = this.getTableBox().getColumnWidths();
+
+ LayoutUtils.iterateTableCells(context.getStyleSheet(), element, this.getStartOffset(), this.getEndOffset(), new ElementOrRangeCallback() {
+ private int column = 0;
+ public void onElement(Element child, String displayStyle) {
+ children.add(new TableCellBox(context, TableRowBox.this, child, widths[column++]));
+ }
+ public void onRange(Element parent, int startOffset, int endOffset) {
+ children.add(new TableCellBox(context, TableRowBox.this, startOffset, endOffset, widths[column++]));
+ }
+ });
+
+ return children;
+ }
+
+ /**
+ * Override drawBox to do nothing. Table rows have no borders in
+ * border-collapse:separate mode.
+ */
+ public void drawBox(LayoutContext context, int x, int y, int containerWidth, boolean drawBorders) {
+ }
+
+ public Caret getCaret(LayoutContext context, int offset) {
+
+ int hSpacing = this.getTableBox().getHorizonalSpacing();
+
+ Box[] children = this.getChildren();
+
+ // If we haven't yet laid out this block, estimate the caret.
+ if (children == null) {
+ int relative = offset - this.getStartOffset();
+ int size = this.getEndOffset() - this.getStartOffset();
+ int y = 0;
+ if (size > 0) {
+ y = this.getHeight() * relative / size;
+ }
+ return new HCaret(0, y, this.getWidth());
+ }
+
+ int x = hSpacing / 2;
+
+ int[] widths = this.getTableBox().getColumnWidths();
+
+ for (int i = 0; i < children.length; i++) {
+
+ Box child = children[i];
+
+ if (!child.hasContent()) {
+ continue; // TODO can we really have generated table cells?
+ }
+
+ if (offset < child.getStartOffset()) {
+ return new TextCaret(x, 0, this.getHeight());
+ }
+
+ if (offset >= child.getStartOffset()
+ && offset <= child.getEndOffset()) {
+
+ Caret caret = child.getCaret(context, offset);
+ caret.translate(child.getX(), child.getY());
+ return caret;
+ }
+
+ x += widths[i] + hSpacing;
+ }
+
+ return new TextCaret(x, 0, this.getHeight());
+ }
+
+
+ /**
+ * Override to return zero insets. Table rows have no insets in
+ * border-collapse:separate mode.
+ */
+ public Insets getInsets(LayoutContext context, int containerWidth) {
+ return Insets.ZERO_INSETS;
+ }
+
+ public int getMarginBottom() {
+ return 0;
+ }
+
+ public int getMarginTop() {
+ return 0;
+ }
+
+ public int getNextLineOffset(LayoutContext context, int offset, int x) {
+
+ BlockBox[] children = this.getContentChildren();
+ int[] widths = this.getTableBox().getColumnWidths();
+ int leftEdge = 0;
+
+ for (int i = 0; i < children.length; i++) {
+ if (leftEdge + widths[i] > x) {
+ int newOffset = children[i].getNextLineOffset(context, offset, x - leftEdge);
+ if (newOffset == children[i].getEndOffset() + 1) {
+ return -1;
+ } else {
+ return newOffset;
+ }
+ }
+ leftEdge += widths[i];
+ }
+
+ return -1;
+ }
+
+ public int getPreviousLineOffset(LayoutContext context, int offset, int x) {
+
+ BlockBox[] children = this.getContentChildren();
+ int[] widths = this.getTableBox().getColumnWidths();
+ int leftEdge = 0;
+
+ for (int i = 0; i < children.length; i++) {
+ if (leftEdge + widths[i] > x) {
+ int newOffset = children[i].getPreviousLineOffset(context, offset, x - leftEdge);
+ if (newOffset == children[i].getStartOffset() - 1) {
+ return -1;
+ } else {
+ return newOffset;
+ }
+ }
+ leftEdge += widths[i];
+ }
+
+ return -1;
+ }
+
+ /**
+ * Returns the TableBox associated with this row.
+ */
+ public TableBox getTableBox() {
+ return (TableBox) this.getParent().getParent().getParent();
+ }
+
+
+ protected int positionChildren(LayoutContext context) {
+
+ int hSpacing = this.getTableBox().getHorizonalSpacing();
+
+ int childX = hSpacing;
+ int topInset = 0;
+ int height = 0;
+ int bottomInset = 0;
+ for (int i = 0; i < this.getChildren().length; i++) {
+ Box child = this.getChildren()[i];
+ Insets insets = child.getInsets(context, this.getWidth());
+
+ childX += insets.getLeft();
+
+ child.setX(childX);
+
+ childX += child.getWidth() + insets.getRight() + hSpacing;
+
+ topInset = Math.max(topInset, insets.getTop());
+ height = Math.max(height, child.getHeight());
+ bottomInset = Math.max(bottomInset, insets.getBottom());
+ }
+
+ this.setHeight(topInset + height + bottomInset);
+
+ for (int i = 0; i < this.getChildren().length; i++) {
+ Box child = this.getChildren()[i];
+ child.setY(topInset);
+ child.setHeight(height);
+ }
+
+ return -1; // TODO revisit
+ }
+
+ public int viewToModel(LayoutContext context, int x, int y) {
+
+ Box[] children = this.getChildren();
+
+ if (children == null) {
+ int charCount = this.getEndOffset() - this.getStartOffset() - 1;
+ if (charCount == 0 || this.getHeight() == 0) {
+ return this.getEndOffset();
+ } else {
+ return this.getStartOffset()
+ + charCount * y / this.getHeight();
+ }
+ } else {
+ for (int i = 0; i < children.length; i++) {
+ Box child = children[i];
+ if (!child.hasContent()) {
+ continue;
+ }
+ if (x < child.getX()) {
+ return child.getStartOffset() - 1;
+ } else if (x < child.getX() + child.getWidth()) {
+ return child.viewToModel(context, x - child.getX(), y
+ - child.getY());
+ }
+ }
+ }
+
+ return this.getEndOffset();
+ }
+
+
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/layout/TableRowGroupBox.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/layout/TableRowGroupBox.java
new file mode 100644
index 0000000..b9f0a84
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/layout/TableRowGroupBox.java
@@ -0,0 +1,137 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.layout;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import org.eclipse.wst.xml.vex.core.internal.core.Insets;
+import org.eclipse.wst.xml.vex.core.internal.css.CSS;
+import org.eclipse.wst.xml.vex.core.internal.css.Styles;
+import org.eclipse.wst.xml.vex.core.internal.dom.Element;
+
+
+/**
+ * Container for TableRowBox objects. May correspond to an element with
+ * display:table-row-group, display:table-head-group, display:table-foot-group,
+ * or may be anonymous.
+ */
+public class TableRowGroupBox extends AbstractBlockBox {
+
+ /**
+ * Class constructor for non-anonymous table row groups.
+ *
+ * @param context LayoutContext to use.
+ * @param parent Parent of this box.
+ * @param element Element that generated this box.
+ */
+ public TableRowGroupBox(LayoutContext context, BlockBox parent, Element element) {
+ super(context, parent, element);
+ }
+
+
+ /**
+ * Class constructor for anonymous table row groups.
+ *
+ * @param context LayoutContext to use.
+ * @param parent Parent of this box.
+ * @param startOffset Start of the range encompassing the table.
+ * @param endOffset End of the range encompassing the table.
+ */
+ public TableRowGroupBox(LayoutContext context, BlockBox parent, int startOffset, int endOffset) {
+ super(context, parent, startOffset, endOffset);
+
+ }
+
+
+ protected List createChildren(final LayoutContext context) {
+ // TODO Auto-generated method stub
+
+ // Walk children in range
+ // - table-row children get non-anonymous TableRowBox
+ // - runs of others get anonymous TableRowBox
+
+ final List children = new ArrayList();
+
+ this.iterateChildrenByDisplayStyle(context.getStyleSheet(), childDisplayStyles, new ElementOrRangeCallback() {
+ public void onElement(Element child, String displayStyle) {
+ children.add(new TableRowBox(context, TableRowGroupBox.this, child));
+ }
+ public void onRange(Element parent, int startOffset, int endOffset) {
+ children.add(new TableRowBox(context, TableRowGroupBox.this, startOffset, endOffset));
+ }
+ });
+
+ return children;
+ }
+
+ public Insets getInsets(LayoutContext context, int containerWidth) {
+ return Insets.ZERO_INSETS;
+ }
+
+ public int getMarginBottom() {
+ return 0;
+ }
+
+ public int getMarginTop() {
+ return 0;
+ }
+
+ public void paint(LayoutContext context, int x, int y) {
+
+ if (this.skipPaint(context, x, y)) {
+ return;
+ }
+
+ this.paintChildren(context, x, y);
+
+ this.paintSelectionFrame(context, x, y, true);
+ }
+
+
+ protected int positionChildren(LayoutContext context) {
+
+ Styles styles = context.getStyleSheet().getStyles(this.findContainingElement());
+ int spacing = styles.getBorderSpacing().getVertical();
+
+ int childY = spacing;
+ for (int i = 0; i < this.getChildren().length; i++) {
+
+ TableRowBox child = (TableRowBox) this.getChildren()[i];
+ // TODO must force table row margins to be zero
+ Insets insets = child.getInsets(context, this.getWidth());
+
+ childY += insets.getTop();
+
+ child.setX(insets.getLeft());
+ child.setY(childY);
+
+ childY += child.getHeight() + insets.getBottom() + spacing;
+ }
+ this.setHeight(childY);
+
+ return -1; // TODO revisit
+ }
+
+
+ //====================================================== PRIVATE
+
+ private static Set childDisplayStyles = new HashSet();
+
+ static {
+ childDisplayStyles.add(CSS.TABLE_ROW);
+ }
+
+
+
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/layout/TextBox.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/layout/TextBox.java
new file mode 100644
index 0000000..1fd75c9
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/layout/TextBox.java
@@ -0,0 +1,277 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.layout;
+
+import org.eclipse.wst.xml.vex.core.internal.core.Caret;
+import org.eclipse.wst.xml.vex.core.internal.core.ColorResource;
+import org.eclipse.wst.xml.vex.core.internal.core.FontMetrics;
+import org.eclipse.wst.xml.vex.core.internal.core.FontResource;
+import org.eclipse.wst.xml.vex.core.internal.core.FontSpec;
+import org.eclipse.wst.xml.vex.core.internal.core.Graphics;
+import org.eclipse.wst.xml.vex.core.internal.css.Styles;
+import org.eclipse.wst.xml.vex.core.internal.dom.Element;
+
+
+/**
+ * An inline box containing text. The <code>getText</code> and <code>splitAt</code>
+ * methods are abstract and must be implemented by subclasses.
+ */
+public abstract class TextBox extends AbstractBox implements InlineBox {
+
+ private Element element;
+ private int baseline;
+
+ public static final char NEWLINE_CHAR = 0xa;
+ public static final String NEWLINE_STRING = "\n";
+
+ /**
+ * Class constructor.
+ * @param element Element containing the text. This is used for styling
+ * information.
+ */
+ public TextBox(Element element) {
+ this.element = element;
+ }
+
+ /**
+ * Causes the box to recalculate it size. Subclasses should call this from
+ * their constructors after they are initialized.
+ * @param context LayoutContext used to calculate size.
+ */
+ protected void calculateSize(LayoutContext context) {
+ String s = this.getText();
+ if (s.endsWith(NEWLINE_STRING)) {
+ s = s.substring(0, s.length() - 1);
+ }
+
+ Graphics g = context.getGraphics();
+ Styles styles = context.getStyleSheet().getStyles(this.getElement());
+ FontResource font = g.createFont(styles.getFont());
+ FontResource oldFont = g.setFont(font);
+ FontMetrics fm = g.getFontMetrics();
+ this.setWidth(g.stringWidth(s));
+ this.setHeight(styles.getLineHeight());
+ int halfLeading = (this.getHeight() - (fm.getAscent() + fm.getDescent())) / 2;
+ this.baseline = halfLeading + fm.getAscent();
+ g.setFont(oldFont);
+ font.dispose();
+ }
+
+
+ /**
+ * @see org.eclipse.wst.xml.vex.core.internal.layout.InlineBox#getBaseline()
+ */
+ public int getBaseline() {
+ return this.baseline;
+ }
+
+ /**
+ * @see org.eclipse.wst.xml.vex.core.internal.layout.Box#getCaret(org.eclipse.wst.xml.vex.core.internal.layout.LayoutContext, int)
+ */
+ public Caret getCaret(LayoutContext context, int offset) {
+ Graphics g = context.getGraphics();
+ Styles styles = context.getStyleSheet().getStyles(this.element);
+ FontResource oldFont = g.getFont();
+ FontResource font = g.createFont(styles.getFont());
+ g.setFont(font);
+ char[] chars = this.getText().toCharArray();
+ int x = g.charsWidth(chars, 0, offset - this.getStartOffset());
+ g.setFont(oldFont);
+ font.dispose();
+ return new TextCaret(x, 0, this.getHeight());
+ }
+
+ /**
+ * Returns the element that controls the styling for this text element.
+ */
+ public Element getElement() {
+ return this.element;
+ }
+
+ /**
+ * Return the text that comprises this text box. The actual text can come
+ * from the document content or from a static string.
+ */
+ public abstract String getText();
+
+ /**
+ * Returns true if the given character is one where a linebreak should
+ * occur, e.g. a space.
+ * @param c the character to test
+ */
+ public static boolean isSplitChar(char c) {
+ return Character.isWhitespace(c);
+ }
+
+ public boolean isEOL() {
+ String s = this.getText();
+ return s.length() > 0 && s.charAt(s.length() - 1) == NEWLINE_CHAR;
+ }
+
+ /**
+ * Paints a string as selected text.
+ * @param context LayoutContext to be used. It is assumed that the contained
+ * Graphics object is set up with the proper font.
+ * @param s String to draw
+ * @param x x-coordinate at which to draw the text
+ * @param y y-coordinate at which to draw the text
+ */
+ protected void paintSelectedText(LayoutContext context, String s, int x, int y) {
+ Graphics g = context.getGraphics();
+
+ boolean inSelectedBlock = false;
+ Element e = this.getElement();
+ while (e != null) {
+ Styles styles = context.getStyleSheet().getStyles(e);
+ if (styles.isBlock()) {
+ if (context.isElementSelected(e)) {
+ inSelectedBlock = true;
+ }
+ break;
+ }
+ e = e.getParent();
+ }
+
+ if (inSelectedBlock) {
+ g.setColor(g.getSystemColor(ColorResource.SELECTION_BACKGROUND));
+ g.drawString(s, x, y);
+ } else {
+ int width = g.stringWidth(s);
+ g.setColor(g.getSystemColor(ColorResource.SELECTION_BACKGROUND));
+ g.fillRect(x, y, width, this.getHeight());
+ g.setColor(g.getSystemColor(ColorResource.SELECTION_FOREGROUND));
+ g.drawString(s, x, y);
+ }
+ }
+
+ protected void paintTextDecoration(LayoutContext context, Styles styles, String s, int x, int y) {
+ int fontStyle = styles.getFont().getStyle();
+ Graphics g = context.getGraphics();
+ FontMetrics fm = g.getFontMetrics();
+
+ if ((fontStyle & FontSpec.UNDERLINE) != 0) {
+ int lineWidth = fm.getAscent()/12;
+ int ypos = y+fm.getAscent()+lineWidth;
+ paintBaseLine(g, s, x, ypos);
+ }
+ if ((fontStyle & FontSpec.OVERLINE) != 0) {
+ int lineWidth = fm.getAscent()/12;
+ int ypos = y + lineWidth/2;
+ paintBaseLine(g, s, x, ypos);
+ }
+ if ((fontStyle & FontSpec.LINE_THROUGH) != 0) {
+ int ypos = y + fm.getHeight()/2;
+ paintBaseLine(g, s, x, ypos);
+ }
+ }
+
+ /**
+ * Paint a line along the baseline of the text, for showing underline,
+ * overline and strike-through formatting.
+ * @param context LayoutContext to be used. It is assumed that the contained
+ * Graphics object is set up with the proper font.
+ * @param x x-coordinate at which to start drawing baseline
+ * @param y x-coordinate at which to start drawing baseline (adjusted
+ * to produce the desired under/over/though effect)
+ */
+ protected void paintBaseLine(Graphics g, String s, int x, int y) {
+ FontMetrics fm = g.getFontMetrics();
+ int width = g.stringWidth(s);
+ int lineWidth = fm.getAscent()/12;
+ g.setLineStyle(Graphics.LINE_SOLID);
+ g.setLineWidth(lineWidth);
+ g.drawLine(x, y, x+width, y);
+ }
+
+ /**
+ * @see org.eclipse.wst.xml.vex.core.internal.layout.InlineBox#split(org.eclipse.wst.xml.vex.core.internal.layout.LayoutContext, int, boolean)
+ */
+ public Pair split(LayoutContext context, int maxWidth, boolean force) {
+
+ char[] chars = this.getText().toCharArray();
+
+ if (chars.length == 0) {
+ throw new IllegalStateException();
+ }
+
+ Graphics g = context.getGraphics();
+ Styles styles = context.getStyleSheet().getStyles(this.element);
+ FontResource font = g.createFont(styles.getFont());
+ FontResource oldFont = g.setFont(font);
+
+ int split = 0;
+ int next = 1;
+ boolean eol = false; // end of line found
+ while (next < chars.length) {
+ if (isSplitChar(chars[next - 1])) {
+ if (g.charsWidth(chars, 0, next) <= maxWidth) {
+ split = next;
+ if (chars[next - 1] == NEWLINE_CHAR) {
+ eol = true;
+ break;
+ }
+ } else {
+ break;
+ }
+ }
+ next++;
+ }
+
+ if (force && split == 0) {
+ // find some kind of split
+ split = 1;
+ while (split < chars.length) {
+ if (g.charsWidth(chars, 0, split+1) > maxWidth) {
+ break;
+ }
+ split++;
+ }
+
+ }
+
+ // include any trailing spaces in the split
+ // this also grabs any leading spaces when split==0
+ if (!eol) {
+ while (split < chars.length - 1 && chars[split] == ' ') {
+ split++;
+ }
+ }
+
+ g.setFont(oldFont);
+ font.dispose();
+
+ return this.splitAt(context, split);
+ }
+
+ /**
+ * Return a pair of boxes representing a split at the given offset.
+ * If split is zero, then the returned left box should be null.
+ * If the split is equal to the length of the text, then the right box
+ * should be null.
+ *
+ * @param context LayoutContext used to calculate the sizes of the resulting
+ * boxes.
+ * @param offset location of the split, relative to the start of the
+ * text box.
+ * @return
+ */
+ public abstract Pair splitAt(LayoutContext context, int offset);
+
+ /**
+ * @see java.lang.Object#toString()
+ */
+ public String toString() {
+ return this.getText();
+ }
+
+
+
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/layout/TextCaret.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/layout/TextCaret.java
new file mode 100644
index 0000000..2fe5ff2
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/layout/TextCaret.java
@@ -0,0 +1,50 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.layout;
+
+import org.eclipse.wst.xml.vex.core.internal.core.Caret;
+import org.eclipse.wst.xml.vex.core.internal.core.Color;
+import org.eclipse.wst.xml.vex.core.internal.core.ColorResource;
+import org.eclipse.wst.xml.vex.core.internal.core.Graphics;
+import org.eclipse.wst.xml.vex.core.internal.core.Rectangle;
+
+/**
+ * A caret drawn as a vertical line between characters.
+ */
+public class TextCaret extends Caret {
+
+ private static final int LINE_WIDTH = 2;
+
+ private int height;
+
+ /**
+ * Class constructor
+ * @param x x-coordinate of the caret
+ * @param y y-coordinate of the top of the caret
+ * @param height height of the caret
+ */
+ public TextCaret(int x, int y, int height) {
+ super(x, y);
+ this.height = height;
+ }
+
+ public void draw(Graphics g, Color color) {
+ ColorResource newColor = g.createColor(color);
+ ColorResource oldColor = g.setColor(newColor);
+ g.fillRect(this.getX(), this.getY(), LINE_WIDTH, height);
+ g.setColor(oldColor);
+ newColor.dispose();
+ }
+
+ public Rectangle getBounds() {
+ return new Rectangle(this.getX(), this.getY(), LINE_WIDTH, height);
+ }
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/layout/package.html b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/layout/package.html
new file mode 100644
index 0000000..8042cab
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/layout/package.html
@@ -0,0 +1,124 @@
+<?xml version='1.0'?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html>
+
+ <head>
+
+ <title>The Vex Layout Engine</title>
+
+ </head>
+
+ <body>
+
+ <h1>The Vex Layout Engine</h1>
+
+ <p>The purpose of the Vex Layout Engine is to create a visual
+ representation of a document given a CSS stylesheet. This visual
+ representation is a nested hierarchy of rectangular boxes,
+ implemented as a tree of objects each implementing the Box
+ interface. Each box has the following properties.</p>
+
+ <ul>
+
+ <li>The x- and y-coordinates of the box. These coordinates are
+ relative to the containing parent box for efficiency: if a
+ box's position changes, it need not recalculate the
+ positions of its children. For boxes corresponding to a document
+ element, the coordinates match the coordinates of the CSS content
+ area, which is inside any margins, borders, and padding.</li>
+
+ <li>The height and width of the box. For boxes corresponding to a
+ document element, this is the height and width of the CSS content
+ area, that is, the area inside any margins, borders, and
+ padding.</li>
+
+ <li>The document element associated with the box, if any.</li>
+
+ <li>The child boxes of the box, if any.</li>
+
+ <li>The range of document offsets represented by the box, if
+ any.</li>
+
+ </ul>
+
+ <p>Additionally, each box supports the following operations.</p>
+
+ <ul>
+
+ <li>Determine a <i>caret</i>, that is, a visual representation of
+ the current insertion point, for a given document offset.</li>
+
+ <li>Return the document offset closest to a given (x, y) position
+ relative to the top-left corner of the box.</li>
+
+ </ul>
+
+ <p>There are two main types of box. <i>Block boxes</i> normally
+ contain other boxes and stack their children vertically (with
+ TableRowBox being an exception whose children are stacked
+ horizontally). <i>Inline boxes</i> may contain child boxes or other
+ content such as text; their children are stacked horizontally and
+ they may be split to wrap content into a series of lines.</p>
+
+ <p>A box may acquire its children in a number of ways. Boxes
+ associated with document elements (e.g. BlockElementBox) create
+ their own children by inspecting the child nodes of the associated
+ element. This can happen immediately in the box's constructor,
+ or may be deferred for performance. In other cases, a box's
+ children are created by its parent and passed to its constructor.
+ Finally, simple boxes such as DocumentTextBox and PlaceholderBox
+ have no child boxes; they serve simply to display content or to aid
+ in navigation.</p>
+
+ <h2>Layout Process</h2>
+
+ <p>The layout process begins with a VexWidgetImpl object, which
+ creates a RootBox containing a BlockElementBox corresponding with
+ the document's root element. Each BlockElementBox does not
+ initially create its children. Instead, it estimates its height
+ based on the current font size and the number of characters covered
+ by the element.</p>
+
+ <p>At any one time, the user can only view a particular horizontal
+ band of the document. To avoid unnecessary work, the VexWidgetImpl
+ only requests that the RootBox lay out (that is, create and
+ position children) within that horizontal band. Each
+ BlockElementBox that is asked to layout a band creates its
+ children, then propagates the layout call to children that fall
+ inside the band. Newly created children that fall outside the
+ visible band are not laid out. Instead, they are left with their
+ initial size estimates.</p>
+
+ <p>While many BlockElementBoxes contain only other BlockElementBox
+ children, eventually a BlockElementBox will need to lay out a run
+ of inline content, that is, text and inline-formatted
+ elements...</p>
+
+ <h2>Document Changes and Layout Updates</h2>
+
+ <h2>Content Boxes</h2>
+
+ <p>A box is said to <i>have content</i> if it corresponds to a
+ range of character offsets in the source document. The simplest
+ type of content box is a DocumentTextBox, which corresponds to a
+ sequence of characters in the source document. BlockElementBoxes
+ and InlineElementBoxes also have content. Boxes such as
+ StaticTextBox and DrawableBox are purely visual and do not
+ represent document content.</p>
+
+ <h2>Keyboard Navigation</h2>
+
+ <p>The user can change the insertion point by pressing one of the
+ four arrow keys, the PgUp or PgDn keys, or the Home or End keys.
+ The challenge of keyboard navigation is to calculate a new document
+ offset for the insertion point given the current insertion point
+ and the desired direction of motion. Complicating the situation is
+ the fact that ranges of offsets may be invisible (e.g. inside a
+ display:none block); the new offset must be visible.</p>
+
+ <h2>Mouse Navigation</h2>
+
+ </body>
+
+</html>
+
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/swing/AwtColor.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/swing/AwtColor.java
new file mode 100644
index 0000000..49ae9ba
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/swing/AwtColor.java
@@ -0,0 +1,32 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.swing;
+
+import org.eclipse.wst.xml.vex.core.internal.core.ColorResource;
+
+/**
+ * Wrapper for the AWT Color class.
+ */
+public class AwtColor implements ColorResource {
+
+ private java.awt.Color awtColor;
+
+ public AwtColor(java.awt.Color awtColor) {
+ this.awtColor = awtColor;
+ }
+
+ java.awt.Color getAwtColor() {
+ return this.awtColor;
+ }
+
+ public void dispose() {
+ }
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/swing/AwtDisplayDevice.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/swing/AwtDisplayDevice.java
new file mode 100644
index 0000000..b1ed41b
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/swing/AwtDisplayDevice.java
@@ -0,0 +1,53 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * John Krasnay - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.wst.xml.vex.core.internal.swing;
+
+import java.awt.Toolkit;
+
+import org.eclipse.wst.xml.vex.core.internal.core.DisplayDevice;
+
+
+/**
+ * Swing implementation of the Display Device abstract class
+ * @author Vincent Lambert, Matrox Imaging
+ */
+public class AwtDisplayDevice extends DisplayDevice{
+
+ private boolean loaded = false;
+ private int horizontalPPI = 72;
+ private int verticalPPI = 72;
+
+ /** Creates a new instance of AwtDisplayDevice */
+ public AwtDisplayDevice() {}
+
+ public int getHorizontalPPI(){
+ if (!this.loaded) {
+ this.load();
+ }
+ return this.horizontalPPI;
+ }
+
+ public int getVerticalPPI(){
+ if (!this.loaded) {
+ this.load();
+ }
+ return this.verticalPPI;
+ }
+
+ private void load() {
+ Toolkit tk = Toolkit.getDefaultToolkit();
+
+ this.horizontalPPI = tk.getScreenResolution();
+ this.verticalPPI = tk.getScreenResolution();
+ this.loaded = true;
+ }
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/swing/AwtFont.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/swing/AwtFont.java
new file mode 100644
index 0000000..9eb3cee
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/swing/AwtFont.java
@@ -0,0 +1,32 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.swing;
+
+import org.eclipse.wst.xml.vex.core.internal.core.FontResource;
+
+/**
+ * Wrapper for the AWT Font class.
+ */
+public class AwtFont implements FontResource {
+
+ private java.awt.Font awtFont;
+
+ public AwtFont(java.awt.Font awtFont) {
+ this.awtFont = awtFont;
+ }
+
+ java.awt.Font getAwtFont() {
+ return this.awtFont;
+ }
+
+ public void dispose() {
+ }
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/swing/AwtFontMetrics.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/swing/AwtFontMetrics.java
new file mode 100644
index 0000000..afeff5a
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/swing/AwtFontMetrics.java
@@ -0,0 +1,54 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.swing;
+
+import org.eclipse.wst.xml.vex.core.internal.core.FontMetrics;
+
+/**
+ * Wrapper for the AWT FontMetrics class.
+ */
+public class AwtFontMetrics implements FontMetrics {
+
+ private java.awt.FontMetrics awtFontMetrics;
+
+ public AwtFontMetrics(java.awt.FontMetrics awtFontMetrics) {
+ this.awtFontMetrics = awtFontMetrics;
+ }
+
+ /**
+ * @see org.eclipse.wst.xml.vex.core.internal.core.FontMetrics#getAscent()
+ */
+ public int getAscent() {
+ return this.awtFontMetrics.getAscent();
+ }
+
+ /**
+ * @see org.eclipse.wst.xml.vex.core.internal.core.FontMetrics#getDescent()
+ */
+ public int getDescent() {
+ return this.awtFontMetrics.getDescent();
+ }
+
+ /**
+ * @see org.eclipse.wst.xml.vex.core.internal.core.FontMetrics#getHeight()
+ */
+ public int getHeight() {
+ return this.awtFontMetrics.getHeight();
+ }
+
+ /**
+ * @see org.eclipse.wst.xml.vex.core.internal.core.FontMetrics#getLeading()
+ */
+ public int getLeading() {
+ return this.awtFontMetrics.getLeading();
+ }
+
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/swing/AwtGraphics.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/swing/AwtGraphics.java
new file mode 100644
index 0000000..6cff27e
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/swing/AwtGraphics.java
@@ -0,0 +1,218 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.swing;
+
+import java.awt.BasicStroke;
+import java.awt.GraphicsEnvironment;
+import java.awt.Stroke;
+import java.util.HashSet;
+import java.util.Set;
+
+import javax.swing.UIManager;
+
+import org.eclipse.wst.xml.vex.core.internal.core.Color;
+import org.eclipse.wst.xml.vex.core.internal.core.ColorResource;
+import org.eclipse.wst.xml.vex.core.internal.core.FontMetrics;
+import org.eclipse.wst.xml.vex.core.internal.core.FontResource;
+import org.eclipse.wst.xml.vex.core.internal.core.FontSpec;
+import org.eclipse.wst.xml.vex.core.internal.core.Graphics;
+import org.eclipse.wst.xml.vex.core.internal.core.Rectangle;
+
+
+/**
+ * Implementation of the Vex Graphics interface, mapping it to a
+ * org.eclipse.swt.graphics.GC object.
+ */
+public class AwtGraphics implements Graphics {
+
+ private java.awt.Graphics2D g;
+ private int originX;
+ private int originY;
+
+ private static Set availableFontFamilies = new HashSet();
+
+ static {
+ GraphicsEnvironment ge =
+ GraphicsEnvironment.getLocalGraphicsEnvironment();
+ String[] names = ge.getAvailableFontFamilyNames();
+ for (int i = 0; i < names.length; i++) {
+ availableFontFamilies.add(names[i].toLowerCase());
+ }
+ }
+
+ /**
+ * Class constructor.
+ * @param gc SWT GC to which we are drawing.
+ */
+ public AwtGraphics(java.awt.Graphics2D g) {
+ this.g = g;
+ }
+
+ public void dispose() {
+ }
+
+ public void drawChars(char[] chars, int offset, int length, int x, int y) {
+ this.g.drawString(new String(chars, offset, length), x + originX, y + originY + this.g.getFontMetrics().getAscent());
+ }
+
+ public void drawLine(int x1, int y1, int x2, int y2) {
+ this.g.drawLine(x1 + originX, y1 + originY, x2 + originX, y2 + originY);
+ }
+
+ public void drawOval(int x, int y, int width, int height) {
+ this.g.drawOval(x + originX, y + originY, width, height);
+ }
+
+ public void drawRect(int x, int y, int width, int height) {
+ this.g.drawRect(x + originX, y + originY, width, height);
+ }
+
+ public void drawString(String s, int x, int y) {
+ this.g.drawString(s, x + originX, y + originY + this.g.getFontMetrics().getAscent());
+ }
+
+ public void fillOval(int x, int y, int width, int height) {
+ this.g.fillOval(x + originX, y + originY, width, height);
+ }
+
+ public void fillRect(int x, int y, int width, int height) {
+ this.g.fillRect(x + originX, y + originY, width, height);
+ }
+
+ public Rectangle getClipBounds() {
+ java.awt.Rectangle r = this.g.getClipBounds();
+ return new Rectangle(r.x - originX, r.y - originY , r.width, r.height);
+ }
+
+ public ColorResource getColor() {
+ return new AwtColor(this.g.getColor());
+ }
+
+ public FontResource getFont() {
+ return new AwtFont(this.g.getFont());
+ }
+
+ public FontMetrics getFontMetrics() {
+ return new AwtFontMetrics(this.g.getFontMetrics());
+ }
+
+ public int getLineStyle() {
+ return this.lineStyle;
+ }
+
+ public int getLineWidth() {
+ return this.lineWidth;
+ }
+
+ public boolean isAntiAliased() {
+ return false;
+ }
+
+ public void setAntiAliased(boolean antiAliased) {
+ }
+
+ public ColorResource setColor(ColorResource color) {
+ ColorResource oldColor = this.getColor();
+ this.g.setColor(((AwtColor) color).getAwtColor());
+ return oldColor;
+ }
+
+ public FontResource setFont(FontResource font) {
+ FontResource oldFont = this.getFont();
+ this.g.setFont(((AwtFont) font).getAwtFont());
+ return oldFont;
+ }
+
+ public void setLineStyle(int lineStyle) {
+ this.lineStyle = lineStyle;
+ this.makeStroke();
+ }
+
+ public void setLineWidth(int lineWidth) {
+ this.lineWidth = lineWidth;
+ this.makeStroke();
+ }
+
+ public int charsWidth(char[] data, int offset, int length) {
+ return this.stringWidth(new String(data, offset, length));
+ }
+
+ public void setOrigin(int x, int y) {
+ this.originX = x;
+ this.originY = y;
+ }
+
+ public ColorResource createColor(Color rgb) {
+ return new AwtColor(
+ new java.awt.Color(
+ rgb.getRed(), rgb.getGreen(), rgb.getBlue()));
+ }
+
+ public FontResource createFont(FontSpec fontSpec) {
+ int style = java.awt.Font.PLAIN;
+ if ((fontSpec.getStyle() & FontSpec.BOLD) > 0) {
+ style |= java.awt.Font.BOLD;
+ }
+ if ((fontSpec.getStyle() & FontSpec.ITALIC) > 0) {
+ style |= java.awt.Font.ITALIC;
+ }
+ int size = Math.round(fontSpec.getSize());
+
+ String name = "sans-serif";
+ String[] names = fontSpec.getNames();
+ for (int i = 0; i < names.length; i++) {
+ if (availableFontFamilies.contains(names[i])) {
+ name = names[i];
+ break;
+ }
+ }
+ return new AwtFont(new java.awt.Font(name, style, size));
+ }
+
+ public ColorResource getSystemColor(int id) {
+
+ if (id == ColorResource.SELECTION_BACKGROUND) {
+ return new AwtColor(UIManager.getColor("TextPane.selectionBackground"));
+ } else if (id == ColorResource.SELECTION_FOREGROUND) {
+ return new AwtColor(UIManager.getColor("TextPane.selectionForeground"));
+ } else {
+ return new AwtColor(java.awt.Color.BLACK);
+ }
+ }
+
+ public int stringWidth(String s) {
+ return this.g.getFontMetrics().stringWidth(s);
+ }
+
+ //============================================================= PRIVATE
+
+ private int lineWidth = 1;
+ private int lineStyle;
+
+ private void makeStroke() {
+
+ float dashLength = this.lineWidth;
+ if (this.lineStyle == LINE_DASH) {
+ dashLength = 3 * this.lineWidth;
+ }
+
+ Stroke stroke = new BasicStroke(
+ (float) this.lineWidth,
+ BasicStroke.CAP_SQUARE, // to be compatible with SWT GC
+ BasicStroke.JOIN_MITER,
+ 1.0f,
+ new float[] { dashLength, dashLength },
+ 0.0f);
+
+ this.g.setStroke(stroke);
+ }
+
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/swing/NullSelection.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/swing/NullSelection.java
new file mode 100644
index 0000000..2dd1970
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/swing/NullSelection.java
@@ -0,0 +1,20 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.swing;
+
+
+/**
+ * Passed to {@link SelectionListener}s to indicate that nothing is currently
+ * selected.
+ */
+public class NullSelection implements Selection {
+
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/swing/Selection.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/swing/Selection.java
new file mode 100644
index 0000000..99d8801
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/swing/Selection.java
@@ -0,0 +1,19 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.swing;
+
+/**
+ * Marker interface for classes that represent selections passed to
+ * {@link SelectionListener}s.
+ */
+public interface Selection {
+
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/swing/SelectionListener.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/swing/SelectionListener.java
new file mode 100644
index 0000000..7c785f1
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/swing/SelectionListener.java
@@ -0,0 +1,26 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.swing;
+
+/**
+ * Interface for receiving selection change events. Typically, objects
+ * implementing this interface are registered with a
+ * {@link SelectionProvider}.
+ * @see Selection
+ */
+public interface SelectionListener {
+
+ /**
+ * Called by a selection provider when the current selection changes.
+ * @param selection the new {@link Selection}.
+ */
+ public void selectionChanged(Selection selection);
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/swing/SelectionProvider.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/swing/SelectionProvider.java
new file mode 100644
index 0000000..13fb2c6
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/swing/SelectionProvider.java
@@ -0,0 +1,33 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.swing;
+
+/**
+ * Represents a class that can fire selection change events to
+ * {@link SelectionChangeListener}s.
+ */
+public interface SelectionProvider {
+
+ /**
+ * Add the given {@link SelectionChangeListener} to be notified when
+ * the current selection changes.
+ * @param listener SelectionChangeListener to add.
+ */
+ public void addSelectionListener(SelectionListener listener);
+
+ /**
+ * Remove the given {@link SelectionChangeListener} from the
+ * notification list.
+ * @param listener SelectionChangeListener to remove.
+ */
+ public void removeSelectionListener(SelectionListener listener);
+
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/swing/SelectionProviderImpl.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/swing/SelectionProviderImpl.java
new file mode 100644
index 0000000..5c909dd
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/swing/SelectionProviderImpl.java
@@ -0,0 +1,63 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.swing;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * Implementation of the {@link SelectionProvider} interface. Also acts as
+ * a selection event multiplexor: any events received on its
+ * {@link SelectionListener} interface are relayed to any registered listeners.
+ */
+public class SelectionProviderImpl
+ implements SelectionProvider, SelectionListener {
+
+ private List listeners = new ArrayList();
+
+ /**
+ * @see net.sf.vex.core.SelectionProvider#addSelectionChangeListener(net.sf.vex.SelectionListener)
+ */
+ public void addSelectionListener(SelectionListener listener) {
+ this.listeners.add(listener);
+
+ }
+
+ /**
+ * Call <code>selectionChanged</code> on all registered listeners.
+ * @param selection Selection that has changed.
+ */
+ public void fireSelectionChanged(Selection selection) {
+ for (Iterator iter = listeners.iterator(); iter.hasNext(); ) {
+ SelectionListener listener = (SelectionListener) iter.next();
+ //long start = System.currentTimeMillis();
+ listener.selectionChanged(selection);
+ //long end = System.currentTimeMillis();
+ //System.out.println("" + (end-start) + ": " + listener);
+ }
+ }
+
+ /**
+ * @see net.sf.vex.core.SelectionProvider#removeSelectionChangeListener(net.sf.vex.SelectionListener)
+ */
+ public void removeSelectionListener(SelectionListener listener) {
+ this.listeners.remove(listener);
+ }
+
+ /**
+ * @see net.sf.vex.core.SelectionListener#selectionChanged(net.sf.vex.Selection)
+ */
+ public void selectionChanged(Selection selection) {
+ this.fireSelectionChanged(selection);
+ }
+
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/swing/VexComponent.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/swing/VexComponent.java
new file mode 100644
index 0000000..534c1e5
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/swing/VexComponent.java
@@ -0,0 +1,1133 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.swing;
+
+import java.awt.Cursor;
+import java.awt.Dimension;
+import java.awt.Graphics;
+import java.awt.Graphics2D;
+import java.awt.GraphicsConfiguration;
+import java.awt.GraphicsDevice;
+import java.awt.GraphicsEnvironment;
+import java.awt.Rectangle;
+import java.awt.Toolkit;
+import java.awt.datatransfer.Clipboard;
+import java.awt.datatransfer.DataFlavor;
+import java.awt.datatransfer.StringSelection;
+import java.awt.datatransfer.Transferable;
+import java.awt.datatransfer.UnsupportedFlavorException;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.ComponentAdapter;
+import java.awt.event.ComponentEvent;
+import java.awt.event.ComponentListener;
+import java.awt.event.FocusEvent;
+import java.awt.event.FocusListener;
+import java.awt.event.KeyEvent;
+import java.awt.event.KeyListener;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+import java.awt.event.MouseListener;
+import java.awt.event.MouseMotionAdapter;
+import java.awt.event.MouseMotionListener;
+import java.io.IOException;
+import java.net.URL;
+import java.util.MissingResourceException;
+import java.util.ResourceBundle;
+
+import javax.swing.AbstractAction;
+import javax.swing.Action;
+import javax.swing.ActionMap;
+import javax.swing.InputMap;
+import javax.swing.JComponent;
+import javax.swing.JPopupMenu;
+import javax.swing.KeyStroke;
+import javax.swing.Scrollable;
+import javax.swing.SwingUtilities;
+import javax.swing.Timer;
+import javax.swing.UIManager;
+import javax.swing.undo.CannotRedoException;
+import javax.swing.undo.CannotUndoException;
+import javax.xml.parsers.ParserConfigurationException;
+
+
+import org.eclipse.wst.xml.vex.core.internal.VEXCorePlugin;
+import org.eclipse.wst.xml.vex.core.internal.core.Color;
+import org.eclipse.wst.xml.vex.core.internal.core.ColorResource;
+import org.eclipse.wst.xml.vex.core.internal.core.DisplayDevice;
+import org.eclipse.wst.xml.vex.core.internal.css.StyleSheet;
+import org.eclipse.wst.xml.vex.core.internal.dom.Document;
+import org.eclipse.wst.xml.vex.core.internal.dom.DocumentFragment;
+import org.eclipse.wst.xml.vex.core.internal.dom.DocumentValidationException;
+import org.eclipse.wst.xml.vex.core.internal.dom.Element;
+import org.eclipse.wst.xml.vex.core.internal.layout.Box;
+import org.eclipse.wst.xml.vex.core.internal.layout.BoxFactory;
+import org.eclipse.wst.xml.vex.core.internal.widget.HostComponent;
+import org.eclipse.wst.xml.vex.core.internal.widget.IBoxFilter;
+import org.eclipse.wst.xml.vex.core.internal.widget.VexWidgetImpl;
+import org.xml.sax.SAXException;
+/**
+ *
+ */
+public class VexComponent extends JComponent implements Scrollable, SelectionProvider {
+
+ private VexWidgetImpl impl;
+
+ private ActionMap staticActionMap = createActionMap();
+ private static InputMap staticInputMap = createInputMap();
+
+ private Timer caretTimer;
+
+ private int originX = 0;
+ private int originY = 0;
+
+ // Temporary clipboard is used during begin/end work
+ private Clipboard clipboard =
+ Toolkit.getDefaultToolkit().getSystemClipboard();
+
+ private SelectionProviderImpl selectionProvider =
+ new SelectionProviderImpl();
+
+ private static ResourceBundle uiStringBundle;
+
+ // Last VexComponent that had the focus. We need this sometimes to
+ // determine the target of an action.
+ private static VexComponent lastFocusedComponent;
+
+
+ //======================================================= LISTENERS
+
+ private ActionListener caretTimerListener = new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ VexComponent.this.impl.toggleCaret();
+ }
+ };
+
+ private ComponentListener componentListener = new ComponentAdapter() {
+ public void componentResized(ComponentEvent e) {
+ int width = VexComponent.this.getWidth();
+ VexComponent.this.impl.setLayoutWidth(width);
+ }
+ };
+
+ private FocusListener focusListener = new FocusListener() {
+ public void focusGained(FocusEvent e) {
+ lastFocusedComponent = VexComponent.this;
+ impl.setFocus(true);
+ VexComponent.this.caretTimer.start();
+ }
+
+ public void focusLost(FocusEvent e) {
+ impl.setFocus(false);
+ VexComponent.this.caretTimer.stop();
+ }
+ };
+
+ private KeyListener keyListener = new KeyListener() {
+ public void keyPressed(KeyEvent e) {
+
+
+ //final char NEWLINE = 0xa;
+
+ if (VexComponent.this.impl.getDocument() == null) {
+ Toolkit.getDefaultToolkit().beep();
+ return;
+ }
+
+ if (e.getKeyCode() == KeyEvent.VK_SHIFT){
+ return;
+ }
+
+ try {
+ InputMap map = VexComponent.staticInputMap;
+ KeyStroke keyStroke = KeyStroke.getKeyStrokeForEvent(e);
+ Object keyStrokeBinding = map.get(keyStroke);
+ if (keyStrokeBinding != null) {
+ BaseAction action = (BaseAction)VexComponent.this.staticActionMap.get(map.get(keyStroke));
+ action.actionPerformed(new ActionEvent(VexComponent.this, 0, ""), VexComponent.this.impl);
+ }else if (!Character.isISOControl(e.getKeyChar())
+ && !e.isControlDown()) {
+ // We check e.isControlDown() to ensure Ctrl-Space does not
+ // also enter a space.
+ if (VexComponent.this.impl.hasSelection()) {
+ VexComponent.this.impl.deleteSelection();
+ }
+ insertChar(e.getKeyChar());
+ }
+ } catch (DocumentValidationException ex) {
+ Toolkit.getDefaultToolkit().beep();
+ } catch (Exception ex){
+ Toolkit.getDefaultToolkit().beep();
+ ex.printStackTrace();
+ }
+
+
+ }
+
+ public void keyReleased(KeyEvent e) {
+ }
+
+ public void keyTyped(KeyEvent e) {}
+
+ };
+
+
+
+
+ private MouseListener mouseListener = new MouseAdapter() {
+ public void mousePressed(MouseEvent e) {
+
+ boolean isButton1 =
+ (e.getModifiers() & MouseEvent.BUTTON1_MASK) > 0;
+
+ if (VexComponent.this.impl.getRootBox() != null && isButton1) {
+ if (hasFocus()) {
+ int offset = VexComponent.this.impl.viewToModel(e.getX() - originX, e.getY() - originY);
+ moveTo(offset);
+
+ if (e.getClickCount() == 2) {
+ selectWord();
+ }
+ }
+ requestFocus();
+ }
+ }
+ };
+
+
+
+ private MouseMotionListener mouseMotionListener =
+ new MouseMotionAdapter() {
+ public void mouseDragged(MouseEvent e) {
+ boolean isButton1 =
+ (e.getModifiers() & MouseEvent.BUTTON1_MASK) > 0;
+
+ if (VexComponent.this.impl.getRootBox() != null && isButton1) {
+ int offset = VexComponent.this.viewToModel(e.getX(), e.getY());
+ moveTo(offset, true);
+ requestFocus();
+ }
+ }
+ };
+
+
+ /**
+ * @see net.sf.vex.core.SelectionProvider#addSelectionListener(net.sf.vex.SelectionListener)
+ */
+ public void addSelectionListener(SelectionListener listener) {
+ this.selectionProvider.addSelectionListener(listener);
+ }
+
+ public boolean canPaste() {
+
+ // TODO: sacrifice paste toolbar button state for performance
+ // (see note below).
+ if (true) {
+ return true;
+ }
+
+ // TODO: This next line takes a looong time in X11
+ // ~130ms on a Pentum-M 1.6GHz on Linux 2.4.21/XFree86 4.3.0
+ Transferable tfbl = this.clipboard.getContents(null);
+ DataFlavor flavor = VexSelection.VEX_DOCUMENT_FRAGMENT_FLAVOR;
+ if (!tfbl.isDataFlavorSupported(flavor)) {
+ return this.canPasteText();
+ }
+
+ DocumentFragment frag;
+ try {
+ frag = (DocumentFragment) tfbl.getTransferData(flavor);
+ } catch (UnsupportedFlavorException ex) {
+ return false;
+ } catch (IOException ex) {
+ return false;
+ }
+
+ return this.impl.canInsertFragment(frag);
+ }
+
+ /**
+ * Returns true if the clipboard has plain text content that can be
+ * pasted. Used to enable/disable the "paste text" action of a containing
+ * application.
+ */
+ public boolean canPasteText() {
+
+ // TODO: sacrifice paste toolbar button state for performance
+ // (see note below).
+ if (true) {
+ return true;
+ }
+
+ // TODO: This next line takes a looong time in X11
+ // ~130ms on a Pentum-M 1.6GHz on Linux 2.4.21/XFree86 4.3.0
+ Transferable tfbl = this.clipboard.getContents(null);
+ DataFlavor plainText = new DataFlavor(String.class, "text/plain");
+ return tfbl.isDataFlavorSupported(plainText)
+ && this.impl.canPasteText();
+ }
+
+ /**
+ * Copy the current selection to the clipboard.
+ */
+ public void copySelection() {
+ if (this.impl.hasSelection()) {
+ StringSelection sel =
+ new VexSelection(
+ this.impl.getSelectedText(),
+ this.impl.getSelectedFragment());
+ this.clipboard.setContents(sel, sel);
+ }
+ }
+
+ public void cutSelection() {
+ this.copySelection();
+ this.deleteSelection();
+ }
+
+ public void deleteSelection() {
+ this.impl.deleteSelection();
+ }
+
+ /**
+ * Returns the VexComponent that last had focus.
+ */
+ public static VexComponent getLastFocusedComponent() {
+ return lastFocusedComponent;
+ }
+
+ /**
+ * Returns a string from the resource bundle for the current locale.
+ * If the string is not found in the resource bundle, returns null.
+ *
+ * @param name property for which to return the string.
+ */
+ public static String getUIString(String name) {
+ if (uiStringBundle == null) {
+ uiStringBundle =
+ ResourceBundle.getBundle("net.sf.vex.app.UIStrings");
+ }
+
+ try {
+ return uiStringBundle.getString(name);
+ } catch (MissingResourceException ex) {
+ return null;
+ }
+ }
+
+
+ //------------------------------------------------ Scrollable methods
+
+ public Dimension getPreferredScrollableViewportSize() {
+ return this.getPreferredSize();
+ }
+
+ public int getScrollableBlockIncrement(
+ Rectangle visibleRect,
+ int orientation,
+ int direction) {
+ return Math.max(visibleRect.height - 40, 40);
+ }
+
+ public boolean getScrollableTracksViewportHeight() {
+ return false;
+ }
+
+ public boolean getScrollableTracksViewportWidth() {
+ return true;
+ }
+
+ public int getScrollableUnitIncrement(
+ Rectangle visibleRect,
+ int orientation,
+ int direction) {
+ return 20; // TODO: fix scrolling increment
+ }
+
+ public VexComponent() {
+ ActionMap customActionMap = new ActionMap();
+ customActionMap.setParent(staticActionMap);
+ this.setActionMap(customActionMap);
+
+ InputMap customInputMap = new InputMap();
+ customInputMap.setParent(staticInputMap);
+ this.setInputMap(JComponent.WHEN_FOCUSED, customInputMap);
+
+ this.addComponentListener(this.componentListener);
+ this.addFocusListener(this.focusListener);
+ this.addKeyListener(this.keyListener);
+ this.addMouseListener(this.mouseListener);
+ this.addMouseMotionListener(this.mouseMotionListener);
+ this.setCursor(Cursor.getPredefinedCursor(Cursor.TEXT_CURSOR));
+
+ this.caretTimer = new Timer(
+ UIManager.getInt("TextPane.caretBlinkRate"),
+ this.caretTimerListener);
+ this.caretTimer.start();
+
+
+ DisplayDevice.setCurrent(new AwtDisplayDevice());
+
+ impl = new VexWidgetImpl(this.hostComponent);
+
+ }
+
+ public void insertElement(Element element) throws DocumentValidationException {
+ this.impl.insertElement(element);
+ }
+
+ public void morph(Element element) throws DocumentValidationException {
+ this.impl.morph(element);
+ }
+
+ public void paint(Graphics g){
+ this.paintComponent(g);
+ }
+
+ protected void paintComponent(Graphics g) {
+
+ long start = 0;
+ if (VEXCorePlugin.getInstance().isDebugging()) {
+ start = System.currentTimeMillis();
+ }
+
+ if (this.impl.getDocument() == null){
+ return;
+ }
+
+ AwtGraphics awtg = new AwtGraphics((Graphics2D) g);
+ awtg.setOrigin(0, this.originY);
+
+ Color bgColor = impl.getBackgroundColor();
+ if (bgColor == null) {
+ bgColor = new Color(255, 255, 255);
+ }
+
+ ColorResource color = awtg.createColor(bgColor);
+ ColorResource oldColor = awtg.setColor(color);
+ Rectangle r = g.getClipBounds();
+ awtg.fillRect((int)r.getX() - this.originX, (int)r.getY() - this.originY,(int) r.getWidth(),(int) r.getHeight());
+ awtg.setColor(oldColor);
+ color.dispose();
+
+ this.impl.paint(awtg, 0, 0);
+
+
+
+/*
+ Graphics2D g2d = (Graphics2D) g;
+ if (this.isAntiAliased()) {
+ g2d.setRenderingHint(
+ RenderingHints.KEY_ANTIALIASING,
+ RenderingHints.VALUE_ANTIALIAS_ON);
+ }
+
+ impl.paint(new AwtGraphics(g2d), 0, 0);
+
+ if (this.caretVisible && this.isEnabled()) {
+ if (this.hasFocus()) {
+ g.setColor(Color.black);
+ } else {
+ g.setColor(Color.gray);
+ }
+ g2d.setStroke(caretStroke);
+ g2d.draw(this.caretShapes[0]);
+ }
+*/
+
+ if (VEXCorePlugin.getInstance().isDebugging()) {
+ long end = System.currentTimeMillis();
+ System.out.println("paint took " + (end - start) + "ms");
+ }
+ }
+
+ public void paste() throws DocumentValidationException {
+
+ if (!this.canPaste()) {
+ return;
+ }
+
+ try {
+ Transferable tfbl = this.clipboard.getContents(null);
+ DataFlavor flavor = VexSelection.VEX_DOCUMENT_FRAGMENT_FLAVOR;
+ if (tfbl.isDataFlavorSupported(flavor)) {
+ DocumentFragment frag =
+ (DocumentFragment) tfbl.getTransferData(flavor);
+ this.impl.insertFragment(frag);
+ } else {
+ this.pasteText();
+ }
+ } catch (IOException ex) {
+ ex.printStackTrace();
+ } catch (UnsupportedFlavorException ex) {
+ ex.printStackTrace();
+ }
+ }
+
+ public void pasteText() throws DocumentValidationException {
+
+ try {
+ Transferable tfbl = this.clipboard.getContents(null);
+ DataFlavor plainText = new DataFlavor(String.class, "text/plain");
+ if (tfbl.isDataFlavorSupported(plainText)) {
+ String text = (String) tfbl.getTransferData(plainText);
+ this.impl.insertText(text);
+ }
+ } catch (IOException ex) {
+ ex.printStackTrace();
+ } catch (UnsupportedFlavorException ex) {
+ ex.printStackTrace();
+ }
+ }
+
+ public void split() throws DocumentValidationException { this.impl.split(); }
+ public void undo() throws CannotUndoException { this.impl.undo(); }
+ public int viewToModel(int x, int y) { return this.impl.viewToModel(x, y); }
+
+ public void deleteNextChar() throws DocumentValidationException { this.impl.deleteNextChar(); }
+ public void deletePreviousChar() throws DocumentValidationException { this.impl.deletePreviousChar(); }
+ public void doWork(Runnable runnable) { this.impl.doWork(runnable); }
+ public void doWork(boolean savePosition, Runnable runnable) { this.impl.doWork(savePosition, runnable); }
+ public void endWork(boolean success) { this.impl.endWork(success); }
+ public Box findInnermostBox(IBoxFilter filter) { return this.impl.findInnermostBox(filter); }
+ public BoxFactory getBoxFactory() { return this.impl.getBoxFactory(); }
+ public int getCaretOffset() { return this.impl.getCaretOffset(); }
+ public Element getCurrentElement() { return this.impl.getCurrentElement(); }
+ public Document getDocument() { return this.impl.getDocument(); }
+ public int getLayoutWidth() { return this.impl.getLayoutWidth(); }
+ public int getSelectionEnd() { return this.impl.getSelectionEnd(); }
+ public int getSelectionStart() { return this.impl.getSelectionStart(); }
+ public DocumentFragment getSelectedFragment() { return this.impl.getSelectedFragment(); }
+ public String getSelectedText() { return this.impl.getSelectedText(); }
+ public StyleSheet getStyleSheet() { return this.impl.getStyleSheet(); }
+ public int getUndoDepth() { return this.impl.getUndoDepth(); }
+ public String[] getValidInsertElements() { return this.impl.getValidInsertElements(); }
+ public String[] getValidMorphElements() { return this.impl.getValidMorphElements(); }
+ public boolean hasSelection() { return this.impl.hasSelection(); }
+ public void insertChar(char c) throws DocumentValidationException { this.impl.insertChar(c); }
+ public void insertFragment(DocumentFragment frag) throws DocumentValidationException { this.impl.insertFragment(frag); }
+ public void insertText(String text) throws DocumentValidationException { this.impl.insertText(text); }
+ public boolean isDebugging() { return impl.isDebugging(); }
+ public void moveBy(int distance) { this.impl.moveBy(distance); }
+ public void moveBy(int distance, boolean select) { this.impl.moveBy(distance, select); }
+ public void moveTo(int offset) { this.impl.moveTo(offset); }
+ public void moveTo(int offset, boolean select) { this.impl.moveTo(offset, select); }
+ public void moveToLineEnd(boolean select) { this.impl.moveToLineEnd(select); }
+ public void moveToLineStart(boolean select) { this.impl.moveToLineStart(select); }
+ public void moveToNextLine(boolean select) { this.impl.moveToNextLine(select); }
+ public void moveToNextPage(boolean select) { this.impl.moveToNextPage(select); }
+ public void moveToNextWord(boolean select) { this.impl.moveToNextWord(select); }
+ public void moveToPreviousLine(boolean select) { this.impl.moveToPreviousLine(select); }
+ public void moveToPreviousPage(boolean select) { this.impl.moveToPreviousPage(select); }
+ public void moveToPreviousWord(boolean select) { this.impl.moveToPreviousWord(select); }
+
+
+ public void redo() throws CannotRedoException { this.impl.redo(); }
+ public void removeAttribute(String attributeName) { this.impl.removeAttribute(attributeName); }
+ public void savePosition(Runnable runnable) { this.impl.savePosition(runnable); }
+ public void selectAll() { this.impl.selectAll(); }
+ public void selectWord() { this.impl.selectWord(); }
+ public void setAttribute(String attributeName, String value) { this.impl.setAttribute(attributeName, value); }
+ public void setBoxFactory(BoxFactory boxFactory) { this.impl.setBoxFactory(boxFactory); }
+ public void setDebugging(boolean debugging) { impl.setDebugging(debugging); }
+ public void setDocument(Document doc, StyleSheet styleSheet) { this.impl.setDocument(doc, styleSheet); }
+ public void setDocument(URL docUrl, URL ssURL) throws IOException, ParserConfigurationException, SAXException {
+ this.impl.setDocument(docUrl, ssURL);
+ }
+ public void setLayoutWidth(int width) { this.impl.setLayoutWidth(width); }
+ public void setStyleSheet(StyleSheet styleSheet) { this.impl.setStyleSheet(styleSheet); }
+ public void setStyleSheet(URL ssUrl) throws IOException { this.impl.setStyleSheet(ssUrl); }
+
+ /**
+ * @see SelectionProvider#removeSelectionListener
+ */
+ public void removeSelectionListener(SelectionListener listener) {
+ this.selectionProvider.removeSelectionListener(listener);
+ }
+
+ public Action[] getInsertElementActions(){
+ String[] names = this.getValidInsertElements();
+ Action[] actions = new Action[names.length];
+ for (int i = 0; i < names.length; i++){
+ actions[i] = new InsertElementAction(names[i]);
+ }
+ return actions;
+ }
+
+ public Action[] getMorphElementActions(){
+ String[] names = this.getValidMorphElements();
+ Action[] actions = new Action[names.length];
+ for (int i = 0; i < names.length; i++){
+ actions[i] = new MorphAction(names[i]);
+ }
+ return actions;
+ }
+
+
+ /**
+ * Display a popup menu of valid elements to insert at the current caret
+ * position.
+ */
+ public void showInsertElementPopup() {
+
+ JPopupMenu popup = new JPopupMenu();
+
+ Action[] actions = this.getInsertElementActions();
+
+ if (actions.length == 0) {
+ return;
+ }
+
+ for (int i = 0; i < actions.length; i++) {
+ popup.add(actions[i]);
+ }
+
+ org.eclipse.wst.xml.vex.core.internal.core.Rectangle caretBounds = this.impl.getCaret().getBounds();
+ popup.show(this, caretBounds.getX() + 10, caretBounds.getY());
+ }
+
+ /**
+ * Display a popup menu of valid elements to which the current element
+ * can be morphed.
+ */
+ public void showMorphElementPopup() {
+
+ JPopupMenu popup = new JPopupMenu();
+
+ Action[] actions = this.getMorphElementActions();
+
+ if (actions.length == 0) {
+ return;
+ }
+
+ for (int i = 0; i < actions.length; i++) {
+ popup.add(actions[i]);
+ }
+
+ org.eclipse.wst.xml.vex.core.internal.core.Rectangle caretBounds = this.impl.getCaret().getBounds();
+ popup.show(this, caretBounds.getX() + 10, caretBounds.getY());
+ }
+
+
+ //========================================================= PRIVATE
+
+ private HostComponent hostComponent = new HostComponent() {
+
+ // This is needed to create a default Graphics object,
+ // but creating these is really slow, so we cache 'em.
+ private GraphicsEnvironment graphicsEnvironment = null;
+ private java.awt.image.BufferedImage dummyImage = null;
+
+ public org.eclipse.wst.xml.vex.core.internal.core.Graphics createDefaultGraphics() {
+ if (graphicsEnvironment == null) {
+ graphicsEnvironment =
+ GraphicsEnvironment.getLocalGraphicsEnvironment();
+
+ GraphicsDevice gdev = graphicsEnvironment.getDefaultScreenDevice();
+ GraphicsConfiguration gconf = gdev.getDefaultConfiguration();
+ dummyImage = gconf.createCompatibleImage(1, 1);
+ }
+ Graphics g = graphicsEnvironment.createGraphics(dummyImage);
+ return new AwtGraphics((Graphics2D) g);
+ }
+
+ public void fireSelectionChanged() {
+ VexComponent.this.fireSelectionChanged();
+ }
+
+ public void invokeLater(Runnable runnable) {
+ SwingUtilities.invokeLater(runnable);
+ }
+
+ public void repaint() {
+ VexComponent.this.repaint();
+ }
+
+ public void repaint(int x, int y, int width, int height) {
+ VexComponent.this.repaint();//VexComponent.this.repaint(x , y, width, height);
+ }
+
+ public void scrollTo(int left, int top) {
+ VexComponent.this.setOrigin(-left, -top);
+ }
+
+ public void setPreferredSize(int width, int height) {
+/* Dimension size = new Dimension(width, height);
+ VexComponent.this.setPreferredSize(size);
+ VexComponent.this.setSize(size);*/
+ }
+
+ public org.eclipse.wst.xml.vex.core.internal.core.Rectangle getViewport() {
+
+ return new org.eclipse.wst.xml.vex.core.internal.core.Rectangle(
+ 0 - VexComponent.this.originX,
+ 0 - VexComponent.this.originY,
+ VexComponent.this.getWidth(),
+ VexComponent.this.getHeight());
+
+ }
+ };
+
+
+
+ /**
+ * Action for inserting an element into the document at the current
+ * offset.
+ */
+ private class InsertElementAction extends AbstractAction {
+
+ private String name;
+
+ /**
+ * Class constructor.
+ *
+ * @param name Name of the element to insert.
+ */
+ public InsertElementAction(String name) {
+ super(name);
+ this.name = name;
+ }
+
+ public void actionPerformed(ActionEvent e) {
+ try {
+ insertElement(new Element(this.name));
+ } catch (DocumentValidationException ex) {
+ Toolkit.getDefaultToolkit().beep();
+ }
+ }
+ }
+
+
+ /**
+ * Action for morphing offset.
+ */
+ private class MorphAction extends AbstractAction {
+
+ private String name;
+
+ /**
+ * Class constructor.
+ *
+ * @param name Name of the element to which the current element
+ * is to be morphed.
+ */
+ public MorphAction(String name) {
+ super(name);
+ this.name = name;
+ }
+
+ public void actionPerformed(ActionEvent e) {
+ try {
+ morph(new Element(this.name));
+ } catch (DocumentValidationException ex) {
+ Toolkit.getDefaultToolkit().beep();
+ }
+ }
+ }
+
+ /**
+ * Runnable to layout the control.
+ */
+// private class LayoutRunnable implements Runnable {
+// private boolean force;
+// public LayoutRunnable(boolean force) {
+// this.force = force;
+// }
+//
+// public void run() {
+// relayout(force);
+// }
+// }
+
+
+ /**
+ * Base class for actions in the action map. Overloads the actionPerformed
+ * method to pass a VexComponent and allow a DocumentValidationException
+ * to be returned.
+ */
+ private abstract static class BaseAction extends AbstractAction {
+ /**
+ * Calls <code>actionPerformed(ActionEvent e, VexComponent c)</code>,
+ * and beeps if an exception is thrown.
+ * @param e ActionEvent
+ */
+ public void actionPerformed(ActionEvent e) {
+ try {
+ if (e.getSource() instanceof VexWidgetImpl) {
+ this.actionPerformed(e, (VexWidgetImpl) e.getSource());
+ }
+ } catch (Exception ex) {
+ ex.printStackTrace(); // TODO: log this
+ Toolkit.getDefaultToolkit().beep();
+ }
+ }
+
+ /**
+ * Performs the action. The corresponding VexComponent is provided,
+ * and any exception can be thrown.
+ * @param e the ActionEvent
+ * @param c the VexComponent
+ */
+ public abstract void actionPerformed(ActionEvent e, VexWidgetImpl c)
+ throws Exception;
+
+ }
+
+ public int getHeight(){
+ return impl.getHeight();
+ }
+
+ public void scrollBy(int x, int y){
+ setOrigin(originX - x, originY - y);
+ }
+
+ public void scrollTo(int x, int y){
+ setOrigin(-x, -y);
+ }
+
+ private void setOrigin(int x, int y){
+ this.originX = x;
+ this.originY = y;
+ }
+
+ /**
+ * Creates the ActionMap for the component.
+ */
+ private ActionMap createActionMap() {
+ ActionMap am = new ActionMap();
+
+ am.put("copy-selection", new BaseAction() {
+ public void actionPerformed(ActionEvent e, VexWidgetImpl c)
+ throws DocumentValidationException {
+ copySelection();
+ }
+ });
+ am.put("cut-selection", new BaseAction() {
+ public void actionPerformed(ActionEvent e, VexWidgetImpl c)
+ throws DocumentValidationException {
+ cutSelection();
+ }
+ });
+ am.put("delete-next-char", new BaseAction() {
+ public void actionPerformed(ActionEvent e, VexWidgetImpl c)
+ throws DocumentValidationException {
+ c.deleteNextChar();
+ }
+ });
+ am.put("delete-previous-char", new BaseAction() {
+ public void actionPerformed(ActionEvent e, VexWidgetImpl c)
+ throws DocumentValidationException {
+ c.deletePreviousChar();
+ }
+ });
+ am.put("move-to-document-end", new BaseAction() {
+ public void actionPerformed(ActionEvent e, VexWidgetImpl c)
+ throws DocumentValidationException {
+ c.moveTo(c.getDocument().getLength() - 1, false);
+ }
+ });
+ am.put("move-to-document-start", new BaseAction() {
+ public void actionPerformed(ActionEvent e, VexWidgetImpl c)
+ throws DocumentValidationException {
+ c.moveTo(1, false);
+ }
+ });
+ am.put("move-to-line-end", new BaseAction() {
+ public void actionPerformed(ActionEvent e, VexWidgetImpl c)
+ throws DocumentValidationException {
+ c.moveToLineEnd(false);
+ }
+ });
+ am.put("move-to-line-start", new BaseAction() {
+ public void actionPerformed(ActionEvent e, VexWidgetImpl c)
+ throws DocumentValidationException {
+ c.moveToLineStart(false);
+ }
+ });
+ am.put("move-to-next-char", new BaseAction() {
+ public void actionPerformed(ActionEvent e, VexWidgetImpl c)
+ throws DocumentValidationException {
+ int offset = c.getCaretOffset();
+ if (offset < c.getDocument().getLength() - 1) {
+ c.moveTo(offset + 1, false);
+ }
+ }
+ });
+ am.put("move-to-next-line", new BaseAction() {
+ public void actionPerformed(ActionEvent e, VexWidgetImpl c)
+ throws DocumentValidationException {
+ c.moveToNextLine(false);
+ }
+ });
+ am.put("move-to-next-word", new BaseAction() {
+ public void actionPerformed(ActionEvent e, VexWidgetImpl c)
+ throws DocumentValidationException {
+ c.moveToNextWord(false);
+ }
+ });
+ am.put("move-to-previous-char", new BaseAction() {
+ public void actionPerformed(ActionEvent e, VexWidgetImpl c)
+ throws DocumentValidationException {
+ int offset = c.getCaretOffset();
+ if (offset > 1) {
+ c.moveTo(offset - 1, false);
+ }
+ }
+ });
+ am.put("move-to-previous-line", new BaseAction() {
+ public void actionPerformed(ActionEvent e, VexWidgetImpl c)
+ throws DocumentValidationException {
+ c.moveToPreviousLine(false);
+ }
+ });
+ am.put("move-to-previous-word", new BaseAction() {
+ public void actionPerformed(ActionEvent e, VexWidgetImpl c)
+ throws DocumentValidationException {
+ c.moveToPreviousWord(false);
+ }
+ });
+ am.put("paste", new BaseAction() {
+ public void actionPerformed(ActionEvent e, VexWidgetImpl c)
+ throws DocumentValidationException {
+ paste();
+ }
+ });
+ am.put("paste-text", new BaseAction() {
+ public void actionPerformed(ActionEvent e, VexWidgetImpl c)
+ throws DocumentValidationException {
+ pasteText();
+ }
+ });
+ am.put("redo", new BaseAction() {
+ public void actionPerformed(ActionEvent e, VexWidgetImpl c)
+ throws DocumentValidationException {
+ c.redo();
+ }
+ });
+ am.put("select-to-document-end", new BaseAction() {
+ public void actionPerformed(ActionEvent e, VexWidgetImpl c)
+ throws DocumentValidationException {
+ c.moveTo(c.getDocument().getLength() - 1, true);
+ }
+ });
+ am.put("select-to-document-start", new BaseAction() {
+ public void actionPerformed(ActionEvent e, VexWidgetImpl c)
+ throws DocumentValidationException {
+ c.moveTo(1, true);
+ }
+ });
+ am.put("select-to-line-end", new BaseAction() {
+ public void actionPerformed(ActionEvent e, VexWidgetImpl c)
+ throws DocumentValidationException {
+ c.moveToLineEnd(true);
+ }
+ });
+ am.put("select-to-line-start", new BaseAction() {
+ public void actionPerformed(ActionEvent e, VexWidgetImpl c)
+ throws DocumentValidationException {
+ c.moveToLineStart(true);
+ }
+ });
+ am.put("select-to-next-char", new BaseAction() {
+ public void actionPerformed(ActionEvent e, VexWidgetImpl c)
+ throws DocumentValidationException {
+ int offset = c.getCaretOffset();
+ if (offset < c.getDocument().getLength() - 1) {
+ c.moveTo(offset + 1, true);
+ }
+ }
+ });
+ am.put("select-to-next-line", new BaseAction() {
+ public void actionPerformed(ActionEvent e, VexWidgetImpl c)
+ throws DocumentValidationException {
+ c.moveToNextLine(true);
+ }
+ });
+ am.put("select-to-next-word", new BaseAction() {
+ public void actionPerformed(ActionEvent e, VexWidgetImpl c)
+ throws DocumentValidationException {
+ c.moveToNextWord(true);
+ }
+ });
+ am.put("select-to-previous-char", new BaseAction() {
+ public void actionPerformed(ActionEvent e, VexWidgetImpl c)
+ throws DocumentValidationException {
+ int offset = c.getCaretOffset();
+ if (offset > 1) {
+ c.moveTo(offset - 1, true);
+ }
+ }
+ });
+ am.put("select-to-previous-line", new BaseAction() {
+ public void actionPerformed(ActionEvent e, VexWidgetImpl c)
+ throws DocumentValidationException {
+ c.moveToPreviousLine(true);
+ }
+ });
+ am.put("select-to-previous-word", new BaseAction() {
+ public void actionPerformed(ActionEvent e, VexWidgetImpl c)
+ throws DocumentValidationException {
+ c.moveToPreviousWord(true);
+ }
+ });
+
+ am.put("move-to-previous-page" , new BaseAction(){
+ public void actionPerformed(ActionEvent e, VexWidgetImpl c)
+ throws DocumentValidationException {
+ c.moveToPreviousPage(false);
+ }
+ });
+
+ am.put("select-to-previous-page" , new BaseAction(){
+ public void actionPerformed(ActionEvent e, VexWidgetImpl c)
+ throws DocumentValidationException {
+ c.moveToPreviousPage(true);
+ }
+ });
+
+ am.put("move-to-next-page" , new BaseAction(){
+ public void actionPerformed(ActionEvent e, VexWidgetImpl c)
+ throws DocumentValidationException {
+ c.moveToNextPage(false);
+ }
+ });
+
+ am.put("select-to-next-page" , new BaseAction(){
+ public void actionPerformed(ActionEvent e, VexWidgetImpl c)
+ throws DocumentValidationException {
+ c.moveToNextPage(true);
+ }
+ });
+
+ am.put("show-insert-element-popup", new BaseAction() {
+ public void actionPerformed(ActionEvent e, VexWidgetImpl c)
+ throws DocumentValidationException {
+
+ VexComponent.this.showInsertElementPopup();
+ }
+ });
+ am.put("show-morph-element-popup", new BaseAction() {
+ public void actionPerformed(ActionEvent e, VexWidgetImpl c)
+ throws DocumentValidationException {
+ VexComponent.this.showMorphElementPopup();
+ }
+ });
+ am.put("split", new BaseAction() {
+ public void actionPerformed(ActionEvent e, VexWidgetImpl c)
+ throws DocumentValidationException {
+ c.split();
+ }
+ });
+ am.put("undo", new BaseAction() {
+ public void actionPerformed(ActionEvent e, VexWidgetImpl c)
+ throws DocumentValidationException {
+ c.undo();
+ }
+ });
+
+ return am;
+ }
+
+
+ /**
+ * Create the input map for the control. This input map can reference
+ * both static and non-static actions.
+ */
+ public static InputMap createInputMap() {
+ InputMap im = new InputMap();
+ im.put(KeyStroke.getKeyStroke("LEFT"), "move-to-previous-char");
+ im.put(KeyStroke.getKeyStroke("RIGHT"), "move-to-next-char");
+ im.put(KeyStroke.getKeyStroke("shift LEFT"), "select-to-previous-char");
+ im.put(KeyStroke.getKeyStroke("shift RIGHT"), "select-to-next-char");
+
+ im.put(KeyStroke.getKeyStroke("control LEFT"), "move-to-previous-word");
+ im.put(KeyStroke.getKeyStroke("control RIGHT"), "move-to-next-word");
+ im.put(
+ KeyStroke.getKeyStroke("shift control LEFT"),
+ "select-to-previous-word");
+ im.put(
+ KeyStroke.getKeyStroke("shift control RIGHT"),
+ "select-to-next-word");
+
+ im.put(KeyStroke.getKeyStroke("HOME"), "move-to-line-start");
+ im.put(KeyStroke.getKeyStroke("END"), "move-to-line-end");
+ im.put(KeyStroke.getKeyStroke("shift HOME"), "select-to-line-start");
+ im.put(KeyStroke.getKeyStroke("shift END"), "select-to-line-end");
+
+ im.put(KeyStroke.getKeyStroke("PAGE_UP"), "move-to-previous-page");
+ im.put(KeyStroke.getKeyStroke("PAGE_DOWN"), "move-to-next-page");
+ im.put(KeyStroke.getKeyStroke("shift PAGE_UP"), "select-to-previous-page");
+ im.put(KeyStroke.getKeyStroke("shift PAGE_DOWN"), "select-to-next-page");
+
+ im.put(
+ KeyStroke.getKeyStroke("control HOME"),
+ "move-to-document-start");
+ im.put(KeyStroke.getKeyStroke("control END"), "move-to-document-end");
+ im.put(
+ KeyStroke.getKeyStroke("shift control HOME"),
+ "select-to-document-start");
+ im.put(
+ KeyStroke.getKeyStroke("shift control END"),
+ "select-to-document-end");
+
+ im.put(KeyStroke.getKeyStroke("UP"), "move-to-previous-line");
+ im.put(KeyStroke.getKeyStroke("DOWN"), "move-to-next-line");
+ im.put(KeyStroke.getKeyStroke("shift UP"), "select-to-previous-line");
+ im.put(KeyStroke.getKeyStroke("shift DOWN"), "select-to-next-line");
+
+ im.put(KeyStroke.getKeyStroke("BACK_SPACE"), "delete-previous-char");
+ im.put(KeyStroke.getKeyStroke("DELETE"), "delete-next-char");
+ im.put(KeyStroke.getKeyStroke("ENTER"), "split");
+
+ im.put(KeyStroke.getKeyStroke("control C"), "copy-selection");
+ im.put(KeyStroke.getKeyStroke("control X"), "cut-selection");
+ im.put(KeyStroke.getKeyStroke("control V"), "paste");
+ im.put(KeyStroke.getKeyStroke("shift control V"), "paste-text");
+
+ im.put(KeyStroke.getKeyStroke("control Y"), "redo");
+ im.put(KeyStroke.getKeyStroke("control Z"), "undo");
+
+ im.put(KeyStroke.getKeyStroke("control U"), "unwrap");
+
+ im.put(KeyStroke.getKeyStroke("control SPACE"), "show-insert-element-popup");
+ im.put(KeyStroke.getKeyStroke("control M"), "show-morph-element-popup");
+
+ return im;
+ }
+
+ /**
+ * Fires a selection changed event to registered selection change listeners.
+ * This method suppresses events that occur while there are outstanding
+ * beginWork calls.
+ */
+ private void fireSelectionChanged() {
+// if (this.beginWorkCount == 0) {
+// this.selectionProvider.fireSelectionChanged(
+// new VexComponentSelection(this));
+// }
+ }
+
+ /**
+ * Calls relayout from the Swing event loop rather than immediately.
+ *
+ * @param force Layout should be forced, e.g. when the width of
+ * the component changed.
+ */
+// public void relayoutLater(boolean force) {
+// if (this.rootBox != null) {
+// SwingUtilities.invokeLater(new LayoutRunnable(force));
+// }
+// }
+
+
+
+
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/swing/VexComponentSelection.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/swing/VexComponentSelection.java
new file mode 100644
index 0000000..4532830
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/swing/VexComponentSelection.java
@@ -0,0 +1,37 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.swing;
+
+import org.eclipse.wst.xml.vex.core.internal.widget.VexWidgetImpl;
+
+/**
+ * Generated by a {@VexComponent} when it gains focus, when its caret moves,
+ * and when its selection changes.
+ */
+public class VexComponentSelection implements Selection {
+
+ private VexWidgetImpl vexComponent;
+
+ /**
+ * Class constructor.
+ * @param vexComponent Component that is generating this change.
+ */
+ public VexComponentSelection(VexWidgetImpl vexComponent) {
+ this.vexComponent = vexComponent;
+ }
+
+ /**
+ * @return {@link VexComponent} that generated this selection event.
+ */
+ public VexWidgetImpl getVexComponent() {
+ return this.vexComponent;
+ }
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/swing/VexSelection.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/swing/VexSelection.java
new file mode 100644
index 0000000..21ba362
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/swing/VexSelection.java
@@ -0,0 +1,73 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.swing;
+
+import java.awt.datatransfer.DataFlavor;
+import java.awt.datatransfer.StringSelection;
+import java.awt.datatransfer.UnsupportedFlavorException;
+import java.io.IOException;
+
+import org.eclipse.wst.xml.vex.core.internal.dom.DocumentFragment;
+
+
+/**
+ * Represents a selection of a Vex document, which can be viewed as plaintext
+ * or as a document fragment.
+ */
+public class VexSelection extends StringSelection {
+
+ /**
+ * DataFlavor representing a Vex document fragment.
+ */
+ public static final DataFlavor VEX_DOCUMENT_FRAGMENT_FLAVOR =
+ new DataFlavor(DocumentFragment.class, DocumentFragment.MIME_TYPE);
+
+ private DataFlavor[] flavors;
+ private DocumentFragment frag;
+
+ /**
+ * Class constructor.
+ * @param s String representing the selection.
+ * @param frag Document fragment representing the selection.
+ */
+ public VexSelection(String s, DocumentFragment frag) {
+ super(s);
+ this.frag = frag;
+
+ DataFlavor[] superFlavors = super.getTransferDataFlavors();
+ this.flavors = new DataFlavor[superFlavors.length + 1];
+ System.arraycopy(superFlavors, 0, this.flavors, 0, superFlavors.length);
+ this.flavors[this.flavors.length - 1] = VEX_DOCUMENT_FRAGMENT_FLAVOR;
+ }
+
+ public Object getTransferData(DataFlavor flavor)
+ throws UnsupportedFlavorException, IOException {
+
+ if (flavor.equals(VEX_DOCUMENT_FRAGMENT_FLAVOR)) {
+ return this.frag;
+ } else {
+ return super.getTransferData(flavor);
+ }
+ }
+
+ public DataFlavor[] getTransferDataFlavors() {
+ return this.flavors;
+ }
+
+ public boolean isDataFlavorSupported(DataFlavor flavor) {
+ if (flavor.equals(VEX_DOCUMENT_FRAGMENT_FLAVOR)) {
+ return true;
+ } else {
+ return super.isDataFlavorSupported(flavor);
+ }
+ }
+
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/swt/DocumentFragmentTransfer.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/swt/DocumentFragmentTransfer.java
new file mode 100644
index 0000000..2065330
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/swt/DocumentFragmentTransfer.java
@@ -0,0 +1,98 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.swt;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+
+
+import org.eclipse.swt.dnd.ByteArrayTransfer;
+import org.eclipse.swt.dnd.TransferData;
+import org.eclipse.wst.xml.vex.core.internal.dom.DocumentFragment;
+
+/**
+ * Transfer object that handles Vex DocumentFragments.
+ */
+public class DocumentFragmentTransfer extends ByteArrayTransfer {
+
+ /**
+ * Returns the singleton instance of the DocumentFragmentTransfer.
+ */
+ public static DocumentFragmentTransfer getInstance() {
+ if (instance == null) {
+ instance = new DocumentFragmentTransfer();
+ }
+ return instance;
+ }
+
+ protected String[] getTypeNames() {
+ return typeNames;
+ }
+
+ protected int[] getTypeIds() {
+ return typeIds;
+ }
+
+ public void javaToNative (Object object, TransferData transferData) {
+ if (object == null || !(object instanceof DocumentFragment)) return;
+
+ if (isSupportedType(transferData)) {
+ DocumentFragment frag = (DocumentFragment) object;
+ try {
+ // write data to a byte array and then ask super to convert to pMedium
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ ObjectOutputStream oos = new ObjectOutputStream(out);
+ oos.writeObject(frag);
+ byte[] buffer = out.toByteArray();
+ oos.close();
+ super.javaToNative(buffer, transferData);
+ } catch (IOException e) {
+ }
+ }
+ }
+
+ public Object nativeToJava(TransferData transferData){
+
+ if (isSupportedType(transferData)) {
+ byte[] buffer = (byte[])super.nativeToJava(transferData);
+ if (buffer == null) return null;
+
+ try {
+ ByteArrayInputStream in = new ByteArrayInputStream(buffer);
+ ObjectInputStream ois = new ObjectInputStream(in);
+ Object object = ois.readObject();
+ ois.close();
+ return object;
+ } catch (ClassNotFoundException ex) {
+ return null;
+ } catch (IOException ex) {
+ return null;
+ }
+ }
+
+ return null;
+ }
+
+ //=================================================== PRIVATE
+
+ private static final String[] typeNames = { DocumentFragment.MIME_TYPE };
+ private static final int[] typeIds = {
+ ByteArrayTransfer.registerType(DocumentFragment.MIME_TYPE)
+ };
+
+ private static DocumentFragmentTransfer instance;
+
+ private DocumentFragmentTransfer() {
+ }
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/swt/KeyStroke.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/swt/KeyStroke.java
new file mode 100644
index 0000000..c08e7d7
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/swt/KeyStroke.java
@@ -0,0 +1,60 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.swt;
+
+import org.eclipse.swt.events.KeyEvent;
+
+/**
+ * Represents a keystroke and a certain set of modifiers.
+ */
+public class KeyStroke {
+
+ private char character;
+ private int keyCode;
+ private int stateMask;
+
+ /**
+ * Class constructor.
+ * @param character the key character
+ * @param keyCode the key code
+ * @param stateMask the set of modifiers
+ */
+ public KeyStroke(char character, int keyCode, int stateMask) {
+ this.character = character;
+ this.keyCode = keyCode;
+ this.stateMask = stateMask;
+ }
+
+ /**
+ * Class constructor.
+ * @param e a KeyEvent representing the key stroke
+ */
+ public KeyStroke(KeyEvent e) {
+ this.character = e.character;
+ this.keyCode = e.keyCode;
+ this.stateMask = e.stateMask;
+ }
+
+ public boolean equals(Object o) {
+ if (o == null || !(o instanceof KeyStroke)) {
+ return false;
+ }
+ KeyStroke other = (KeyStroke) o;
+ return this.character == other.character
+ && this.keyCode == other.keyCode
+ && this.stateMask == other.stateMask;
+ }
+
+ public int hashCode() {
+ return this.character + this.keyCode + this.stateMask;
+ }
+
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/swt/SwtColor.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/swt/SwtColor.java
new file mode 100644
index 0000000..0a6236d
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/swt/SwtColor.java
@@ -0,0 +1,33 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.swt;
+
+import org.eclipse.wst.xml.vex.core.internal.core.ColorResource;
+
+/**
+ * Wrapper for the SWT Color class.
+ */
+public class SwtColor implements ColorResource {
+
+ private org.eclipse.swt.graphics.Color swtColor;
+
+ public SwtColor(org.eclipse.swt.graphics.Color swtColor) {
+ this.swtColor = swtColor;
+ }
+
+ org.eclipse.swt.graphics.Color getSwtColor() {
+ return this.swtColor;
+ }
+
+ public void dispose() {
+ this.swtColor.dispose();
+ }
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/swt/SwtDisplayDevice.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/swt/SwtDisplayDevice.java
new file mode 100644
index 0000000..824d1e5
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/swt/SwtDisplayDevice.java
@@ -0,0 +1,60 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.swt;
+
+
+import org.eclipse.swt.widgets.Display;
+import org.eclipse.wst.xml.vex.core.internal.core.DisplayDevice;
+
+/**
+ * Adapts the DisplayDevice display to the current SWT display.
+ */
+public class SwtDisplayDevice extends DisplayDevice {
+
+ /**
+ * Class constructor.
+ */
+ public SwtDisplayDevice() {
+ // We used to do it like this, but it turns out sometimes we did it
+ // too early and getCurrent() returned null, so now the convoluted stuff below.
+// Display display = Display.getCurrent();
+// this.horizontalPPI = display.getDPI().x;
+// this.verticalPPI = display.getDPI().y;
+ }
+
+ public int getHorizontalPPI() {
+ if (!this.loaded) {
+ this.load();
+ }
+ return this.horizontalPPI;
+ }
+
+ public int getVerticalPPI() {
+ if (!this.loaded) {
+ this.load();
+ }
+ return this.verticalPPI;
+ }
+
+ private boolean loaded = false;
+ private int horizontalPPI = 72;
+ private int verticalPPI = 72;
+
+ private void load() {
+ Display display = Display.getCurrent();
+ if (display != null) {
+ this.horizontalPPI = display.getDPI().x;
+ this.verticalPPI = display.getDPI().y;
+ this.loaded = true;
+ }
+ }
+
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/swt/SwtFont.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/swt/SwtFont.java
new file mode 100644
index 0000000..0426b27
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/swt/SwtFont.java
@@ -0,0 +1,33 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.swt;
+
+import org.eclipse.wst.xml.vex.core.internal.core.FontResource;
+
+/**
+ * Wrapper for the SWT Font class.
+ */
+public class SwtFont implements FontResource {
+
+ private org.eclipse.swt.graphics.Font swtFont;
+
+ public SwtFont(org.eclipse.swt.graphics.Font swtFont) {
+ this.swtFont = swtFont;
+ }
+
+ org.eclipse.swt.graphics.Font getSwtFont() {
+ return this.swtFont;
+ }
+
+ public void dispose() {
+ this.swtFont.dispose();
+ }
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/swt/SwtFontMetrics.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/swt/SwtFontMetrics.java
new file mode 100644
index 0000000..cac979e
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/swt/SwtFontMetrics.java
@@ -0,0 +1,54 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.swt;
+
+import org.eclipse.wst.xml.vex.core.internal.core.FontMetrics;
+
+/**
+ * Wrapper for the SWT FontMetrics class.
+ */
+public class SwtFontMetrics implements FontMetrics {
+
+ private org.eclipse.swt.graphics.FontMetrics swtFontMetrics;
+
+ public SwtFontMetrics(org.eclipse.swt.graphics.FontMetrics swtFontMetrics) {
+ this.swtFontMetrics = swtFontMetrics;
+ }
+
+ /**
+ * @see org.eclipse.wst.xml.vex.core.internal.core.FontMetrics#getAscent()
+ */
+ public int getAscent() {
+ return this.swtFontMetrics.getAscent();
+ }
+
+ /**
+ * @see org.eclipse.wst.xml.vex.core.internal.core.FontMetrics#getDescent()
+ */
+ public int getDescent() {
+ return this.swtFontMetrics.getDescent();
+ }
+
+ /**
+ * @see org.eclipse.wst.xml.vex.core.internal.core.FontMetrics#getHeight()
+ */
+ public int getHeight() {
+ return this.swtFontMetrics.getHeight();
+ }
+
+ /**
+ * @see org.eclipse.wst.xml.vex.core.internal.core.FontMetrics#getLeading()
+ */
+ public int getLeading() {
+ return this.swtFontMetrics.getLeading();
+ }
+
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/swt/SwtGraphics.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/swt/SwtGraphics.java
new file mode 100644
index 0000000..d5e67e5
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/swt/SwtGraphics.java
@@ -0,0 +1,214 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.swt;
+
+
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.graphics.FontData;
+import org.eclipse.swt.graphics.GC;
+import org.eclipse.swt.widgets.Display;
+import org.eclipse.wst.xml.vex.core.internal.core.Color;
+import org.eclipse.wst.xml.vex.core.internal.core.ColorResource;
+import org.eclipse.wst.xml.vex.core.internal.core.FontMetrics;
+import org.eclipse.wst.xml.vex.core.internal.core.FontResource;
+import org.eclipse.wst.xml.vex.core.internal.core.FontSpec;
+import org.eclipse.wst.xml.vex.core.internal.core.Graphics;
+import org.eclipse.wst.xml.vex.core.internal.core.Rectangle;
+
+/**
+ * Implementation of the Vex Graphics interface, mapping it to a
+ * org.eclipse.swt.graphics.GC object.
+ *
+ * <p>The GC given to us by SWT is that of the Canvas, which is just a viewport
+ * into the document. This class therefore implements an "origin", which
+ * represents the top-left corner of the document relative to the
+ * top-left corner of the canvas. The x- and y-coordinates of the origin
+ * are always negative.</p>.
+ */
+public class SwtGraphics implements Graphics {
+
+ private GC gc;
+ private int originX;
+ private int originY;
+
+ /**
+ * Class constructor.
+ * @param gc SWT GC to which we are drawing.
+ */
+ public SwtGraphics(GC gc) {
+ this.gc = gc;
+ }
+
+ public void dispose() {
+ this.gc.dispose();
+ }
+
+ public void drawChars(char[] chars, int offset, int length, int x, int y) {
+ this.drawString(new String(chars, offset, length), x, y);
+
+ }
+
+ public void drawLine(int x1, int y1, int x2, int y2) {
+ this.gc.drawLine(x1 + originX, y1 + originY, x2 + originX, y2 + originY);
+ }
+
+ public void drawOval(int x, int y, int width, int height) {
+ this.gc.drawOval(x + originX, y + originY, width, height);
+ }
+
+ public void drawRect(int x, int y, int width, int height) {
+ this.gc.drawRectangle(x + originX, y + originY, width, height);
+ }
+
+ public void drawString(String s, int x, int y) {
+ this.gc.drawString(s, x + originX, y + originY, true);
+ }
+
+ /**
+ * Fills the given oval with the <em>foreground</em> color. This overrides
+ * the default SWT behaviour to be more like Swing.
+ */
+ public void fillOval(int x, int y, int width, int height) {
+ this.gc.fillOval(x + originX, y + originY, width, height);
+ }
+
+ /**
+ * Fills the given rectangle with the <em>foreground</em> color. This overrides
+ * the default SWT behaviour to be more like Swing.
+ */
+ public void fillRect(int x, int y, int width, int height) {
+ this.gc.fillRectangle(x + originX, y + originY, width, height);
+ }
+
+ public Rectangle getClipBounds() {
+ org.eclipse.swt.graphics.Rectangle r = this.gc.getClipping();
+ return new Rectangle(r.x - this.originX, r.y - this.originY, r.width, r.height);
+ }
+
+ public ColorResource getColor() {
+ return new SwtColor(this.gc.getForeground());
+ }
+
+ public FontResource getFont() {
+ return new SwtFont(this.gc.getFont());
+ }
+
+ public FontMetrics getFontMetrics() {
+ return new SwtFontMetrics(this.gc.getFontMetrics());
+ }
+
+ public int getLineStyle() {
+ return this.lineStyle;
+ }
+
+ public int getLineWidth() {
+ return this.gc.getLineWidth();
+ }
+
+ public boolean isAntiAliased() {
+ return false;
+ }
+
+ public void setAntiAliased(boolean antiAliased) {
+ }
+
+ public ColorResource setColor(ColorResource color) {
+ ColorResource oldColor = this.getColor();
+ this.gc.setForeground(((SwtColor) color).getSwtColor());
+ this.gc.setBackground(((SwtColor) color).getSwtColor());
+ return oldColor;
+ }
+
+ public FontResource setFont(FontResource font) {
+ FontResource oldFont = this.getFont();
+ this.gc.setFont(((SwtFont) font).getSwtFont());
+ return oldFont;
+ }
+
+ public void setLineStyle(int lineStyle) {
+ this.lineStyle = lineStyle;
+ switch (lineStyle) {
+ case LINE_DASH:
+ this.gc.setLineStyle(SWT.LINE_DASH);
+ break;
+ case LINE_DOT:
+ this.gc.setLineStyle(SWT.LINE_DOT);
+ break;
+ default:
+ this.gc.setLineStyle(SWT.LINE_SOLID);
+ break;
+ }
+ }
+
+ public void setLineWidth(int lineWidth) {
+ this.gc.setLineWidth(lineWidth);
+ }
+
+ public int charsWidth(char[] data, int offset, int length) {
+ return this.stringWidth(new String(data, offset, length));
+ }
+
+ public ColorResource createColor(Color rgb) {
+ return new SwtColor(
+ new org.eclipse.swt.graphics.Color(
+ null, rgb.getRed(), rgb.getGreen(), rgb.getBlue()));
+ }
+
+ public FontResource createFont(FontSpec fontSpec) {
+ int style = SWT.NORMAL;
+ if ((fontSpec.getStyle() & FontSpec.BOLD) > 0) {
+ style |= SWT.BOLD;
+ }
+ if ((fontSpec.getStyle() & FontSpec.ITALIC) > 0) {
+ style |= SWT.ITALIC;
+ }
+ int size = Math.round(fontSpec.getSize() * 72 / 90); // TODO: fix. SWT uses pts, AWT uses device units
+ String[] names = fontSpec.getNames();
+ FontData[] fd = new FontData[names.length];
+ for (int i = 0; i < names.length; i++) {
+ fd[i] = new FontData(names[i], size, style);
+ }
+ return new SwtFont(new org.eclipse.swt.graphics.Font(null, fd));
+ }
+
+ public ColorResource getSystemColor(int id) {
+
+ if (id == ColorResource.SELECTION_BACKGROUND) {
+ return new SwtColor(Display.getCurrent().getSystemColor(SWT.COLOR_LIST_SELECTION));
+ } else if (id == ColorResource.SELECTION_FOREGROUND) {
+ return new SwtColor(Display.getCurrent().getSystemColor(SWT.COLOR_LIST_SELECTION_TEXT));
+ } else {
+ return new SwtColor(Display.getCurrent().getSystemColor(-1));
+ }
+ }
+
+ /**
+ * Sets the origin of this graphics object. See the class description
+ * for more details.
+ *
+ * @param x x-coordinate of the origin, relative to the viewport.
+ * @param y y-coordinate of the origin, relative to the viewport.
+ */
+ public void setOrigin(int x, int y) {
+ this.originX = x;
+ this.originY = y;
+ }
+
+ public int stringWidth(String s) {
+ return this.gc.stringExtent(s).x;
+ }
+
+
+ //========================================================== PRIVATE
+
+ private int lineStyle = LINE_SOLID;
+
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/swt/Timer.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/swt/Timer.java
new file mode 100644
index 0000000..47d2960
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/swt/Timer.java
@@ -0,0 +1,80 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.swt;
+
+import org.eclipse.swt.widgets.Display;
+
+/**
+ * Periodic timer, built using the Display.timerExec method.
+ */
+public class Timer {
+
+ /**
+ * Class constructor. The timer must be explicitly started using the
+ * start() method.
+ * @param periodMs Milliseconds between each invocation.
+ * @param runnable Runnable to execute when the period expires.
+ */
+ public Timer(int periodMs, Runnable runnable) {
+ this.periodMs = periodMs;
+ this.runnable = runnable;
+ }
+
+ /**
+ * Reset the timer so that it waits another period before firing.
+ */
+ public void reset() {
+ if (this.started) {
+ this.stop();
+ this.start();
+ }
+ }
+
+ /**
+ * Start the timer.
+ */
+ public void start() {
+ if (!this.started) {
+ this.innerRunnable = new InnerRunnable();
+ Display.getCurrent().timerExec(this.periodMs, this.innerRunnable);
+ this.started = true;
+ }
+ }
+
+ /**
+ * Stop the timer.
+ */
+ public void stop() {
+ if (this.started) {
+ this.innerRunnable.discarded = true;
+ this.innerRunnable = null;
+ this.started = false;
+ }
+ }
+
+ //==================================================== PRIVATE
+
+ private Runnable runnable;
+ private int periodMs;
+ private boolean started = false;
+ private InnerRunnable innerRunnable;
+
+ private class InnerRunnable implements Runnable {
+ public boolean discarded = false;
+ public void run() {
+ if (!discarded) {
+ runnable.run();
+ //Display display = Display.getCurrent();
+ Display.getCurrent().timerExec(periodMs, this);
+ }
+ }
+ }
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/swt/VexWidget.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/swt/VexWidget.java
new file mode 100644
index 0000000..b545414
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/swt/VexWidget.java
@@ -0,0 +1,707 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.swt;
+
+import java.io.IOException;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import javax.swing.undo.CannotRedoException;
+import javax.swing.undo.CannotUndoException;
+import javax.xml.parsers.ParserConfigurationException;
+
+
+import org.eclipse.jface.action.IAction;
+import org.eclipse.jface.viewers.ISelection;
+import org.eclipse.jface.viewers.ISelectionChangedListener;
+import org.eclipse.jface.viewers.ISelectionProvider;
+import org.eclipse.jface.viewers.SelectionChangedEvent;
+import org.eclipse.jface.viewers.StructuredSelection;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.custom.PopupList;
+import org.eclipse.swt.dnd.Clipboard;
+import org.eclipse.swt.dnd.TextTransfer;
+import org.eclipse.swt.dnd.Transfer;
+import org.eclipse.swt.events.ControlEvent;
+import org.eclipse.swt.events.ControlListener;
+import org.eclipse.swt.events.FocusEvent;
+import org.eclipse.swt.events.FocusListener;
+import org.eclipse.swt.events.KeyEvent;
+import org.eclipse.swt.events.KeyListener;
+import org.eclipse.swt.events.MouseEvent;
+import org.eclipse.swt.events.MouseListener;
+import org.eclipse.swt.events.MouseMoveListener;
+import org.eclipse.swt.events.PaintEvent;
+import org.eclipse.swt.events.PaintListener;
+import org.eclipse.swt.events.SelectionEvent;
+import org.eclipse.swt.events.SelectionListener;
+import org.eclipse.swt.graphics.GC;
+import org.eclipse.swt.graphics.Point;
+import org.eclipse.swt.widgets.Canvas;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.ScrollBar;
+import org.eclipse.wst.xml.vex.core.internal.action.AbstractVexAction;
+import org.eclipse.wst.xml.vex.core.internal.action.DuplicateSelectionAction;
+import org.eclipse.wst.xml.vex.core.internal.action.IVexAction;
+import org.eclipse.wst.xml.vex.core.internal.action.NextTableCellAction;
+import org.eclipse.wst.xml.vex.core.internal.action.PreviousTableCellAction;
+import org.eclipse.wst.xml.vex.core.internal.action.RemoveElementAction;
+import org.eclipse.wst.xml.vex.core.internal.action.SplitAction;
+import org.eclipse.wst.xml.vex.core.internal.action.SplitItemAction;
+import org.eclipse.wst.xml.vex.core.internal.core.Color;
+import org.eclipse.wst.xml.vex.core.internal.core.ColorResource;
+import org.eclipse.wst.xml.vex.core.internal.core.DisplayDevice;
+import org.eclipse.wst.xml.vex.core.internal.core.Graphics;
+import org.eclipse.wst.xml.vex.core.internal.core.Rectangle;
+import org.eclipse.wst.xml.vex.core.internal.css.StyleSheet;
+import org.eclipse.wst.xml.vex.core.internal.dom.Document;
+import org.eclipse.wst.xml.vex.core.internal.dom.DocumentFragment;
+import org.eclipse.wst.xml.vex.core.internal.dom.DocumentValidationException;
+import org.eclipse.wst.xml.vex.core.internal.dom.Element;
+import org.eclipse.wst.xml.vex.core.internal.dom.Node;
+import org.eclipse.wst.xml.vex.core.internal.layout.Box;
+import org.eclipse.wst.xml.vex.core.internal.layout.BoxFactory;
+import org.eclipse.wst.xml.vex.core.internal.widget.HostComponent;
+import org.eclipse.wst.xml.vex.core.internal.widget.IBoxFilter;
+import org.eclipse.wst.xml.vex.core.internal.widget.IVexWidget;
+import org.eclipse.wst.xml.vex.core.internal.widget.VexWidgetImpl;
+import org.xml.sax.SAXException;
+
+/**
+ * An implementation of the Vex widget based on SWT.
+ */
+public class VexWidget
+ extends Canvas
+ implements IVexWidget, ISelectionProvider {
+
+ public VexWidget(Composite parent, int style) {
+ super(parent, style);
+
+ if (DisplayDevice.getCurrent() == null) {
+ DisplayDevice.setCurrent(new SwtDisplayDevice());
+ }
+
+ this.impl = new VexWidgetImpl(hostComponent);
+ this.setBackground(this.getDisplay().getSystemColor(SWT.COLOR_WHITE));
+
+ ScrollBar vbar = this.getVerticalBar();
+ if (vbar != null) {
+ vbar.setIncrement(20);
+ vbar.addSelectionListener(selectionListener);
+ }
+
+ this.addControlListener(this.controlListener);
+ this.addFocusListener(this.focusListener);
+ this.addKeyListener(this.keyListener);
+ this.addMouseListener(this.mouseListener);
+ this.addMouseMoveListener(this.mouseMoveListener);
+ this.addPaintListener(this.painter);
+ }
+
+ public void dispose() {
+ super.dispose();
+ this.impl.setFocus(false); // This stops the caret timer, in case the control
+ // is disposed before focus is lost.
+ }
+
+ //----------------------------------------- IInputProvider methods
+
+ public Object getInput() {
+ return this.impl.getDocument();
+ }
+
+ //----------------------------------------- ISelectionProvider methods
+
+ public void addSelectionChangedListener(ISelectionChangedListener listener) {
+ this.selectionListeners.add(listener);
+ }
+ public ISelection getSelection() {
+ return this.selection;
+ }
+ public void removeSelectionChangedListener(ISelectionChangedListener listener) {
+ this.selectionListeners.remove(listener);
+ }
+ public void setSelection(ISelection selection) {
+ throw new RuntimeException("Unexpected call to setSelection");
+ }
+
+ public void beginWork() { this.impl.beginWork(); }
+
+ public boolean canPaste() {
+ // TODO Auto-generated method stub
+ return false;
+ }
+
+ /**
+ * @see org.eclipse.wst.xml.vex.core.internal.widget.IVexWidget#canPasteText()
+ */
+ public boolean canPasteText() {
+ // TODO Auto-generated method stub
+ return false;
+ }
+
+ public boolean canRedo() { return this.impl.canRedo(); }
+ public boolean canUndo() { return this.impl.canUndo(); }
+ public boolean canUnwrap() { return this.impl.canUnwrap(); }
+
+
+ public Point computeSize(int wHint, int hHint, boolean changed) {
+ org.eclipse.swt.graphics.Rectangle r = this.getClientArea();
+ int height = r.height;
+
+ ScrollBar vbar = this.getVerticalBar();
+ if (vbar != null) {
+ height = vbar.getMaximum();
+ }
+ return new Point(r.width, height);
+ }
+
+ public void copySelection() {
+ Clipboard clipboard = new Clipboard(this.getDisplay());
+ Object[] data = {
+ this.getSelectedFragment(),
+ this.getSelectedText()
+ };
+ Transfer[] transfers = {
+ DocumentFragmentTransfer.getInstance(),
+ TextTransfer.getInstance()
+ };
+ clipboard.setContents(data, transfers);
+ }
+
+ public void cutSelection() {
+ this.copySelection();
+ this.deleteSelection();
+ }
+
+ public void deleteNextChar() throws DocumentValidationException { this.impl.deleteNextChar(); }
+ public void deletePreviousChar() throws DocumentValidationException { this.impl.deletePreviousChar(); }
+ public void deleteSelection() { this.impl.deleteSelection(); }
+ public void doWork(Runnable runnable) { this.impl.doWork(runnable); }
+ public void doWork(boolean savePosition, Runnable runnable) { this.impl.doWork(savePosition, runnable); }
+ public void endWork(boolean success) { this.impl.endWork(success); }
+ public Box findInnermostBox(IBoxFilter filter) { return this.impl.findInnermostBox(filter); }
+ public BoxFactory getBoxFactory() { return this.impl.getBoxFactory(); }
+ public int getCaretOffset() { return this.impl.getCaretOffset(); }
+ public Element getCurrentElement() { return this.impl.getCurrentElement(); }
+ public Document getDocument() { return this.impl.getDocument(); }
+ public int getLayoutWidth() { return this.impl.getLayoutWidth(); }
+ public int getSelectionEnd() { return this.impl.getSelectionEnd(); }
+ public int getSelectionStart() { return this.impl.getSelectionStart(); }
+ public DocumentFragment getSelectedFragment() { return this.impl.getSelectedFragment(); }
+ public String getSelectedText() { return this.impl.getSelectedText(); }
+ public StyleSheet getStyleSheet() { return this.impl.getStyleSheet(); }
+ public int getUndoDepth() { return this.impl.getUndoDepth(); }
+ public String[] getValidInsertElements() { return this.impl.getValidInsertElements(); }
+ public String[] getValidMorphElements() { return this.impl.getValidMorphElements(); }
+ public boolean hasSelection() { return this.impl.hasSelection(); }
+ public void insertChar(char c) throws DocumentValidationException { this.impl.insertChar(c); }
+ public void insertFragment(DocumentFragment frag) throws DocumentValidationException { this.impl.insertFragment(frag); }
+ public void insertElement(Element element) throws DocumentValidationException { this.impl.insertElement(element); }
+ public void insertText(String text) throws DocumentValidationException { this.impl.insertText(text); }
+ public boolean isDebugging() { return impl.isDebugging(); }
+ public void morph(Element element) throws DocumentValidationException { this.impl.morph(element); }
+ public void moveBy(int distance) { this.impl.moveBy(distance); }
+ public void moveBy(int distance, boolean select) { this.impl.moveBy(distance, select); }
+ public void moveTo(int offset) { this.impl.moveTo(offset); }
+ public void moveTo(int offset, boolean select) { this.impl.moveTo(offset, select); }
+ public void moveToLineEnd(boolean select) { this.impl.moveToLineEnd(select); }
+ public void moveToLineStart(boolean select) { this.impl.moveToLineStart(select); }
+ public void moveToNextLine(boolean select) { this.impl.moveToNextLine(select); }
+ public void moveToNextPage(boolean select) { this.impl.moveToNextPage(select); }
+ public void moveToNextWord(boolean select) { this.impl.moveToNextWord(select); }
+ public void moveToPreviousLine(boolean select) { this.impl.moveToPreviousLine(select); }
+ public void moveToPreviousPage(boolean select) { this.impl.moveToPreviousPage(select); }
+ public void moveToPreviousWord(boolean select) { this.impl.moveToPreviousWord(select); }
+
+
+ public IAction[] getValidInsertActions() {
+ String[] names = this.getValidInsertElements();
+ IAction[] actions = new IAction[names.length];
+ for (int i = 0; i < names.length; i++) {
+ actions[i] = new InsertElementAction(names[i]);
+ }
+ return actions;
+ }
+
+ public IAction[] getValidMorphActions() {
+ String[] names = this.getValidMorphElements();
+ IAction[] actions = new IAction[names.length];
+ for (int i = 0; i < names.length; i++) {
+ actions[i] = new MorphElementAction(names[i]);
+ }
+ return actions;
+ }
+
+
+ /**
+ * @see org.eclipse.wst.xml.vex.core.internal.widget.IVexWidget#paste()
+ */
+ public void paste() throws DocumentValidationException {
+ Clipboard clipboard = new Clipboard(this.getDisplay());
+ DocumentFragment frag = (DocumentFragment)
+ clipboard.getContents(DocumentFragmentTransfer.getInstance());
+ if (frag != null) {
+ this.insertFragment(frag);
+ } else {
+ this.pasteText();
+ }
+ }
+
+ /**
+ * @see org.eclipse.wst.xml.vex.core.internal.widget.IVexWidget#pasteText()
+ */
+ public void pasteText() throws DocumentValidationException {
+ Clipboard clipboard = new Clipboard(this.getDisplay());
+ String text = (String) clipboard.getContents(TextTransfer.getInstance());
+ if (text != null) {
+ this.insertText(text);
+ }
+ }
+
+ public void redo() throws CannotRedoException { this.impl.redo(); }
+ public void removeAttribute(String attributeName) { this.impl.removeAttribute(attributeName); }
+ public void savePosition(Runnable runnable) { this.impl.savePosition(runnable); }
+ public void selectAll() { this.impl.selectAll(); }
+ public void selectWord() { this.impl.selectWord(); }
+ public void setAttribute(String attributeName, String value) { this.impl.setAttribute(attributeName, value); }
+ public void setBoxFactory(BoxFactory boxFactory) { this.impl.setBoxFactory(boxFactory); }
+ public void setDebugging(boolean debugging) { impl.setDebugging(debugging); }
+ public void setDocument(Document doc, StyleSheet styleSheet) { this.impl.setDocument(doc, styleSheet); }
+ public void setDocument(URL docUrl, URL ssURL) throws IOException, ParserConfigurationException, SAXException {
+ this.impl.setDocument(docUrl, ssURL);
+ }
+ public void setLayoutWidth(int width) { this.impl.setLayoutWidth(width); }
+ public void setStyleSheet(StyleSheet styleSheet) { this.impl.setStyleSheet(styleSheet); }
+ public void setStyleSheet(URL ssUrl) throws IOException { this.impl.setStyleSheet(ssUrl); }
+
+ /**
+ * Show a popup list of elements that are valid to be inserted at the
+ * current position. If one of the elements is selected, it is inserted
+ * before returning.
+ */
+ public void showInsertElementPopup() {
+ PopupList list = new PopupList(this.getShell());
+ list.setItems(this.getValidInsertElements());
+
+ Rectangle caret = this.impl.getCaret().getBounds();
+ Point display = this.toDisplay(caret.getX() + 10, caret.getY());
+ String selected = list.open(new org.eclipse.swt.graphics.Rectangle(display.x, display.y, 200, 0));
+ if (selected != null) {
+ try {
+ this.insertElement(new Element(selected));
+ } catch (DocumentValidationException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+
+ /**
+ * Show a popup list of elements to which it is valid to morph the current
+ * element. If one of the elements is selected, the current element is
+ * morphed before returning.
+ */
+ public void showMorphElementPopup() {
+ PopupList list = new PopupList(this.getShell());
+ list.setItems(this.getValidMorphElements());
+
+ Rectangle caret = this.impl.getCaret().getBounds();
+ Point display = this.toDisplay(caret.getX() + 10, caret.getY());
+ String selected = list.open(new org.eclipse.swt.graphics.Rectangle(display.x, display.y, 200, 0));
+ if (selected != null) {
+ try {
+ this.morph(new Element(selected));
+ } catch (DocumentValidationException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+
+ public void split() throws DocumentValidationException { this.impl.split(); }
+ public void undo() throws CannotUndoException { this.impl.undo(); }
+ public int viewToModel(int x, int y) { return this.impl.viewToModel(x, y); }
+
+ //====================================================== PRIVATE
+
+ //------------------------------------------------------ Fields
+
+ private static final char CHAR_NONE = 0;
+ private static Map keyMap;
+
+ private VexWidgetImpl impl;
+
+ // Fields controlling scrolling
+ int originX = 0;
+ int originY = 0;
+
+ private List selectionListeners = new ArrayList();
+ private ISelection selection;
+
+ //------------------------------------------------------ Inner Classes
+
+ private Runnable caretTimerRunnable = new Runnable() {
+ public void run() {
+ impl.toggleCaret();
+ }
+ };
+ private Timer caretTimer = new Timer(500, this.caretTimerRunnable);
+
+ private ControlListener controlListener = new ControlListener() {
+ public void controlMoved(ControlEvent e) {
+ }
+ public void controlResized(ControlEvent e) {
+ org.eclipse.swt.graphics.Rectangle r = getClientArea();
+ // There seems to be a bug in SWT (at least on Linux/GTK+)
+ // When maximizing the editor, the width is first set to 1,
+ // then to the correct width
+ if (r.width == 1) {
+ return;
+ }
+ impl.setLayoutWidth(r.width);
+
+ ScrollBar vbar = getVerticalBar();
+ if (vbar != null) {
+ vbar.setThumb(r.height);
+ vbar.setPageIncrement(Math.round(r.height * 0.9f));
+ }
+ }
+ };
+
+ private FocusListener focusListener = new FocusListener() {
+ public void focusGained(FocusEvent e) {
+ impl.setFocus(true);
+ caretTimer.start();
+ }
+ public void focusLost(FocusEvent e) {
+ impl.setFocus(false);
+ caretTimer.stop();
+ }
+ };
+
+ private HostComponent hostComponent = new HostComponent() {
+
+ public Graphics createDefaultGraphics() {
+ if (VexWidget.this.isDisposed()) {
+ System.out.println("*** Woot! VexWidget is disposed!");
+ }
+ return new SwtGraphics(new GC(VexWidget.this));
+ }
+
+ public void fireSelectionChanged() {
+
+ if (hasSelection()) {
+ Node[] nodes = getDocument().getNodes(getSelectionStart(), getSelectionEnd());
+ selection = new StructuredSelection(nodes);
+ } else {
+ selection = new StructuredSelection(getCurrentElement());
+ }
+
+ SelectionChangedEvent e = new SelectionChangedEvent(VexWidget.this, selection);
+ for (int i = 0; i < selectionListeners.size(); i++) {
+ ISelectionChangedListener listener = (ISelectionChangedListener)
+ selectionListeners.get(i);
+ listener.selectionChanged(e);
+ }
+ caretTimer.reset();
+ }
+
+ public Rectangle getViewport() {
+ return new Rectangle(
+ getClientArea().x - originX,
+ getClientArea().y - originY,
+ getClientArea().width,
+ getClientArea().height);
+ }
+
+ public void invokeLater(Runnable runnable) {
+ VexWidget.this.getDisplay().asyncExec(runnable);
+ }
+
+ public void repaint() {
+ if (!VexWidget.this.isDisposed()) {
+ // We can sometimes get a repaint from the VexWidgetImpl's
+ // caret timer thread after the Widget is disposed.
+ VexWidget.this.redraw();
+ }
+ }
+
+ public void repaint(int x, int y, int width, int height) {
+ VexWidget.this.redraw(x + originX, y + originY, width, height, true);
+ }
+
+ public void scrollTo(int left, int top) {
+ ScrollBar vbar = getVerticalBar();
+ if (vbar != null) {
+ vbar.setSelection(top);
+ }
+ setOrigin(-left, -top);
+ }
+
+ public void setPreferredSize(int width, int height) {
+ ScrollBar vbar = getVerticalBar();
+ if (vbar != null) {
+ vbar.setMaximum(height);
+ }
+ }
+
+ };
+
+ private static abstract class Action extends AbstractVexAction {
+
+ public void run(IVexWidget vexWidget) {
+ try {
+ this.runEx(vexWidget);
+ } catch (Exception ex) {
+ ex.printStackTrace();
+ }
+ }
+
+ public abstract void runEx(IVexWidget w) throws Exception;
+ }
+
+ private KeyListener keyListener = new KeyListener() {
+
+ public void keyPressed(KeyEvent e) {
+ //System.out.println("Key pressed, keyCode is " + e.keyCode + ", keyChar is " + ((int) e.character) + ", stateMask is " + e.stateMask);
+ KeyStroke keyStroke = new KeyStroke(e);
+ Map map = getKeyMap();
+ if (map.containsKey(keyStroke)) {
+ Object action = map.get(keyStroke);
+ if (action instanceof IVexAction) {
+ ((IVexAction) action).run(VexWidget.this);
+ } else {
+ try {
+ ((Action) map.get(keyStroke)).runEx(VexWidget.this);
+ } catch (Exception ex) {
+ ex.printStackTrace();
+ }
+ }
+ } else if (!Character.isISOControl(e.character)) {
+ try {
+ insertChar(e.character);
+ } catch (DocumentValidationException e1) {
+ // TODO Auto-generated catch block
+ e1.printStackTrace();
+ }
+ }
+ }
+
+ public void keyReleased(KeyEvent e) {
+ }
+ };
+
+ private MouseListener mouseListener = new MouseListener() {
+ public void mouseDoubleClick(MouseEvent e) {
+ if (e.button == 1) {
+ selectWord();
+ }
+ }
+ public void mouseDown(MouseEvent e) {
+ if (e.button == 1) {
+ int offset = viewToModel(e.x - originX, e.y - originY);
+ boolean select = (e.stateMask == SWT.SHIFT);
+ moveTo(offset, select);
+ }
+ }
+ public void mouseUp(MouseEvent e) {
+ }
+ };
+
+ private MouseMoveListener mouseMoveListener = new MouseMoveListener() {
+ public void mouseMove(MouseEvent e) {
+ if ((e.stateMask & SWT.BUTTON1) > 0) {
+ int offset = viewToModel(e.x - originX, e.y - originY);
+ moveTo(offset, true);
+ }
+ }
+ };
+
+ private PaintListener painter = new PaintListener() {
+ public void paintControl(PaintEvent e) {
+
+ SwtGraphics g = new SwtGraphics(e.gc);
+ g.setOrigin(originX, originY);
+
+ Color bgColor = impl.getBackgroundColor();
+ if (bgColor == null) {
+ bgColor = new Color(255, 255, 255);
+ }
+
+ ColorResource color = g.createColor(bgColor);
+ ColorResource oldColor = g.setColor(color);
+ Rectangle r = g.getClipBounds();
+ g.fillRect(r.getX(), r.getY(), r.getWidth(), r.getHeight());
+ g.setColor(oldColor);
+ color.dispose();
+
+ impl.paint(g, 0, 0);
+ }
+ };
+
+ private SelectionListener selectionListener = new SelectionListener() {
+ public void widgetSelected(SelectionEvent e) {
+ ScrollBar vbar = getVerticalBar();
+ if (vbar != null) {
+ int y = - vbar.getSelection();
+ setOrigin(0, y);
+ }
+ }
+ public void widgetDefaultSelected(SelectionEvent e) {
+ }
+ };
+
+ private class InsertElementAction extends org.eclipse.jface.action.Action {
+ public InsertElementAction(String name) {
+ this.name = name;
+ this.setText(name);
+ }
+ public void run() {
+ try {
+ insertElement(new Element(name));
+ } catch (DocumentValidationException e) {
+ }
+ }
+ private String name;
+ }
+
+ private class MorphElementAction extends org.eclipse.jface.action.Action {
+ public MorphElementAction(String elementName) {
+ this.elementName = elementName;
+ this.setText(elementName);
+ }
+ public void run() {
+ try {
+ morph(new Element(elementName));
+ } catch (DocumentValidationException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+ }
+ private String elementName;
+ }
+
+
+ //------------------------------------------------------ Methods
+
+ private static void addKey(char character, int keyCode, int stateMask, Action action) {
+ keyMap.put(new KeyStroke(character, keyCode, stateMask), action);
+ }
+
+ private static void addKey(char character, int keyCode, int stateMask, IVexAction action) {
+ keyMap.put(new KeyStroke(character, keyCode, stateMask), action);
+ }
+
+
+ private static void buildKeyMap() {
+ addKey(CHAR_NONE, SWT.ARROW_DOWN, SWT.NONE, new Action() {
+ public void runEx(IVexWidget w) { w.moveToNextLine(false); } });
+ addKey(CHAR_NONE, SWT.ARROW_DOWN, SWT.SHIFT, new Action() {
+ public void runEx(IVexWidget w) { w.moveToNextLine(true); } });
+
+ addKey(CHAR_NONE, SWT.ARROW_LEFT, SWT.NONE, new Action() {
+ public void runEx(IVexWidget w) { w.moveBy(-1); } });
+ addKey(CHAR_NONE, SWT.ARROW_LEFT, SWT.SHIFT, new Action() {
+ public void runEx(IVexWidget w) { w.moveBy(-1, true); } });
+ addKey(CHAR_NONE, SWT.ARROW_LEFT, SWT.CONTROL, new Action() {
+ public void runEx(IVexWidget w) { w.moveToPreviousWord(false); } });
+ addKey(CHAR_NONE, SWT.ARROW_LEFT, SWT.SHIFT | SWT.CONTROL, new Action() {
+ public void runEx(IVexWidget w) { w.moveToPreviousWord(true); } });
+
+ addKey(CHAR_NONE, SWT.ARROW_RIGHT, SWT.NONE, new Action() {
+ public void runEx(IVexWidget w) { w.moveBy(+1); } });
+ addKey(CHAR_NONE, SWT.ARROW_RIGHT, SWT.SHIFT, new Action() {
+ public void runEx(IVexWidget w) { w.moveBy(+1, true); } });
+ addKey(CHAR_NONE, SWT.ARROW_RIGHT, SWT.CONTROL, new Action() {
+ public void runEx(IVexWidget w) { w.moveToNextWord(false); } });
+ addKey(CHAR_NONE, SWT.ARROW_RIGHT, SWT.SHIFT | SWT.CONTROL, new Action() {
+ public void runEx(IVexWidget w) { w.moveToNextWord(true); } });
+
+ addKey(CHAR_NONE, SWT.ARROW_UP, SWT.NONE, new Action() {
+ public void runEx(IVexWidget w) { w.moveToPreviousLine(false); } });
+ addKey(CHAR_NONE, SWT.ARROW_UP, SWT.SHIFT, new Action() {
+ public void runEx(IVexWidget w) { w.moveToPreviousLine(true); } });
+
+ addKey(SWT.BS, SWT.BS, SWT.NONE, new Action() {
+ public void runEx(IVexWidget w) throws Exception { w.deletePreviousChar(); } });
+ addKey(SWT.DEL, SWT.DEL, SWT.NONE, new Action() {
+ public void runEx(IVexWidget w) throws Exception { w.deleteNextChar(); } });
+
+ addKey(SWT.TAB, SWT.TAB, SWT.NONE, new NextTableCellAction());
+ addKey(SWT.TAB, SWT.TAB, SWT.SHIFT, new PreviousTableCellAction());
+
+ addKey(CHAR_NONE, SWT.END, SWT.NONE, new Action() {
+ public void runEx(IVexWidget w) { w.moveToLineEnd(false); } });
+ addKey(CHAR_NONE, SWT.END, SWT.SHIFT, new Action() {
+ public void runEx(IVexWidget w) { w.moveToLineEnd(true); } });
+ addKey(CHAR_NONE, SWT.END, SWT.CONTROL, new Action() {
+ public void runEx(IVexWidget w) { w.moveTo(w.getDocument().getLength() - 1); } });
+ addKey(CHAR_NONE, SWT.END, SWT.SHIFT | SWT.CONTROL, new Action() {
+ public void runEx(IVexWidget w) { w.moveTo(w.getDocument().getLength() - 1, true); } });
+
+ addKey(CHAR_NONE, SWT.HOME, SWT.NONE, new Action() {
+ public void runEx(IVexWidget w) { w.moveToLineStart(false); } });
+ addKey(CHAR_NONE, SWT.HOME, SWT.SHIFT, new Action() {
+ public void runEx(IVexWidget w) { w.moveToLineStart(true); } });
+ addKey(CHAR_NONE, SWT.HOME, SWT.CONTROL, new Action() {
+ public void runEx(IVexWidget w) { w.moveTo(1); } });
+ addKey(CHAR_NONE, SWT.HOME, SWT.SHIFT | SWT.CONTROL, new Action() {
+ public void runEx(IVexWidget w) { w.moveTo(1, true); } });
+
+ addKey(CHAR_NONE, SWT.PAGE_DOWN, SWT.NONE, new Action() {
+ public void runEx(IVexWidget w) { w.moveToNextPage(false); } });
+ addKey(CHAR_NONE, SWT.PAGE_DOWN, SWT.SHIFT, new Action() {
+ public void runEx(IVexWidget w) { w.moveToNextPage(true); } });
+
+ addKey(CHAR_NONE, SWT.PAGE_UP, SWT.NONE, new Action() {
+ public void runEx(IVexWidget w) { w.moveToPreviousPage(false); } });
+ addKey(CHAR_NONE, SWT.PAGE_UP, SWT.SHIFT, new Action() {
+ public void runEx(IVexWidget w) { w.moveToPreviousPage(true); } });
+
+ addKey(' ', 0, SWT.CONTROL, new Action() { // Ctrl-Space
+ public void runEx(IVexWidget w) { ((VexWidget)w).showInsertElementPopup(); } });
+ addKey('\r', 0, SWT.CONTROL, new Action() { // Ctrl-M
+ public void runEx(IVexWidget w) { ((VexWidget)w).showMorphElementPopup(); } });
+ addKey((char) 23, 0, SWT.CONTROL, new RemoveElementAction());
+// addKey('\r', '\r', SWT.NONE, new Action() { // Enter key
+// public void runEx(IVexWidget w) throws Exception { w.split(); } });
+ addKey('\r', '\r', SWT.NONE, new SplitAction());
+ addKey('\r', '\r', SWT.SHIFT, new SplitItemAction());
+
+ addKey((char) 4, 100, SWT.CONTROL, new DuplicateSelectionAction()); // Ctrl-D
+ }
+
+ private static Map getKeyMap() {
+ if (keyMap == null) {
+ keyMap = new HashMap();
+ buildKeyMap();
+ }
+ return keyMap;
+ }
+
+ /**
+ * Scrolls to the given position in the widget.
+ * @param x x-coordinate of the position to which to scroll
+ * @param y y-coordinate of the position to which to scroll
+ */
+ private void setOrigin(int x, int y) {
+ int destX = x - originX;
+ int destY = y - originY;
+ org.eclipse.swt.graphics.Rectangle ca = getClientArea();
+ scroll(destX, destY, 0, 0, ca.width, ca.height, false);
+ originX = x;
+ originY = y;
+ }
+
+
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/undo/CannotRedoException.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/undo/CannotRedoException.java
new file mode 100644
index 0000000..8a55a04
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/undo/CannotRedoException.java
@@ -0,0 +1,49 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.undo;
+
+/**
+ * Thrown when an IUndoableEdit cannot be undone.
+ */
+public class CannotRedoException extends RuntimeException {
+
+ /**
+ * Class constructor.
+ */
+ public CannotRedoException() {
+ }
+
+ /**
+ * Class constructor.
+ * @param message Message indicating the reason for the failure.
+ */
+ public CannotRedoException(String message) {
+ super(message);
+ }
+
+ /**
+ * Class constructor.
+ * @param cause Root cause of the failure.
+ */
+ public CannotRedoException(Throwable cause) {
+ super(cause);
+ }
+
+ /**
+ * Class constructor.
+ * @param message Message indicating the reason for the failure.
+ * @param cause Root cause of the failure.
+ */
+ public CannotRedoException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/undo/CannotUndoException.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/undo/CannotUndoException.java
new file mode 100644
index 0000000..2c5cbea
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/undo/CannotUndoException.java
@@ -0,0 +1,49 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.undo;
+
+/**
+ * Thrown when an IUndoableEdit cannot be undone.
+ */
+public class CannotUndoException extends RuntimeException {
+
+ /**
+ * Class constructor.
+ */
+ public CannotUndoException() {
+ }
+
+ /**
+ * Class constructor.
+ * @param message Message indicating the reason for the failure.
+ */
+ public CannotUndoException(String message) {
+ super(message);
+ }
+
+ /**
+ * Class constructor.
+ * @param cause Root cause of the failure.
+ */
+ public CannotUndoException(Throwable cause) {
+ super(cause);
+ }
+
+ /**
+ * Class constructor.
+ * @param message Message indicating the reason for the failure.
+ * @param cause Root cause of the failure.
+ */
+ public CannotUndoException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/undo/CompoundEdit.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/undo/CompoundEdit.java
new file mode 100644
index 0000000..7e7197b
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/undo/CompoundEdit.java
@@ -0,0 +1,63 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.undo;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * An undoable edit that is a composite of others.
+ */
+public class CompoundEdit implements IUndoableEdit {
+
+ /**
+ * Class constructor.
+ */
+ public CompoundEdit() {
+ }
+
+ /**
+ * Adds an edit to the list.
+ * @param edit Edit to be undone/redone as part of the compound group.
+ */
+ public void addEdit(IUndoableEdit edit) {
+ edits.add(edit);
+ }
+
+ public boolean combine(IUndoableEdit edit) {
+ return false;
+ }
+
+ /**
+ * Calls redo() on each contained edit, in the order that they were added.
+ */
+ public void redo() {
+ for (int i = 0; i < this.edits.size(); i++) {
+ IUndoableEdit edit = (IUndoableEdit) this.edits.get(i);
+ edit.redo();
+ }
+ }
+
+ /**
+ * Calls undo() on each contained edit, in reverse order from which they
+ * were added.
+ */
+ public void undo() {
+ for (int i = this.edits.size() - 1; i >= 0; i--) {
+ IUndoableEdit edit = (IUndoableEdit) this.edits.get(i);
+ edit.undo();
+ }
+ }
+
+ //===================================================== PRIVATE
+
+ private List edits = new ArrayList();
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/undo/IUndoableEdit.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/undo/IUndoableEdit.java
new file mode 100644
index 0000000..4edb719
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/undo/IUndoableEdit.java
@@ -0,0 +1,41 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.undo;
+
+/**
+ * Represents a change to a document (an edit) that can be undone and redone.
+ * Typically, the edit source (i.e. the document) will have a flag that is set
+ * by the edit to indicate that the edits being performed are part of an undo
+ * or redo. The document can use this to supress events to any
+ * IUndoableEventListeners during undo/redo.
+ */
+public interface IUndoableEdit {
+
+ /**
+ * Try to combine the given edit event with this one. The common use-case
+ * involves a user typing sequential characters into the document: all
+ * such insertions should be undone in one go.
+ *
+ * @param edit IUndoableEdit to be combined with this one.
+ * @return True if the given edit was successfully combined into this one.
+ */
+ public boolean combine(IUndoableEdit edit);
+
+ /**
+ * Redo the edit.
+ */
+ public void redo() throws CannotRedoException;
+
+ /**
+ * Undo the edit.
+ */
+ public void undo() throws CannotUndoException;
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/widget/CssWhitespacePolicy.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/widget/CssWhitespacePolicy.java
new file mode 100644
index 0000000..56ad19c
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/widget/CssWhitespacePolicy.java
@@ -0,0 +1,44 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.widget;
+
+import org.eclipse.wst.xml.vex.core.internal.css.CSS;
+import org.eclipse.wst.xml.vex.core.internal.css.StyleSheet;
+import org.eclipse.wst.xml.vex.core.internal.dom.Element;
+import org.eclipse.wst.xml.vex.core.internal.dom.IWhitespacePolicy;
+
+
+
+/**
+ * Implementation of WhitespacePolicy using a CSS stylesheet.
+ */
+public class CssWhitespacePolicy implements IWhitespacePolicy {
+
+ /**
+ * Class constructor.
+ * @param styleSheet The stylesheet used for the policy.
+ */
+ public CssWhitespacePolicy(StyleSheet styleSheet) {
+ this.styleSheet = styleSheet;
+ }
+
+ public boolean isBlock(Element element) {
+ return this.styleSheet.getStyles(element).isBlock();
+ }
+
+ public boolean isPre(Element element) {
+ return CSS.PRE.equals(this.styleSheet.getStyles(element).getWhiteSpace());
+ }
+
+ //===================================================== PRIVATE
+
+ private StyleSheet styleSheet;
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/widget/HostComponent.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/widget/HostComponent.java
new file mode 100644
index 0000000..bf85907
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/widget/HostComponent.java
@@ -0,0 +1,70 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.widget;
+
+import org.eclipse.wst.xml.vex.core.internal.core.Graphics;
+import org.eclipse.wst.xml.vex.core.internal.core.Rectangle;
+
+/**
+ * Callback interface through which VexComponentImpl accesses its host
+ * component.
+ */
+public interface HostComponent {
+
+ /**
+ * Creates a Graphics object for the default system display. The returned
+ * object must be disposed after use.
+ */
+ public Graphics createDefaultGraphics();
+
+ /**
+ * If the component is scrollable, return the height of the viewport;
+ * otherwise, return the size of the widget.
+ */
+ public Rectangle getViewport();
+
+ /**
+ * Called when the selection in the widget has changed. This method
+ * should reset the caret timer to the full interval.
+ */
+ public void fireSelectionChanged();
+
+ public void invokeLater(Runnable runnable);
+
+ /**
+ * Flag the entire component for a repaint.
+ */
+ public void repaint();
+
+
+ /**
+ * Flag a rectangular area of the component to be repainted.
+ * @param x X-coordinate of the region to be repainted.
+ * @param y Y-coordinate of the region to be repainted.
+ * @param width Width of the region to be repainted.
+ * @param height Height of the region to be repainted.
+ */
+ public void repaint(int x, int y, int width, int height);
+
+ /**
+ * Move the viewport to a new location
+ * @param left New left-side of the viewport
+ * @param top New top-side of the viewport
+ */
+ public void scrollTo(int left, int top);
+
+ /**
+ * Sets the preferred size of the component.
+ * @param width Preferred width of the component.
+ * @param height Preferred height of the component.
+ */
+ public void setPreferredSize(int width, int height);
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/widget/IBoxFilter.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/widget/IBoxFilter.java
new file mode 100644
index 0000000..07fb04b
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/widget/IBoxFilter.java
@@ -0,0 +1,29 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.widget;
+
+import org.eclipse.wst.xml.vex.core.internal.layout.Box;
+
+/**
+ * Interface implemented by classes that determine whether a Box
+ * matches certain criteria.
+ *
+ * @see IVexWidget#
+ */
+public interface IBoxFilter {
+
+ /**
+ * Returns <code>true</code> if the given box matches the criteria.
+ *
+ * @param box Box to be tested.
+ */
+ public boolean matches(Box box);
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/widget/IVexWidget.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/widget/IVexWidget.java
new file mode 100644
index 0000000..4cb436a
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/widget/IVexWidget.java
@@ -0,0 +1,513 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.widget;
+
+import java.io.IOException;
+import java.net.URL;
+
+import javax.xml.parsers.ParserConfigurationException;
+
+
+import org.eclipse.wst.xml.vex.core.internal.css.StyleSheet;
+import org.eclipse.wst.xml.vex.core.internal.dom.Document;
+import org.eclipse.wst.xml.vex.core.internal.dom.DocumentFragment;
+import org.eclipse.wst.xml.vex.core.internal.dom.DocumentValidationException;
+import org.eclipse.wst.xml.vex.core.internal.dom.Element;
+import org.eclipse.wst.xml.vex.core.internal.layout.Box;
+import org.eclipse.wst.xml.vex.core.internal.layout.BoxFactory;
+import org.eclipse.wst.xml.vex.core.internal.undo.CannotRedoException;
+import org.eclipse.wst.xml.vex.core.internal.undo.CannotUndoException;
+import org.xml.sax.SAXException;
+
+/**
+ * Methods implemented by implementations of the Vex widget on all platforms.
+ * This interface is more important as a place to gather common Javadoc than
+ * as a way to enforce a contract.
+ */
+public interface IVexWidget {
+ /**
+ * Signals the start of a set of operations that should be considered
+ * a single unit for undo/redo purposes.
+ *
+ * <p><b>It is <i>strongly</i> recommended to use the {@link #doWork(IRunnable)} method
+ * instead of manually implementing beginWork/endWork.</b></p>
+ *
+ * <p>Each call to beginWork should
+ * be matched with a call to {@link #endWork(boolean)}. The following pattern can be used
+ * to enforce this rules even in the face of exceptions.</p>
+ *
+ * <pre>
+ * VexComponent c = ...;
+ * boolean success = false;
+ * try {
+ * c.beginWork();
+ * // do multiple inserts/deletes
+ * success = true;
+ * } finally {
+ * c.endWork(success);
+ * }
+ * </pre>
+ *
+ * <p>In the case of nested beginWork/endWork calls, only the outermost
+ * results in an undoable event.</p>
+ *
+ * @see endWork(boolean)
+ */
+ public void beginWork();
+
+ /**
+ * Returns true if the clipboard has content that can be
+ * pasted. Used to enable/disable the paste action of a containing
+ * application.
+ */
+ public boolean canPaste();
+
+ /**
+ * Returns true if the clipboard has plain text content that can be
+ * pasted. Used to enable/disable the "paste text" action of a containing
+ * application.
+ */
+ public boolean canPasteText();
+
+ /**
+ * Returns true if a redo can be performed.
+ */
+ public boolean canRedo();
+
+ /**
+ * Returns true if an undo can be performed.
+ */
+ public boolean canUndo();
+
+ /**
+ * Returns true if the current element can be unwrapped, i.e. replaced with
+ * its content.
+ */
+ public boolean canUnwrap();
+
+ /**
+ * Copy the current selection to the clipboard.
+ */
+ public void copySelection();
+
+ /**
+ * Cuts the current selection to the clipboard.
+ */
+ public void cutSelection();
+
+ /**
+ * Deletes the character to the right of the caret.
+ */
+ public void deleteNextChar() throws DocumentValidationException;
+
+ /**
+ * Deletes the character to the left of the caret.
+ */
+ public void deletePreviousChar() throws DocumentValidationException;
+
+ /**
+ * Delete the current selection. Does nothing if there is no
+ * current selection.
+ */
+ public void deleteSelection();
+
+ /**
+ * Perform the runnable's run method within a beginWork/endWork pair.
+ * All operations in the runnable are treated as a single unit of
+ * work, and can be undone in one operation by the user. Also, if
+ * a later operation fails, all earlier operations are also undone.
+ *
+ * @param runnable Runnable implementing the work to be done.
+ */
+ public void doWork(Runnable runnable);
+
+ /**
+ * Perform the runnable's run method within a beginWork/endWork pair.
+ * All operations in the runnable are treated as a single unit of
+ * work, and can be undone in one operation by the user. Also, if
+ * a later operation fails, all earlier operations are also undone.
+ *
+ * @param savePosition If true, the current caret position is saved
+ * and restored once the operation is complete.
+ * @param runnable Runnable implementing the work to be done.
+ */
+ public void doWork(boolean savePosition, Runnable runnable);
+
+ /**
+ * Signals the end of a set of operations that should be treated as
+ * a single unit for undo/redo purposes.
+ *
+ * @param success If true, an edit is added to the undo stack.
+ * If false, all the changes since the matching beginWork call
+ * are undone.
+ *
+ * @see #beginWork()
+ */
+ public void endWork(boolean success);
+
+ /**
+ * Returns the innermost box containing the current caret offset
+ * that matches the given filter.
+ *
+ * @param filter IBoxFilter that determines which box to return
+ */
+ public Box findInnermostBox(IBoxFilter filter);
+
+ /**
+ * Returns the <code>BoxFactory</code> used for generating boxes in the
+ * layout.
+ */
+ public BoxFactory getBoxFactory();
+
+ /**
+ * Return the offset into the document represented by the caret.
+ */
+ public int getCaretOffset();
+
+ /**
+ * Returns the element at the current caret offset.
+ */
+ public Element getCurrentElement();
+
+ /**
+ * Returns the document associated with this component.
+ */
+ public Document getDocument();
+
+ /**
+ * Returns the width to which the document was layed out.
+ */
+ public int getLayoutWidth();
+
+ /**
+ * Returns the offset at which the selection ends.
+ */
+ public int getSelectionEnd();
+
+ /**
+ * Returns the offset at which the selection starts.
+ */
+ public int getSelectionStart();
+
+ /**
+ * Returns the currently selected document fragment, or null if
+ * there is no current selection.
+ */
+ public DocumentFragment getSelectedFragment();
+
+ /**
+ * Returns the currently selected string, or an empty string if
+ * there is no current selection.
+ */
+ public String getSelectedText();
+
+
+ /**
+ * Returns the style sheet used to format the document while editing.
+ */
+ public StyleSheet getStyleSheet();
+
+ /**
+ * Returns the number of undoable edits that have occurred on this document
+ * since editing has started, not including limitations due to maximum
+ * undo depth.
+ */
+ public int getUndoDepth();
+
+ /**
+ * Returns an array of names of elements that are valid to insert at the
+ * given caret offset and selection
+ */
+ public String[] getValidInsertElements();
+
+ /**
+ * Returns an array of names of elements to which the element at the
+ * current caret location can be morphed.
+ */
+ public String[] getValidMorphElements();
+
+ /**
+ * Returns true if the user currently has some text selected.
+ */
+ public boolean hasSelection();
+
+ /**
+ * Inserts the given character at the current caret position. Any selected
+ * content is deleted. The main difference between this method and
+ * insertText is that this method does not use beginWork/endWork, so
+ * consecutive calls to insertChar are collapsed into a single
+ * IUndoableEdit. This method should normally only be called in response
+ * to a user typing a key.
+ *
+ * @param c Character to insert.
+ */
+ public void insertChar(char c) throws DocumentValidationException;
+
+ /**
+ * Inserts the given document fragment at the current caret position. Any
+ * selected content is deleted.
+ *
+ * @param frag DocumentFragment to insert.
+ */
+ public void insertFragment(DocumentFragment frag)
+ throws DocumentValidationException;
+
+ /**
+ * Inserts the given element at the current caret position. Any
+ * selected content becomes the new contents of the element.
+ *
+ * @param element Element to insert.
+ */
+ public void insertElement(Element element)
+ throws DocumentValidationException;
+
+ /**
+ * Inserts the given text at the current caret position. Any
+ * selected content is first deleted.
+ *
+ * @param text String to insert.
+ */
+ public void insertText(String text) throws DocumentValidationException;
+
+ /**
+ * Returns the value of the debugging flag.
+ */
+ public boolean isDebugging();
+
+ /**
+ * Sets the value of the debugging flag. When debugging, copious information
+ * is dumped to stdout.
+ * @param debugging true if debugging is to be enabled.
+ */
+ public void setDebugging(boolean debugging);
+
+ /**
+ * Replaces the current element with the given element.
+ * The content of the element is preserved.
+ * @param element Element to replace the current element with.
+ * @throws DocumentValidationException if the given element is
+ * not valid at this place in the document, or if the current
+ * element's content is not compatible with the given element.
+ */
+ public void morph(Element element) throws DocumentValidationException;
+
+ /**
+ * Moves the caret a given distance relative to the current caret
+ * offset.
+ * @param distance Amount by which to alter the caret offset.
+ * Positive values increase the caret offset.
+ */
+ public void moveBy(int distance);
+
+ /**
+ * Moves the caret a given distance relative to the current caret
+ * offset.
+ * @param distance Amount by which to alter the caret offset.
+ * Positive values increase the caret offset.
+ * @param select if true, the current selection is extended to
+ * match the new caret offset
+ */
+ public void moveBy(int distance, boolean select);
+
+ /**
+ * Moves the caret to a new offset. The selection is not extended.
+ * This is equivalent to <code>moveTo(offset, false)</code>.
+ * @param int new offset for the caret. The offset must be >= 1 and less
+ * than the document size; if not, it is silently ignored.
+ */
+ public void moveTo(int offset);
+
+ /**
+ * Moves the caret to the new offset, possibly changing the selection.
+ *
+ * @param int new offset for the caret. The offset must be >= 1 and less
+ * than the document size; if not, it is silently ignored.
+ * @param select if true, the current selection is extended to
+ * match the new caret offset.
+ */
+ public void moveTo(int offset, boolean select);
+
+ /**
+ * Move the caret to the end of the current line.
+ * @param select If true, the selection is extended.
+ */
+ public void moveToLineEnd(boolean select);
+
+ /**
+ * Move the caret to the start of the current line.
+ * @param select If true, the selection is extended.
+ */
+ public void moveToLineStart(boolean select);
+
+ /**
+ * Move the caret down to the next line. Attempts to preserve the
+ * same distance from the left edge of the control.
+ * @param select If true, the selection is extended.
+ */
+ public void moveToNextLine(boolean select);
+
+ /**
+ * Move the caret down to the next page. Attempts to preserve the same
+ * distance from the left edge of the control.
+ * @param select If true, the selection is extended.
+ */
+ public void moveToNextPage(boolean select);
+
+ /**
+ * Moves the caret to the end of the current or next word.
+ * @param select If true, the selection is extended.
+ */
+ public void moveToNextWord(boolean select);
+
+ /**
+ * Moves the caret up to the previous line.
+ * @param select If true, the selection is extended
+ */
+ public void moveToPreviousLine(boolean select);
+
+ /**
+ * Moves the caret up to the previous page.
+ * @param select If true, the selection is extended
+ */
+ public void moveToPreviousPage(boolean select);
+
+ /**
+ * Moves the caret to the start of the current or previous word.
+ * @param select If true, the selection is extended.
+ */
+ public void moveToPreviousWord(boolean select);
+
+ /**
+ * Paste the current clipboard contents into the document at the
+ * current caret position.
+ */
+ public void paste() throws DocumentValidationException;
+
+ /**
+ * Paste the current clipboard contents as plain text into the document at the
+ * current caret position.
+ */
+ public void pasteText() throws DocumentValidationException;
+ /**
+ * Redoes the last action on the redo stack.
+ * @throws CannotRedoException if the last action cannot be re-done, or if
+ * there is nothing to redo.
+ */
+ public void redo();
+
+ /**
+ * Removes an attribute from the current element. Attributes removed in this manner
+ * (as opposed to calling Element.setAttribute directly) will be subject to
+ * undo/redo.
+ * @param attributeName Name of the attribute to remove.
+ */
+ public void removeAttribute(String attributeName);
+
+ /**
+ * Execute a Runnable, restoring the caret position to its original position
+ * afterward.
+ *
+ * @param runnable Runnable to be invoked.
+ */
+ public void savePosition(Runnable runnable);
+
+ /**
+ * Selects all content in the document.
+ */
+ public void selectAll();
+
+ /**
+ * Selects the word at the current caret offset.
+ */
+ public void selectWord();
+
+ /**
+ * Sets the value of an attribute in the current element.
+ * Attributes set in this manner
+ * (as opposed to calling Element.setAttribute directly) will be subject to
+ * undo/redo.
+ * @param attributeName Name of the attribute being changed.
+ * @param value New value for the attribute. If null, the attribute is
+ * removed from the element.
+ */
+ public void setAttribute(String attributeName, String value);
+
+ /**
+ * Sets the box factory to be applied to the current document during editing.
+ *
+ * @param boxFactory the new BoxFactory to use
+ */
+ public void setBoxFactory(BoxFactory boxFactory);
+
+ /**
+ * Sets a new document for this control.
+ *
+ * @param document new Document to display
+ * @param styleSheet StyleSheet to use for formatting
+ */
+ public void setDocument(Document document, StyleSheet styleSheet);
+
+
+ /**
+ * Sets a new document for this control.
+ *
+ * @param docURL URL of the document to display.
+ * @param ssURL URL of the stylesheet to use for formatting.
+ */
+ public void setDocument(URL docURL, URL ssURL)
+ throws IOException, ParserConfigurationException, SAXException;
+
+ /**
+ * Sets the width to which the document should be layed out. The actual
+ * resulting width may be different due to overflowing boxes.
+ */
+ public void setLayoutWidth(int width);
+
+ /**
+ * Sets the style sheet to be applied to the current document during editing.
+ * If no resolver has been set, the style sheet will also be used for any
+ * subsequently loaded documents. If a resolver has been set, the style sheet
+ * returned by the resolver will be used for subsequently loaded documents.
+ *
+ * @param styleSheet the new StyleSheet to use
+ */
+ public void setStyleSheet(StyleSheet styleSheet);
+
+ /**
+ * Sets the stylesheet to be used for this widget.
+ *
+ * @param ssUrl URL of the CSS style sheet to use.
+ */
+ public void setStyleSheet(URL ssUrl) throws IOException;
+
+ /**
+ * Split the element at the current caret offset. This is the normal behaviour
+ * when the user presses Enter.
+ */
+ public void split() throws DocumentValidationException;
+
+ /**
+ * Undoes the last action on the undo stack.
+ * @throws CannotUndoException if the last action cannot be undone, or
+ * if there's nothing left to undo.
+ */
+ public void undo();
+
+ /**
+ * Return the offset into the document for the given coordinates.
+ *
+ * @param x the x-coordinate
+ * @param y the y-coordinate
+ */
+ public int viewToModel(int x, int y);
+
+
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/widget/VexWidgetImpl.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/widget/VexWidgetImpl.java
new file mode 100644
index 0000000..c1be685
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/widget/VexWidgetImpl.java
@@ -0,0 +1,1638 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.widget;
+
+import java.io.IOException;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+
+import javax.xml.parsers.ParserConfigurationException;
+
+
+import org.eclipse.wst.xml.vex.core.internal.core.Caret;
+import org.eclipse.wst.xml.vex.core.internal.core.Color;
+import org.eclipse.wst.xml.vex.core.internal.core.Graphics;
+import org.eclipse.wst.xml.vex.core.internal.core.IntRange;
+import org.eclipse.wst.xml.vex.core.internal.core.Rectangle;
+import org.eclipse.wst.xml.vex.core.internal.css.CSS;
+import org.eclipse.wst.xml.vex.core.internal.css.StyleSheet;
+import org.eclipse.wst.xml.vex.core.internal.css.StyleSheetReader;
+import org.eclipse.wst.xml.vex.core.internal.css.Styles;
+import org.eclipse.wst.xml.vex.core.internal.dom.Document;
+import org.eclipse.wst.xml.vex.core.internal.dom.DocumentEvent;
+import org.eclipse.wst.xml.vex.core.internal.dom.DocumentFragment;
+import org.eclipse.wst.xml.vex.core.internal.dom.DocumentListener;
+import org.eclipse.wst.xml.vex.core.internal.dom.DocumentReader;
+import org.eclipse.wst.xml.vex.core.internal.dom.DocumentValidationException;
+import org.eclipse.wst.xml.vex.core.internal.dom.Element;
+import org.eclipse.wst.xml.vex.core.internal.dom.IWhitespacePolicy;
+import org.eclipse.wst.xml.vex.core.internal.dom.IWhitespacePolicyFactory;
+import org.eclipse.wst.xml.vex.core.internal.dom.Position;
+import org.eclipse.wst.xml.vex.core.internal.dom.Validator;
+import org.eclipse.wst.xml.vex.core.internal.layout.BlockBox;
+import org.eclipse.wst.xml.vex.core.internal.layout.Box;
+import org.eclipse.wst.xml.vex.core.internal.layout.BoxFactory;
+import org.eclipse.wst.xml.vex.core.internal.layout.CssBoxFactory;
+import org.eclipse.wst.xml.vex.core.internal.layout.LayoutContext;
+import org.eclipse.wst.xml.vex.core.internal.layout.RootBox;
+import org.eclipse.wst.xml.vex.core.internal.undo.CannotRedoException;
+import org.eclipse.wst.xml.vex.core.internal.undo.CannotUndoException;
+import org.eclipse.wst.xml.vex.core.internal.undo.CompoundEdit;
+import org.eclipse.wst.xml.vex.core.internal.undo.IUndoableEdit;
+import org.xml.sax.SAXException;
+
+/**
+ * A Swing component that allows the display and edit of an XML
+ * document with an associated CSS stylesheet.
+ */
+public class VexWidgetImpl implements IVexWidget {
+
+ /**
+ * Number of pixel rows above and below the caret that are rendered
+ * at a time.
+ */
+ private static final int LAYOUT_WINDOW = 5000;
+
+ /**
+ * Because the height of each BlockElementBox is initially estimated,
+ * we sometimes have to try several times before the band being laid
+ * out is properly positioned about the offset. When the position
+ * of the offset changes by less than this amount between subsequent
+ * layout calls, the layout is considered stable.
+ */
+ private static final int LAYOUT_TOLERANCE = 500;
+
+ /**
+ * Minimum layout width, in pixels. Prevents performance problems
+ * when width is very small.
+ */
+ private static final int MIN_LAYOUT_WIDTH = 200;
+
+ private boolean debugging;
+
+ private HostComponent hostComponent;
+ private int layoutWidth = 500; // something reasonable to handle a document
+ // being set before the widget is sized
+
+ private Document document;
+ private StyleSheet styleSheet;
+ private BoxFactory boxFactory = new CssBoxFactory();
+
+ private RootBox rootBox;
+
+ /** Stacks of UndoableEditEvents; items added and removed from end of list */
+ private LinkedList undoList = new LinkedList();
+ private LinkedList redoList = new LinkedList();
+ private static final int MAX_UNDO_STACK_SIZE = 100;
+ private int undoDepth;
+
+ /** Support for beginWork/endWork */
+ private int beginWorkCount = 0;
+ private int beginWorkCaretOffset;
+ private CompoundEdit compoundEdit;
+
+ private int caretOffset;
+ private int mark;
+ private int selectionStart;
+ private int selectionEnd;
+
+ private Element currentElement;
+
+ private boolean caretVisible = true;
+ private Caret caret;
+ private Color caretColor;
+
+ // x offset to be maintained when moving vertically
+ private int magicX = -1;
+
+ private boolean antiAliased = false;
+
+
+ //======================================================= LISTENERS
+
+ private DocumentListener documentListener = new DocumentListener() {
+
+ public void attributeChanged(DocumentEvent e) {
+ invalidateElementBox(e.getParentElement());
+
+ // flush cached styles, since they might depend attribute values
+ // via conditional selectors
+ getStyleSheet().flushStyles(e.getParentElement());
+
+ if (beginWorkCount == 0) {
+ VexWidgetImpl.this.relayout();
+ }
+
+ addEdit(e.getUndoableEdit(), getCaretOffset());
+ hostComponent.fireSelectionChanged();
+ }
+
+ public void beforeContentDeleted(DocumentEvent e) {
+ }
+
+ public void beforeContentInserted(DocumentEvent e) {
+ }
+
+ public void contentDeleted(DocumentEvent e) {
+ invalidateElementBox(e.getParentElement());
+
+ if (beginWorkCount == 0) {
+ VexWidgetImpl.this.relayout();
+ }
+
+ addEdit(e.getUndoableEdit(), getCaretOffset());
+ }
+
+ public void contentInserted(DocumentEvent e) {
+ invalidateElementBox(e.getParentElement());
+
+ if (beginWorkCount == 0) {
+ VexWidgetImpl.this.relayout();
+ }
+
+ addEdit(e.getUndoableEdit(), getCaretOffset());
+ }
+
+ };
+
+ //======================================================= PUBLIC INTERFACE
+
+ /**
+ * Class constructor.
+ */
+ public VexWidgetImpl(HostComponent hostComponent) {
+ this.hostComponent = hostComponent;
+ }
+
+ public void beginWork() {
+ if (this.beginWorkCount == 0) {
+ this.beginWorkCaretOffset = this.getCaretOffset();
+ this.compoundEdit = new CompoundEdit();
+ }
+ this.beginWorkCount++;
+ }
+
+ /**
+ * Returns true if the given fragment can be inserted at the current
+ * caret position.
+ * @param frag DocumentFragment to be inserted.
+ */
+ public boolean canInsertFragment(DocumentFragment frag) {
+
+ Document doc = this.getDocument();
+ if (doc == null) {
+ return false;
+ }
+
+ Validator validator = doc.getValidator();
+ if (validator == null) {
+ return true;
+ }
+
+ int startOffset = this.getCaretOffset();
+ int endOffset = this.getCaretOffset();
+ if (this.hasSelection()) {
+ startOffset = this.getSelectionStart();
+ endOffset = this.getSelectionEnd();
+ }
+
+ Element parent = this.getDocument().getElementAt(startOffset);
+ String[] seq1 =
+ doc.getNodeNames(parent.getStartOffset() + 1, startOffset);
+ String[] seq2 = frag.getNodeNames();
+ String[] seq3 = doc.getNodeNames(endOffset, parent.getEndOffset());
+
+ return validator.isValidSequence(
+ parent.getName(),
+ seq1,
+ seq2,
+ seq3,
+ true);
+ }
+
+ /**
+ * Returns true if text can be inserted at the current position.
+ */
+ public boolean canInsertText() {
+
+ Document doc = this.getDocument();
+ if (doc == null) {
+ return false;
+ }
+
+ Validator validator = this.document.getValidator();
+ if (validator == null) {
+ return true;
+ }
+
+ int startOffset = this.getCaretOffset();
+ int endOffset = this.getCaretOffset();
+ if (this.hasSelection()) {
+ startOffset = this.getSelectionStart();
+ endOffset = this.getSelectionEnd();
+ }
+
+ Element parent = this.getDocument().getElementAt(startOffset);
+ String[] seq1 =
+ doc.getNodeNames(parent.getStartOffset() + 1, startOffset);
+ String[] seq2 = new String[] { Validator.PCDATA };
+ String[] seq3 = doc.getNodeNames(endOffset, parent.getEndOffset());
+
+ return validator.isValidSequence(
+ parent.getName(),
+ seq1,
+ seq2,
+ seq3,
+ true);
+ }
+
+ public boolean canPaste() {
+ throw new UnsupportedOperationException("Must be implemented in tookit-specific widget.");
+ }
+
+ public boolean canPasteText() {
+ throw new UnsupportedOperationException("Must be implemented in tookit-specific widget.");
+ }
+
+ public boolean canRedo() {
+ return this.redoList.size() > 0;
+ }
+
+ public boolean canUndo() {
+ return this.undoList.size() > 0;
+ }
+
+ public boolean canUnwrap() {
+ Document doc = this.getDocument();
+ if (doc == null) {
+ return false;
+ }
+
+ Validator validator = doc.getValidator();
+ if (validator == null) {
+ return false;
+ }
+
+ Element element = doc.getElementAt(this.getCaretOffset());
+ Element parent = element.getParent();
+ if (parent == null) {
+ // can't unwrap the root
+ return false;
+ }
+
+ String[] seq1 =
+ doc.getNodeNames(
+ parent.getStartOffset() + 1,
+ element.getStartOffset());
+ String[] seq2 =
+ doc.getNodeNames(
+ element.getStartOffset() + 1,
+ element.getEndOffset());
+ String[] seq3 =
+ doc.getNodeNames(element.getEndOffset() + 1, parent.getEndOffset());
+
+ return validator.isValidSequence(
+ parent.getName(),
+ seq1,
+ seq2,
+ seq3,
+ true);
+ }
+
+ public void copySelection() {
+ throw new UnsupportedOperationException("Must be implemented in tookit-specific widget.");
+ }
+
+ public void cutSelection() {
+ throw new UnsupportedOperationException("Must be implemented in tookit-specific widget.");
+ }
+
+ public void deleteNextChar() throws DocumentValidationException {
+ if (this.hasSelection()) {
+ this.deleteSelection();
+ } else {
+ int offset = this.getCaretOffset();
+ Document doc = this.getDocument();
+ int n = doc.getLength() - 1;
+ Element element = doc.getElementAt(offset);
+
+ if (offset == n) {
+ // nop
+ } else if (this.isBetweenMatchingElements(offset)) {
+ this.joinElementsAt(offset);
+ } else if (this.isBetweenMatchingElements(offset + 1)) {
+ this.joinElementsAt(offset + 1);
+ } else if (element.isEmpty()) {
+ // deleting the right sentinel of an empty element
+ // so just delete the whole element an move on
+ this.moveTo(offset - 1, false);
+ this.moveTo(offset + 1, true);
+ this.deleteSelection();
+ } else if (doc.getElementAt(offset + 1).isEmpty()) {
+ // deleting the left sentinel of an empty element
+ // so just delete the whole element an move on
+ this.moveTo(offset + 2, true);
+ this.deleteSelection();
+ } else {
+ if (doc.getCharacterAt(offset) != 0) {
+ this.moveTo(offset, false);
+ this.moveTo(offset + 1, true);
+ this.deleteSelection();
+ }
+ }
+ }
+ }
+
+ public void deletePreviousChar() throws DocumentValidationException {
+
+ if (this.hasSelection()) {
+ this.deleteSelection();
+ } else {
+ int offset = this.getCaretOffset();
+ Document doc = this.getDocument();
+ Element element = doc.getElementAt(offset);
+
+ if (offset == 1) {
+ // nop
+ } else if (this.isBetweenMatchingElements(offset)) {
+ this.joinElementsAt(offset);
+ } else if (this.isBetweenMatchingElements(offset - 1)) {
+ this.joinElementsAt(offset - 1);
+ } else if (element.isEmpty()) {
+ // deleting the left sentinel of an empty element
+ // so just delete the whole element an move on
+ this.moveTo(offset - 1, false);
+ this.moveTo(offset + 1, true);
+ this.deleteSelection();
+ } else if (doc.getElementAt(offset - 1).isEmpty()) {
+ // deleting the right sentinel of an empty element
+ // so just delete the whole element an move on
+ this.moveTo(offset - 2, true);
+ this.deleteSelection();
+ } else {
+ offset--;
+ if (doc.getCharacterAt(offset) != 0) {
+ this.moveTo(offset, false);
+ this.moveTo(offset + 1, true);
+ this.deleteSelection();
+ }
+ }
+ }
+
+ }
+
+ public void deleteSelection() {
+ try {
+ if (this.hasSelection()) {
+ this.document.delete(this.getSelectionStart(), this.getSelectionEnd());
+ this.moveTo(this.getSelectionStart());
+ }
+ } catch (DocumentValidationException ex) {
+ ex.printStackTrace(); // This should never happen, because we
+ // constrain the selection
+ }
+ }
+
+ public void doWork(Runnable runnable) {
+ this.doWork(false, runnable);
+ }
+
+ public void doWork(boolean savePosition, Runnable runnable) {
+ Position position = null;
+
+ if (savePosition) {
+ position = this.getDocument().createPosition(this.getCaretOffset());
+ }
+
+ boolean success = false;
+ try {
+ this.beginWork();
+ runnable.run();
+ success = true;
+ } catch (Exception ex) {
+ ex.printStackTrace();
+ } finally {
+ this.endWork(success);
+ if (position != null) {
+ this.moveTo(position.getOffset());
+ }
+ }
+ }
+
+ public void endWork(boolean success) {
+ this.beginWorkCount--;
+ if (this.beginWorkCount == 0) {
+ //this.compoundEdit.end();
+ if (success) {
+ this.undoList.add(
+ new UndoableAndOffset(
+ this.compoundEdit,
+ this.beginWorkCaretOffset));
+ this.undoDepth++;
+ if (undoList.size() > MAX_UNDO_STACK_SIZE) {
+ undoList.removeFirst();
+ }
+ this.redoList.clear();
+ this.relayout();
+ this.hostComponent.fireSelectionChanged();
+ } else {
+ try {
+ this.compoundEdit.undo();
+ this.moveTo(this.beginWorkCaretOffset);
+ } catch (CannotUndoException e) {
+ // TODO: handle exception
+ }
+ }
+ this.compoundEdit = null;
+ }
+ }
+
+ public Box findInnermostBox(IBoxFilter filter) {
+ return this.findInnermostBox(filter, this.getCaretOffset());
+ }
+
+ /**
+ * Returns the innermost box containing the given offset
+ * that matches the given filter.
+ *
+ * @param filter IBoxFilter that determines which box to return
+ * @param offset Document offset around which to search.
+ */
+ private Box findInnermostBox(IBoxFilter filter, int offset) {
+
+ Box box = this.rootBox.getChildren()[0];
+ Box matchingBox = null;
+
+ for (;;) {
+ if (filter.matches(box)) {
+ matchingBox = box;
+ }
+
+ Box original = box;
+ Box[] children = box.getChildren();
+ for (int i = 0; i < children.length; i++) {
+
+ Box child = children[i];
+
+ if (child.hasContent()
+ && offset >= child.getStartOffset()
+ && offset <= child.getEndOffset()) {
+ box = child;
+ break;
+ }
+ }
+
+ if (box == original) {
+ // No child found containing offset,
+ // so just return the latest match.
+ return matchingBox;
+ }
+ }
+
+ }
+
+ /**
+ * Returns the background color for the control, which is the same
+ * as the background color of the root element.
+ */
+ public Color getBackgroundColor() {
+ return this.styleSheet.getStyles(this.document.getRootElement()).getBackgroundColor();
+ }
+
+ public BoxFactory getBoxFactory() {
+ return this.boxFactory;
+ }
+
+ /**
+ * Returns the current caret.
+ */
+ public Caret getCaret() {
+ return this.caret;
+ }
+
+ public int getCaretOffset() {
+ return this.caretOffset;
+ }
+
+ public Element getCurrentElement() {
+ return this.currentElement;
+ }
+
+ public Document getDocument() {
+ return this.document;
+ }
+
+ /**
+ * Returns the natural height of the widget based on the current layout
+ * width.
+ */
+ public int getHeight() {
+ return this.rootBox.getHeight();
+ }
+
+ public String[] getValidInsertElements() {
+
+ Document doc = this.getDocument();
+ if (doc == null) {
+ return new String[0];
+ }
+
+ Validator validator = doc.getValidator();
+ if (validator == null) {
+ return new String[0];
+ }
+
+ int startOffset = this.getCaretOffset();
+ int endOffset = this.getCaretOffset();
+ if (this.hasSelection()) {
+ startOffset = this.getSelectionStart();
+ endOffset = this.getSelectionEnd();
+ }
+
+ Element parent = doc.getElementAt(startOffset);
+ String[] prefix =
+ doc.getNodeNames(parent.getStartOffset() + 1, startOffset);
+ String[] suffix = doc.getNodeNames(endOffset, parent.getEndOffset());
+ List candidates = new ArrayList();
+ candidates.addAll(
+ validator.getValidItems(parent.getName(), prefix, suffix));
+ candidates.remove(Validator.PCDATA);
+
+ // If there's a selection, root out those candidates that can't
+ // contain it.
+ if (this.hasSelection()) {
+ String[] selectedNodes = doc.getNodeNames(startOffset, endOffset);
+ for (Iterator iter = candidates.iterator(); iter.hasNext();) {
+ String candidate = (String) iter.next();
+ if (!validator
+ .isValidSequence(candidate, selectedNodes, true)) {
+ iter.remove();
+ }
+ }
+ }
+
+ Collections.sort(candidates);
+ return (String[]) candidates.toArray(new String[candidates.size()]);
+ }
+
+ /**
+ * Returns the value of the antiAliased flag.
+ */
+ public boolean isAntiAliased() {
+ return this.antiAliased;
+ }
+
+ public boolean isDebugging() {
+ return debugging;
+ }
+
+ public String[] getValidMorphElements() {
+
+ Document doc = this.getDocument();
+ if (doc == null) {
+ return new String[0];
+ }
+
+ Validator validator = doc.getValidator();
+ if (validator == null) {
+ return new String[0];
+ }
+
+ Element element = doc.getElementAt(this.getCaretOffset());
+ Element parent = element.getParent();
+ if (parent == null) {
+ // can't morph the root
+ return new String[0];
+ }
+
+ String[] prefix =
+ doc.getNodeNames(
+ parent.getStartOffset() + 1,
+ element.getStartOffset());
+ String[] suffix =
+ doc.getNodeNames(element.getEndOffset() + 1, parent.getEndOffset());
+
+ List candidates = new ArrayList();
+ candidates.addAll(
+ validator.getValidItems(parent.getName(), prefix, suffix));
+ candidates.remove(Validator.PCDATA);
+
+ // root out those that can't contain the current content
+ String[] content =
+ doc.getNodeNames(
+ element.getStartOffset() + 1,
+ element.getEndOffset());
+ for (Iterator iter = candidates.iterator(); iter.hasNext();) {
+ String candidate = (String) iter.next();
+ if (!validator.isValidSequence(candidate, content, true)) {
+ iter.remove();
+ }
+ }
+
+ Collections.sort(candidates);
+ return (String[]) candidates.toArray(new String[candidates.size()]);
+ }
+
+ public int getSelectionEnd() {
+ return this.selectionEnd;
+ }
+
+ public int getSelectionStart() {
+ return this.selectionStart;
+ }
+
+ public DocumentFragment getSelectedFragment() {
+ if (this.hasSelection()) {
+ return this.document.getFragment(
+ this.getSelectionStart(),
+ this.getSelectionEnd());
+ } else {
+ return null;
+ }
+ }
+
+ public String getSelectedText() {
+ if (this.hasSelection()) {
+ return this.document.getText(
+ this.getSelectionStart(),
+ this.getSelectionEnd());
+ } else {
+ return "";
+ }
+ }
+
+ public StyleSheet getStyleSheet() {
+ return this.styleSheet;
+ }
+
+ public int getUndoDepth() {
+ return this.undoDepth;
+ }
+
+ public int getLayoutWidth() {
+ return this.layoutWidth;
+ }
+
+ public RootBox getRootBox(){
+ return this.rootBox;
+ }
+
+ public boolean hasSelection() {
+ return this.getSelectionStart() != this.getSelectionEnd();
+ }
+
+ public void insertChar(char c) throws DocumentValidationException {
+ if (this.hasSelection()) {
+ this.deleteSelection();
+ }
+ this.document.insertText(this.getCaretOffset(), Character.toString(c));
+ this.moveBy(+1);
+ }
+
+ public void insertFragment(DocumentFragment frag)
+ throws DocumentValidationException {
+
+ if (this.hasSelection()) {
+ this.deleteSelection();
+ }
+
+ this.document.insertFragment(this.getCaretOffset(), frag);
+ this.moveTo(this.getCaretOffset() + frag.getLength());
+ }
+
+ public void insertElement(Element element)
+ throws DocumentValidationException {
+
+ boolean success = false;
+ try {
+ this.beginWork();
+
+ DocumentFragment frag = null;
+ if (this.hasSelection()) {
+ frag = this.getSelectedFragment();
+ this.deleteSelection();
+ }
+
+ this.document.insertElement(this.getCaretOffset(), element);
+ this.moveTo(this.getCaretOffset() + 1);
+ if (frag != null) {
+ this.insertFragment(frag);
+ }
+ this.scrollCaretVisible();
+ success = true;
+ } finally {
+ this.endWork(success);
+ }
+ }
+
+ public void insertText(String text) throws DocumentValidationException {
+
+ if (this.hasSelection()) {
+ this.deleteSelection();
+ }
+
+ boolean success = false;
+ try {
+ this.beginWork();
+ int i = 0;
+ for (;;) {
+ int j = text.indexOf('\n', i);
+ if (j == -1) {
+ break;
+ }
+ this.document.insertText(this.getCaretOffset(), text.substring(i, j));
+ this.moveTo(this.getCaretOffset() + (j - i));
+ this.split();
+ i = j + 1;
+ }
+
+ if (i < text.length()) {
+ this.document.insertText(this.getCaretOffset(), text.substring(i));
+ this.moveTo(this.getCaretOffset() + (text.length() - i));
+ }
+ success = true;
+ } finally {
+ this.endWork(success);
+ }
+ }
+
+ public void morph(Element element) throws DocumentValidationException {
+
+ Document doc = this.getDocument();
+ int offset = this.getCaretOffset();
+ Element currentElement = doc.getElementAt(offset);
+
+ if (currentElement == doc.getRootElement()) {
+ throw new DocumentValidationException("Cannot morph the root element.");
+ }
+
+ boolean success = false;
+ try {
+ this.beginWork();
+ this.moveTo(currentElement.getStartOffset() + 1, false);
+ this.moveTo(currentElement.getEndOffset(), true);
+ DocumentFragment frag = this.getSelectedFragment();
+ this.deleteSelection();
+ this.moveBy(-1, false);
+ this.moveBy(2, true);
+ this.deleteSelection();
+ this.insertElement(element);
+ if (frag != null) {
+ this.insertFragment(frag);
+ }
+ this.moveTo(offset, false);
+ success = true;
+ } finally {
+ this.endWork(success);
+ }
+
+ }
+
+ public void moveBy(int distance) {
+ this.moveTo(this.getCaretOffset() + distance, false);
+ }
+
+ public void moveBy(int distance, boolean select) {
+ this.moveTo(this.getCaretOffset() + distance, select);
+ }
+
+ public void moveTo(int offset) {
+ this.moveTo(offset, false);
+ }
+
+ public void moveTo(int offset, boolean select) {
+
+ if (offset >= 1 && offset <= this.document.getLength() - 1) {
+
+ // repaint the selection area, if any
+ this.repaintCaret();
+ this.repaintRange(this.getSelectionStart(), this.getSelectionEnd());
+
+ Element oldElement = this.currentElement;
+
+ this.caretOffset = offset;
+
+ this.currentElement = this.document.getElementAt(offset);
+
+ if (select) {
+ this.selectionStart = Math.min(this.mark, this.caretOffset);
+ this.selectionEnd = Math.max(this.mark, this.caretOffset);
+
+ // move selectionStart and selectionEnd to make sure we don't
+ // select a partial element
+ Element commonElement =
+ this.document.findCommonElement(this.selectionStart, this.selectionEnd);
+
+ Element element = this.document.getElementAt(this.selectionStart);
+ while (element != commonElement) {
+ this.selectionStart = element.getStartOffset();
+ element = this.document.getElementAt(this.selectionStart);
+ }
+
+ element = this.document.getElementAt(this.selectionEnd);
+ while (element != commonElement) {
+ this.selectionEnd = element.getEndOffset() + 1;
+ element = this.document.getElementAt(this.selectionEnd);
+ }
+
+ } else {
+ this.mark = offset;
+ this.selectionStart = offset;
+ this.selectionEnd = offset;
+ }
+
+ if (this.beginWorkCount == 0) {
+ this.relayout();
+ }
+
+ Graphics g = this.hostComponent.createDefaultGraphics();
+ LayoutContext context = this.createLayoutContext(g);
+ this.caret = this.rootBox.getCaret(context, offset);
+
+ Element element = this.getCurrentElement();
+ if (element != oldElement) {
+ this.caretColor = Color.BLACK;
+ while (element != null) {
+ Color bgColor = this.styleSheet.getStyles(element).getBackgroundColor();
+ if (bgColor != null) {
+ int red = ~bgColor.getRed() & 0xff;
+ int green = ~bgColor.getGreen() & 0xff;
+ int blue = ~bgColor.getBlue() & 0xff;
+ this.caretColor = new Color(red, green, blue);
+ break;
+ }
+ element = element.getParent();
+ }
+ }
+
+ g.dispose();
+
+ this.magicX = -1;
+
+ this.scrollCaretVisible();
+
+ this.hostComponent.fireSelectionChanged();
+
+ this.caretVisible = true;
+
+ this.repaintRange(this.getSelectionStart(), this.getSelectionEnd());
+ }
+ }
+
+ public void moveToLineEnd(boolean select) {
+ this.moveTo(this.rootBox.getLineEndOffset(this.getCaretOffset()), select);
+ }
+
+ public void moveToLineStart(boolean select) {
+ this.moveTo(this.rootBox.getLineStartOffset(this.getCaretOffset()), select);
+ }
+
+ public void moveToNextLine(boolean select) {
+ int x = this.magicX == -1 ? this.caret.getBounds().getX() : this.magicX;
+
+ Graphics g = this.hostComponent.createDefaultGraphics();
+ int offset = this.rootBox.getNextLineOffset(this.createLayoutContext(g), this.getCaretOffset(), x);
+ g.dispose();
+
+ this.moveTo(offset, select);
+ this.magicX = x;
+ }
+
+ public void moveToNextPage(boolean select) {
+ int x = this.magicX == -1 ? this.caret.getBounds().getX() : this.magicX;
+ int y = this.caret.getY() +
+ Math.round(this.hostComponent.getViewport().getHeight() * 0.9f);
+ this.moveTo(this.viewToModel(x, y), select);
+ this.magicX = x;
+ }
+
+ public void moveToNextWord(boolean select) {
+ Document doc = this.getDocument();
+ int n = doc.getLength() - 1;
+ int offset = this.getCaretOffset();
+ while (offset < n
+ && !Character.isLetterOrDigit(doc.getCharacterAt(offset))) {
+ offset++;
+ }
+
+ while (offset < n
+ && Character.isLetterOrDigit(doc.getCharacterAt(offset))) {
+ offset++;
+ }
+
+ this.moveTo(offset, select);
+ }
+
+ public void moveToPreviousLine(boolean select) {
+ int x = this.magicX == -1 ? this.caret.getBounds().getX() : this.magicX;
+
+ Graphics g = this.hostComponent.createDefaultGraphics();
+ int offset = this.rootBox.getPreviousLineOffset(this.createLayoutContext(g), this.getCaretOffset(), x);
+ g.dispose();
+
+ this.moveTo(offset, select);
+ this.magicX = x;
+ }
+
+ public void moveToPreviousPage(boolean select) {
+ int x = this.magicX == -1 ? this.caret.getBounds().getX() : this.magicX;
+ int y = this.caret.getY() -
+ Math.round(this.hostComponent.getViewport().getHeight() * 0.9f);
+ this.moveTo(this.viewToModel(x, y), select);
+ this.magicX = x;
+ }
+
+ public void moveToPreviousWord(boolean select) {
+ Document doc = this.getDocument();
+ int offset = this.getCaretOffset();
+ while (offset > 1
+ && !Character.isLetterOrDigit(doc.getCharacterAt(offset - 1))) {
+ offset--;
+ }
+
+ while (offset > 1
+ && Character.isLetterOrDigit(doc.getCharacterAt(offset - 1))) {
+ offset--;
+ }
+
+ this.moveTo(offset, select);
+ }
+
+ /**
+ * Paints the contents of the widget in the given Graphics at the given
+ * point.
+ * @param g Graphics in which to draw the widget contents
+ * @param x x-coordinate at which to draw the widget
+ * @param y y-coordinate at which to draw the widget
+ */
+ public void paint(Graphics g, int x, int y) {
+
+ if (this.rootBox == null) {
+ return;
+ }
+
+ LayoutContext context = this.createLayoutContext(g);
+
+ // Since we may be scrolling to sections of the document that have
+ // yet to be layed out, lay out any exposed area.
+ //
+ // TODO: this will probably be inaccurate, since we should really
+ // iterate the layout, but we don't have an offset around which
+ // to iterate...what to do, what to do....
+ Rectangle rect = g.getClipBounds();
+ int oldHeight = this.rootBox.getHeight();
+ this.rootBox.layout(context, rect.getY(), rect.getY() + rect.getHeight());
+ if (this.rootBox.getHeight() != oldHeight) {
+ this.hostComponent.setPreferredSize(this.rootBox.getWidth(), this.rootBox.getHeight());
+ }
+
+ this.rootBox.paint(context, 0, 0);
+ if (this.caretVisible) {
+ this.caret.draw(g, this.caretColor);
+ }
+
+ // Debug hash marks
+ /*
+ ColorResource grey = g.createColor(new Color(160, 160, 160));
+ ColorResource oldColor = g.setColor(grey);
+ for (int y2 = rect.getY() - rect.getY() % 50; y2 < rect.getY() + rect.getHeight(); y2 += 50) {
+ g.drawLine(x, y + y2, x+10, y + y2);
+ g.drawString(Integer.toString(y2), x + 15, y + y2 - 10);
+ }
+ g.setColor(oldColor);
+ grey.dispose();
+ */
+ }
+
+ public void paste() throws DocumentValidationException {
+ throw new UnsupportedOperationException("Must be implemented in tookit-specific widget.");
+ }
+
+ public void pasteText() throws DocumentValidationException {
+ throw new UnsupportedOperationException("Must be implemented in tookit-specific widget.");
+ }
+
+ public void redo() {
+ if (redoList.size() == 0) {
+ throw new CannotRedoException();
+ }
+ UndoableAndOffset event = (UndoableAndOffset) redoList.removeLast();
+ this.moveTo(event.caretOffset, false);
+ event.edit.redo();
+ this.undoList.add(event);
+ undoDepth++;
+ }
+
+ public void removeAttribute(String attributeName) {
+ try {
+ Element element = this.getCurrentElement();
+ if (element.getAttribute(attributeName) != null) {
+ element.removeAttribute(attributeName);
+ }
+ } catch (DocumentValidationException ex) {
+ ex.printStackTrace(); // TODO: when can this happen?
+ }
+ }
+
+ public void savePosition(Runnable runnable) {
+ Position pos = this.getDocument().createPosition(this.getCaretOffset());
+ try {
+ runnable.run();
+ } finally {
+ this.moveTo(pos.getOffset());
+ }
+ }
+
+ public void selectAll() {
+ this.moveTo(1);
+ this.moveTo(this.getDocument().getLength() - 1, true);
+ }
+
+ public void selectWord() {
+ Document doc = this.getDocument();
+ int startOffset = this.getCaretOffset();
+ int endOffset = this.getCaretOffset();
+ while (startOffset > 1
+ && Character.isLetterOrDigit(doc.getCharacterAt(startOffset - 1))) {
+ startOffset--;
+ }
+ int n = doc.getLength() - 1;
+ while (endOffset < n
+ && Character.isLetterOrDigit(doc.getCharacterAt(endOffset))) {
+ endOffset++;
+ }
+
+ if (startOffset < endOffset) {
+ this.moveTo(startOffset, false);
+ this.moveTo(endOffset, true);
+ }
+ }
+
+ /**
+ * Sets the value of the antiAliased flag.
+ *
+ * @param antiAliased if true, text is rendered using antialiasing.
+ */
+ public void setAntiAliased(boolean antiAliased) {
+ this.antiAliased = antiAliased;
+ }
+
+ public void setAttribute(String attributeName, String value) {
+ try {
+ Element element = this.getCurrentElement();
+ if (value == null) {
+ this.removeAttribute(attributeName);
+ } else if (!value.equals(element.getAttribute(attributeName))) {
+ element.setAttribute(attributeName, value);
+ }
+ } catch (DocumentValidationException ex) {
+ ex.printStackTrace(); // TODO: mebbe throw the exception instead
+ }
+ }
+
+ public void setBoxFactory(BoxFactory boxFactory) {
+ this.boxFactory = boxFactory;
+ if (this.document != null) {
+ this.relayout();
+ }
+ }
+
+ public void setDebugging(boolean debugging) {
+ this.debugging = debugging;
+ }
+
+ public void setDocument(Document document, StyleSheet styleSheet) {
+
+ if (this.document != null) {
+ this.document.removeDocumentListener(this.documentListener);
+ }
+
+ this.document = document;
+ this.styleSheet = styleSheet;
+
+ this.undoList = new LinkedList();
+ this.undoDepth = 0;
+ this.redoList = new LinkedList();
+ this.beginWorkCount = 0;
+ this.compoundEdit = null;
+
+ this.createRootBox();
+
+ this.moveTo(1);
+ this.document.addDocumentListener(this.documentListener);
+ }
+
+ public void setDocument(URL docUrl, URL ssURL)
+ throws IOException, ParserConfigurationException, SAXException {
+
+ StyleSheetReader ssReader = new StyleSheetReader();
+ final StyleSheet ss = ssReader.read(ssURL);
+
+ DocumentReader reader = new DocumentReader();
+
+ reader.setWhitespacePolicyFactory(new IWhitespacePolicyFactory() {
+ public IWhitespacePolicy getPolicy(String publicId) {
+ return new CssWhitespacePolicy(ss);
+ }
+ });
+
+ Document doc = reader.read(docUrl);
+ this.setDocument(doc, ss);
+ }
+
+ /**
+ * Called by the host component when it gains or loses focus.
+ * @param focus true if the host component has focus
+ */
+ public void setFocus(boolean focus) {
+ this.caretVisible = true;
+ this.repaintCaret();
+ }
+
+ public void setLayoutWidth(int width) {
+ width = Math.max(width, MIN_LAYOUT_WIDTH);
+ if (this.getDocument() != null && width != this.getLayoutWidth()) {
+ // this.layoutWidth is set by relayoutAll
+ this.relayoutAll(width, this.styleSheet);
+ } else {
+ // maybe doc is null. Let's store layoutWidth so it's right
+ // when we set a doc
+ this.layoutWidth = width;
+ }
+ }
+
+ public void setStyleSheet(StyleSheet styleSheet) {
+ if (this.getDocument() != null) {
+ this.relayoutAll(this.layoutWidth, styleSheet);
+ }
+ }
+
+ public void setStyleSheet(URL ssUrl) throws IOException {
+ StyleSheetReader reader = new StyleSheetReader();
+ StyleSheet ss = reader.read(ssUrl);
+ this.setStyleSheet(ss);
+ }
+
+ public void split() throws DocumentValidationException {
+
+ long start = System.currentTimeMillis();
+
+ Document doc = this.getDocument();
+ Element element = doc.getElementAt(this.getCaretOffset());
+ Styles styles = this.getStyleSheet().getStyles(element);
+ while (!styles.isBlock()) {
+ element = element.getParent();
+ styles = this.getStyleSheet().getStyles(element);
+ }
+
+ boolean success = false;
+ try {
+ this.beginWork();
+ if (styles.getWhiteSpace().equals(CSS.PRE)) {
+ // can't call this.insertText() or we'll get an infinite loop
+ int offset = this.getCaretOffset();
+ doc.insertText(offset, "\n");
+ this.moveTo(offset + 1);
+ } else {
+ DocumentFragment frag = null;
+ int offset = this.getCaretOffset();
+ boolean atEnd = (offset == element.getEndOffset());
+ if (!atEnd) {
+ this.moveTo(element.getEndOffset(), true);
+ frag = this.getSelectedFragment();
+ this.deleteSelection();
+ }
+
+ // either way, we are now at the end offset for the element
+ // let's move just outside
+ this.moveTo(this.getCaretOffset() + 1);
+
+ this.insertElement(new Element(element.getName()));
+ // TODO: clone attributes
+
+ if (!atEnd) {
+ offset = this.getCaretOffset();
+ this.insertFragment(frag);
+ this.moveTo(offset, false);
+ }
+ }
+ success = true;
+ } finally {
+ this.endWork(success);
+ }
+
+ if (this.isDebugging()) {
+ long end = System.currentTimeMillis();
+ System.out.println("split() took " + (end - start) + "ms");
+ }
+ }
+
+ /**
+ * Toggles the caret to produce a flashing caret effect. This method should
+ * be called from the GUI event thread at regular intervals.
+ */
+ public void toggleCaret() {
+ this.caretVisible = !this.caretVisible;
+ this.repaintCaret();
+ }
+
+ public void undo() {
+ if (undoList.size() == 0) {
+ throw new CannotUndoException();
+ }
+ UndoableAndOffset event = (UndoableAndOffset) undoList.removeLast();
+ this.undoDepth--;
+ event.edit.undo();
+ this.moveTo(event.caretOffset, false);
+ this.redoList.add(event);
+ }
+
+ public int viewToModel(int x, int y) {
+ Graphics g = this.hostComponent.createDefaultGraphics();
+ LayoutContext context = this.createLayoutContext(g);
+ int offset = this.rootBox.viewToModel(context, x, y);
+ g.dispose();
+ return offset;
+ }
+
+ //================================================== PRIVATE
+
+ /**
+ * Captures an UndoableAction and the offset at which
+ * it occurred.
+ */
+ private class UndoableAndOffset {
+ public IUndoableEdit edit;
+ public int caretOffset;
+ public UndoableAndOffset(IUndoableEdit edit, int caretOffset) {
+ this.edit = edit;
+ this.caretOffset = caretOffset;
+ }
+ }
+
+ /**
+ * Processes the given edit, adding it to the undo stack.
+ * @param edit The edit to process.
+ * @param caretOffset Offset of the caret before the edit occurred.
+ * If the edit is undone, the caret is returned to this offset.
+ */
+ private void addEdit(IUndoableEdit edit, int caretOffset) {
+
+ if (edit == null) {
+ return;
+ }
+
+ if (this.compoundEdit != null) {
+ this.compoundEdit.addEdit(edit);
+ } else {
+ if (this.undoList.size() > 0 &&
+ ((UndoableAndOffset) this.undoList.getLast()).edit.combine(edit)) {
+ return;
+ } else {
+ this.undoList.add(new UndoableAndOffset(edit, caretOffset));
+ this.undoDepth++;
+ if (undoList.size() > MAX_UNDO_STACK_SIZE) {
+ undoList.removeFirst();
+ }
+ this.redoList.clear();
+ }
+ }
+ }
+
+ /**
+ * Creates a layout context given a particular graphics context.
+ * @param g The graphics context to use for the layout context.
+ * @return the new layout context
+ */
+ private LayoutContext createLayoutContext(Graphics g) {
+ LayoutContext context = new LayoutContext();
+ context.setBoxFactory(this.getBoxFactory());
+ context.setDocument(this.getDocument());
+ context.setGraphics(g);
+ context.setStyleSheet(this.getStyleSheet());
+
+ if (this.hasSelection()) {
+ context.setSelectionStart(this.getSelectionStart());
+ context.setSelectionEnd(this.getSelectionEnd());
+ } else {
+ context.setSelectionStart(this.getCaretOffset());
+ context.setSelectionEnd(this.getCaretOffset());
+ }
+
+ return context;
+ }
+
+ private void createRootBox() {
+ Graphics g = this.hostComponent.createDefaultGraphics();
+ LayoutContext context = this.createLayoutContext(g);
+ this.rootBox = new RootBox(context, this.document.getRootElement(), this.getLayoutWidth());
+ g.dispose();
+ }
+
+ /**
+ * Invalidates the box tree due to document changes. The lowest box that completely
+ * encloses the changed element is invalidated.
+ *
+ * @param element Element for which to search.
+ */
+ private void invalidateElementBox(final Element element) {
+
+ BlockBox elementBox = (BlockBox) this.findInnermostBox(new IBoxFilter() {
+ public boolean matches(Box box) {
+ return box instanceof BlockBox
+ && box.getElement() != null
+ && box.getStartOffset() <= element.getStartOffset() + 1
+ && box.getEndOffset() >= element.getEndOffset();
+ }
+ });
+
+ if (elementBox != null) {
+ elementBox.invalidate(true);
+ }
+ }
+
+ /**
+ * Returns true if the given offset represents the boundary between
+ * two different elements with the same name and parent. This is used
+ * to determine if the elements can be joined via joinElementsAt.
+ *
+ * @param int offset The offset to check.
+ */
+ private boolean isBetweenMatchingElements(int offset) {
+ if (offset <= 1 || offset >= this.getDocument().getLength() - 1) {
+ return false;
+ }
+ Element e1 = this.getDocument().getElementAt(offset - 1);
+ Element e2 = this.getDocument().getElementAt(offset + 1);
+ return e1 != e2
+ && e1.getParent() == e2.getParent()
+ && e1.getName().equals(e2.getName());
+ }
+
+ /**
+ * Calls layout() on the rootBox until the y-coordinate of a caret at
+ * the given offset converges, i.e. is less than LAYOUT_TOLERANCE pixels
+ * from the last call.
+ * @param offset Offset around which we should lay out boxes.
+ */
+ private void iterateLayout(int offset) {
+
+ int repaintStart = Integer.MAX_VALUE;
+ int repaintEnd = 0;
+ Graphics g = this.hostComponent.createDefaultGraphics();
+ LayoutContext context = this.createLayoutContext(g);
+ int layoutY = this.rootBox.getCaret(context, offset).getY();
+
+ while (true) {
+
+ int oldLayoutY = layoutY;
+ IntRange repaintRange = this.rootBox.layout(context, layoutY - LAYOUT_WINDOW/2, layoutY + LAYOUT_WINDOW/2);
+ if (repaintRange != null) {
+ repaintStart = Math.min(repaintStart, repaintRange.getStart());
+ repaintEnd = Math.max(repaintEnd, repaintRange.getEnd());
+ }
+
+ layoutY = this.rootBox.getCaret(context, offset).getY();
+ if (Math.abs(layoutY - oldLayoutY) < LAYOUT_TOLERANCE) {
+ break;
+ }
+ }
+ g.dispose();
+
+ if (repaintStart < repaintEnd) {
+ Rectangle viewport = this.hostComponent.getViewport();
+ if (repaintStart < viewport.getY() + viewport.getHeight() &&
+ repaintEnd > viewport.getY()) {
+ int start = Math.max(repaintStart, viewport.getY());
+ int end = Math.min(repaintEnd, viewport.getY() + viewport.getHeight());
+ this.hostComponent.repaint(viewport.getX(), start, viewport.getWidth(), end - start);
+ }
+ }
+ }
+
+ /**
+ * Joins the elements at the given offset. Only works if
+ * isBetweenMatchingElements returns true for the same offset.
+ * Afterwards, the caret is left at the point where the join occurred.
+ *
+ * @param offset Offset where the two elements meet.
+ */
+ private void joinElementsAt(int offset)
+ throws DocumentValidationException {
+
+ if (!isBetweenMatchingElements(offset)) {
+ throw new DocumentValidationException(
+ "Cannot join elements at offset " + offset);
+ }
+
+ boolean success = false;
+ try {
+ this.beginWork();
+ this.moveTo(offset + 1);
+ Element element = this.getCurrentElement();
+ boolean moveContent = !element.isEmpty();
+ DocumentFragment frag = null;
+ if (moveContent) {
+ this.moveTo(element.getEndOffset(), true);
+ frag = this.getSelectedFragment();
+ this.deleteSelection();
+ }
+ this.moveBy(-1);
+ this.moveBy(2, true);
+ this.deleteSelection();
+ this.moveBy(-1);
+ if (moveContent) {
+ int savedOffset = this.getCaretOffset();
+ this.insertFragment(frag);
+ this.moveTo(savedOffset, false);
+ }
+ success = true;
+ } finally {
+ this.endWork(success);
+ }
+ }
+
+ /**
+ * Lay out the area around the caret.
+ */
+ private void relayout() {
+
+ long start = System.currentTimeMillis();
+
+ int oldHeight = this.rootBox.getHeight();
+
+ this.iterateLayout(this.getCaretOffset());
+
+ if (this.rootBox.getHeight() != oldHeight) {
+ this.hostComponent.setPreferredSize(this.rootBox.getWidth(), this.rootBox.getHeight());
+ }
+
+ Graphics g = this.hostComponent.createDefaultGraphics();
+ LayoutContext context = this.createLayoutContext(g);
+ this.caret = this.rootBox.getCaret(context, this.getCaretOffset());
+ g.dispose();
+
+ if (this.isDebugging()) {
+ long end = System.currentTimeMillis();
+ System.out.println("VexWidget layout took " + (end - start) + "ms");
+ }
+ }
+
+ /**
+ * Re-layout the entire widget, due to either a layout width change or a
+ * stylesheet range. This method does the actual setting of the width
+ * and stylesheet, since it needs to know where the caret is <i>before</i>
+ * the change, so that it can do a reasonable job of restoring the
+ * position of the viewport after the change.
+ *
+ * @param newWidth New width for the widget.
+ * @param newStyleSheet New stylesheet for the widget.
+ */
+ private void relayoutAll(int newWidth, StyleSheet newStyleSheet) {
+
+ Graphics g = this.hostComponent.createDefaultGraphics();
+ LayoutContext context = this.createLayoutContext(g);
+
+ Rectangle viewport = this.hostComponent.getViewport();
+
+ // true if the caret is within the viewport
+ //
+ // TODO: incorrect if caret near the bottom and the viewport is shrinking
+ // To fix, we probably need to save the viewport height, just like
+ // we now store viewport width (as layout width).
+ boolean caretVisible = viewport.intersects(this.caret.getBounds());
+
+ // distance from the top of the viewport to the top of the caret
+ // use this if the caret is visible in the viewport
+ int relCaretY = 0;
+
+ // offset around which we are laying out
+ // this is also where we put the top of the viewport if the caret
+ // isn't visible
+ int offset;
+
+ if (caretVisible) {
+ relCaretY = this.caret.getY() - viewport.getY();
+ offset = this.getCaretOffset();
+ } else {
+ offset = this.rootBox.viewToModel(context, 0, viewport.getY());
+ }
+
+ this.layoutWidth = newWidth;
+ this.styleSheet = newStyleSheet;
+
+ // Re-create the context, since it holds the old stylesheet
+ context = this.createLayoutContext(g);
+
+ this.createRootBox();
+
+ this.iterateLayout(offset);
+
+ this.hostComponent.setPreferredSize(this.rootBox.getWidth(), this.rootBox.getHeight());
+
+ this.caret = this.rootBox.getCaret(context, this.getCaretOffset());
+
+ if (caretVisible) {
+ int viewportY = this.caret.getY() - Math.min(relCaretY, viewport.getHeight());
+ viewportY = Math.min(this.rootBox.getHeight() - viewport.getHeight(), viewportY);
+ viewportY = Math.max(0, viewportY); // this must appear after the above line, since
+ // that line might set viewportY negative
+ this.hostComponent.scrollTo(viewport.getX(), viewportY);
+ this.scrollCaretVisible();
+ } else {
+ int viewportY = this.rootBox.getCaret(context, offset).getY();
+ this.hostComponent.scrollTo(viewport.getX(), viewportY);
+ }
+
+ this.hostComponent.repaint();
+
+ g.dispose();
+
+ }
+
+
+ /**
+ * Repaints the area of the caret.
+ */
+ private void repaintCaret() {
+ if (this.caret != null) {
+ // caret may be null when document is first set
+ Rectangle bounds = this.caret.getBounds();
+ this.hostComponent.repaint(bounds.getX(), bounds.getY(), bounds.getWidth(), bounds.getHeight());
+ }
+ }
+
+
+ /**
+ * Repaints area of the control corresponding to a range of
+ * offsets in the document.
+ *
+ * @param startOffset Starting offset of the range.
+ * @param endOffset Ending offset of the range.
+ */
+ private void repaintRange(int startOffset, int endOffset) {
+
+ Graphics g = this.hostComponent.createDefaultGraphics();
+
+ LayoutContext context = this.createLayoutContext(g);
+
+ Rectangle startBounds = this.rootBox.getCaret(context, startOffset).getBounds();
+ int top1 = startBounds.getY();
+ int bottom1 = top1 + startBounds.getHeight();
+
+ Rectangle endBounds = this.rootBox.getCaret(context, endOffset).getBounds();
+ int top2 = endBounds.getY();
+ int bottom2 = top2 + endBounds.getHeight();
+
+ int top = Math.min(top1, top2);
+ int bottom = Math.max(bottom1, bottom2);
+ if (top == bottom) {
+ // Account for zero-height horizontal carets
+ this.hostComponent.repaint(0, top - 1, this.getLayoutWidth(), bottom - top + 1);
+ } else {
+ this.hostComponent.repaint(0, top, this.getLayoutWidth(), bottom - top);
+ }
+
+ g.dispose();
+ }
+
+ private void scrollCaretVisible() {
+
+ Rectangle caretBounds = this.caret.getBounds();
+ Rectangle viewport = this.hostComponent.getViewport();
+
+ int x = viewport.getX();
+ int y = 0;
+ int offset = getCaretOffset();
+ if (offset == 1) {
+ y = 0;
+ } else if (offset == getDocument().getLength() - 1) {
+ if (this.rootBox.getHeight() < viewport.getHeight()) {
+ y = 0;
+ } else {
+ y = this.rootBox.getHeight() - viewport.getHeight();
+ }
+ } else if (caretBounds.getY() < viewport.getY()) {
+ y = caretBounds.getY();
+ } else if (caretBounds.getY() + caretBounds.getHeight() > viewport.getY() + viewport.getHeight()) {
+ y = caretBounds.getY() + caretBounds.getHeight() - viewport.getHeight();
+ } else {
+ // no scrolling required
+ return;
+ }
+ this.hostComponent.scrollTo(x, y);
+ }
+
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/.classpath b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/.classpath
new file mode 100644
index 0000000..065ac06
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/.classpath
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+ <classpathentry kind="src" path="src"/>
+ <classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>
+ <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
+ <classpathentry kind="output" path="bin"/>
+</classpath>
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/.cvsignore b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/.cvsignore
new file mode 100644
index 0000000..81a1741
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/.cvsignore
@@ -0,0 +1,3 @@
+bin
+net.sf.vex.editor_1.0.0.jar
+vex-editor.jar
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/.options b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/.options
new file mode 100644
index 0000000..cbfc754
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/.options
@@ -0,0 +1,3 @@
+net.sf.vex.editor/debug=false
+net.sf.vex.editor/debug/config=false
+net.sf.vex.editor/debug/layout=false
\ No newline at end of file
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/.project b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/.project
new file mode 100644
index 0000000..6cf47f5
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/.project
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+ <name>org.eclipse.wst.xml.vex.ui</name>
+ <comment></comment>
+ <projects>
+ </projects>
+ <buildSpec>
+ <buildCommand>
+ <name>org.eclipse.jdt.core.javabuilder</name>
+ <arguments>
+ </arguments>
+ </buildCommand>
+ <buildCommand>
+ <name>org.eclipse.pde.ManifestBuilder</name>
+ <arguments>
+ </arguments>
+ </buildCommand>
+ <buildCommand>
+ <name>org.eclipse.pde.SchemaBuilder</name>
+ <arguments>
+ </arguments>
+ </buildCommand>
+ </buildSpec>
+ <natures>
+ <nature>org.eclipse.pde.PluginNature</nature>
+ <nature>org.eclipse.jdt.core.javanature</nature>
+ </natures>
+</projectDescription>
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/.settings/org.eclipse.jdt.core.prefs b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/.settings/org.eclipse.jdt.core.prefs
new file mode 100644
index 0000000..ca94a57
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/.settings/org.eclipse.jdt.core.prefs
@@ -0,0 +1,12 @@
+#Sun Jul 15 01:10:57 CEST 2007
+eclipse.preferences.version=1
+org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=disabled
+org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.2
+org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve
+org.eclipse.jdt.core.compiler.compliance=1.4
+org.eclipse.jdt.core.compiler.debug.lineNumber=generate
+org.eclipse.jdt.core.compiler.debug.localVariable=generate
+org.eclipse.jdt.core.compiler.debug.sourceFile=generate
+org.eclipse.jdt.core.compiler.problem.assertIdentifier=warning
+org.eclipse.jdt.core.compiler.problem.enumIdentifier=warning
+org.eclipse.jdt.core.compiler.source=1.3
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/.settings/org.eclipse.jdt.ui.prefs b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/.settings/org.eclipse.jdt.ui.prefs
new file mode 100644
index 0000000..8c2a508
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/.settings/org.eclipse.jdt.ui.prefs
@@ -0,0 +1,3 @@
+#Sun Jul 15 01:23:59 CEST 2007
+eclipse.preferences.version=1
+internal.default.compliance=default
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/.template b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/.template
new file mode 100644
index 0000000..d65e0f4
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/.template
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<form>
+ <p/><p><b>Tips on working with this plug-in project</b></p><li>For the view of the new plug-in at a glance, go to the <img href="pageImage"/><a href="OverviewPage">Overview</a>.</li><li>You can test the contributions of this plug-in by launching another instance of the workbench. On the <b>Run</b> menu, click <b>Run As</b> and choose <img href="runTimeWorkbenchImage"/><a href="action.run">Run-time Workbench</a> from the available choices.</li><li>You can add more functionality to this plug-in by adding extensions using the <a href="action.newExtension">New Extension Wizard</a>.</li><li>The plug-in project contains Java code that you can debug. Place breakpoints in Java classes. On the <b>Run</b> menu, select <b>Debug As</b> and choose <img href="runTimeWorkbenchImage"/><a href="action.debug">Run-time Workbench</a> from the available choices.</li>
+</form>
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/META-INF/MANIFEST.MF b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/META-INF/MANIFEST.MF
new file mode 100644
index 0000000..6a53f77
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/META-INF/MANIFEST.MF
@@ -0,0 +1,24 @@
+Manifest-Version: 1.0
+Bundle-ManifestVersion: 2
+Bundle-Name: %plugin.name
+Bundle-Version: 0.5.0.qualifier
+Bundle-Activator: org.eclipse.wst.xml.vex.ui.internal.VexPlugin
+Bundle-Vendor: %Bundle-Vendor.0
+Bundle-SymbolicName: org.eclipse.wst.xml.vex.ui;singleton:=true
+Require-Bundle: org.eclipse.core.runtime,
+ org.eclipse.ui,
+ org.eclipse.core.expressions,
+ org.eclipse.ui.views,
+ org.eclipse.core.resources,
+ org.eclipse.ui.ide,
+ org.eclipse.ui.editors,
+ org.junit,
+ org.eclipse.wst.xml.vex.core;bundle-version="0.5.0"
+Export-Package: org.eclipse.wst.xml.vex.ui.internal;x-internal:=true,
+ org.eclipse.wst.xml.vex.ui.internal.action;x-internal:=true,
+ org.eclipse.wst.xml.vex.ui.internal.config;x-internal:=true,
+ org.eclipse.wst.xml.vex.ui.internal.editor;x-internal:=true
+Bundle-Localization: plugin
+Eclipse-LazyStart: true
+
+
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/build.properties b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/build.properties
new file mode 100644
index 0000000..c1d4e83
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/build.properties
@@ -0,0 +1,16 @@
+bin.includes = plugin.xml,\
+ vex-editor.jar,\
+ *.gif,\
+ lgpl.txt,\
+ .,\
+ plugin_fr.properties,\
+ plugin.properties,\
+ icons/,\
+ META-INF/,\
+ schema/,\
+ bin/
+source.vex-editor.jar = src/
+output.vex-editor.jar = bin/
+source.. = src/
+jars.compile.order = .,\
+ vex-editor.jar
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/design.xml b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/design.xml
new file mode 100644
index 0000000..44958df
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/design.xml
@@ -0,0 +1,163 @@
+<?xml version='1.0'?>
+<!DOCTYPE article PUBLIC "-//OASIS//DTD DocBook XML V4.3//EN" "http://www.oasis-open.org/docbook/xml/4.3b2/docbookx.dtd">
+<article>
+
+ <articleinfo>
+
+ <title>Vex Editor Design</title>
+
+ <author><firstname>John</firstname><surname>Krasnay</surname></author>
+
+ <revhistory>
+
+ <revision><revnumber>0.1</revnumber><date>2005-01-06</date><authorinitials>jk</authorinitials><revdescription><para>Initial
+ version.</para></revdescription></revision>
+
+ </revhistory>
+
+ </articleinfo>
+
+ <section>
+
+ <title>Introduction</title>
+
+ <para>This document describes the internal design of the Vex XML
+ Editor. The components described here describe the higher-level UI
+ implemented on top of the Eclipse Platform. Internals of the
+ VexWidget are described in the design document for the <literal>
+ vex-toolkit</literal> module.</para>
+
+ </section>
+
+ <section>
+
+ <title>Configuration Items</title>
+
+ <para>Vex is an extensible platform for editing XML-based content.
+ Because Vex is based on the Eclipse Platform, it leverages the
+ powerful Eclipse extension point mechanism, and extensions to Vex
+ are indeed implemented as Eclipse extensions. See
+ <xref linkend="extensionpoints"/> for a list of the supported
+ extension points. Typically, Vex plugins consist of declarative
+ information such as DTD files, CSS files, and data in the <filename>
+ plugin.xml</filename> file, and can usually be implemented with
+ little or no Java code. Extensions implementing Vex features such
+ as new document types and styles are hereafter referred to as
+ "configuration items".</para>
+
+ <para>One problem with Eclipse extensions, however, is that that
+ they must be installed prior to running the executable. Within the
+ Eclipse Plug-in Development Environment, plugins are tested by
+ running another instance of the workbench, the Runtime Workbench.
+ While this makes sense for Java-based plugins, it is awkward
+ overkill for the declarative-style plugins used to extend
+ Vex.</para>
+
+ <para>To solve this problem, we have implemented (as of v1.2) the
+ concept of Vex Plugin Projects. A Vex Plugin Project is simply a
+ project with the nature <literal>
+ net.sf.vex.editor.pluginNature</literal> and configured with the
+ Vex Plugin Project Builder. When asked to build a Vex Plugin
+ Project, the builder scans the file <filename>
+ vex-plugin.xml</filename> looking for extensions implementing Vex
+ configuration items. Any related resources, such as a document
+ type's DTD, are parsed. If all is well, each configuration item is
+ registered with the VexPlugin and is available for immediate use.
+ Most Vex configuration items (those that do not involve Java code)
+ can therefore be created, tested, and modified from within Vex
+ itself, without deferring to a runtime workbench.</para>
+
+ <sidebar>
+
+ <title>Why vex-plugin.xml?</title>
+
+ <para>In the Eclipse SDK, the resource name
+ <filename>plugin.xml</filename> is mapped to the Eclipse Plug-in
+ Manifest Editor. In the future, we would like to provide a
+ special editor for Vex configuration items. By using a different
+ filename, we can establish this binding without disturbing the
+ default binding for <filename>plugin.xml</filename>.</para>
+
+ </sidebar>
+
+ <section>
+
+ <title>Data Model</title>
+
+ <para>Each Eclipse plug-in that defines Vex configuration items,
+ and each Vex plugin project, is associated with an instance of
+ <classname>net.sf.vex.editor.config.VexConfiguration</classname>,
+ which contains all configuration items defined by that plugin or
+ project. Each Vex configuration has a unique identifier, which
+ for Eclipse plugins is the plugin identifier. The Vex plugin
+ class, <classname>net.sf.vex.editor.VexPlugin</classname>,
+ maintains a registry of <classname>VexConfiguration</classname>
+ objects.</para>
+
+ <para>Configuration items are defined as subclasses of
+ <classname>net.sf.vex.editor.ConfigItem</classname>, and are
+ essentially data objects with properties defining the item, for
+ example, the name and public ID of a document type. Configuration
+ items are usually associated with a resource; the
+ <classname>ConfigItem</classname> class tracks the path of this
+ resource, relative to the defining project/plugin, in the
+ <literal>resourceUri</literal> property.</para>
+
+ </section>
+
+ <section>
+
+ <title>Configuration Item Factories</title>
+
+ <para>Each type of configuration item is associated with a
+ configuration item factory, a class that implements
+ <classname>net.sf.vex.editor.config.IConfigItemFactory</classname>.
+ The factory has the following responsibilities.</para>
+
+ <itemizedlist>
+
+ <listitem>
+
+ <para>To instantiate and initialize a new configuration item
+ given an array of
+ <classname>org.eclipse.core.runtime.IConfigurationElement</classname>
+ objects. These objects represent the contents of an extension
+ in <filename>plugin.xml</filename> or
+ <filename>vex-plugin.xml</filename>.</para>
+
+ </listitem>
+
+ <listitem>
+
+ <para>To parse the resources associated with the
+ configuration item.</para>
+
+ </listitem>
+
+ <listitem>
+
+ <para>To list the file extensions associated with resources
+ associated with that kind of configuration item.</para>
+
+ </listitem>
+
+ </itemizedlist>
+
+ <para>For example, the <classname>DoctypeFactory</classname>
+ produces instances of the <classname>DocumentType</classname>
+ class, parses DTD files, and specifies that it handles re</para>
+
+ <para />
+
+ </section>
+
+ </section>
+
+ <section>
+
+ <title id="extensionpoints">Extension Points</title>
+
+ </section>
+
+</article>
+
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/icons/error_co.gif b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/icons/error_co.gif
new file mode 100644
index 0000000..119dccc
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/icons/error_co.gif
Binary files differ
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/icons/vex16.gif b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/icons/vex16.gif
new file mode 100644
index 0000000..a32605a
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/icons/vex16.gif
Binary files differ
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/icons/vex8.gif b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/icons/vex8.gif
new file mode 100644
index 0000000..96ef558
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/icons/vex8.gif
Binary files differ
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/icons/vex8.xcf b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/icons/vex8.xcf
new file mode 100644
index 0000000..a38000d
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/icons/vex8.xcf
Binary files differ
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/icons/warning_co.gif b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/icons/warning_co.gif
new file mode 100644
index 0000000..ee2dac4
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/icons/warning_co.gif
Binary files differ
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/lib/commons-beanutils-1.6.jar b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/lib/commons-beanutils-1.6.jar
new file mode 100644
index 0000000..795655a
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/lib/commons-beanutils-1.6.jar
Binary files differ
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/lib/commons-collections-2.1.jar b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/lib/commons-collections-2.1.jar
new file mode 100644
index 0000000..f66c6d2
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/lib/commons-collections-2.1.jar
Binary files differ
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/lib/commons-digester-1.4.1.jar b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/lib/commons-digester-1.4.1.jar
new file mode 100644
index 0000000..312be02
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/lib/commons-digester-1.4.1.jar
Binary files differ
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/lib/commons-logging-1.0.3.jar b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/lib/commons-logging-1.0.3.jar
new file mode 100644
index 0000000..b99c937
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/lib/commons-logging-1.0.3.jar
Binary files differ
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/lib/commons-logging-api-1.0.3.jar b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/lib/commons-logging-api-1.0.3.jar
new file mode 100644
index 0000000..209bcdf
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/lib/commons-logging-api-1.0.3.jar
Binary files differ
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/plugin.properties b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/plugin.properties
new file mode 100644
index 0000000..a266962
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/plugin.properties
@@ -0,0 +1,76 @@
+#
+# Language-specific strings in plugin.xml
+#
+
+plugin.name=Vex XML Editor
+
+BuildProblemDecorator.name=Vex Plug-in Build Problem Decorator
+ConfigurationView.name=Vex Configuration
+DebugView.name=Vex Debug
+DoctypePropertyPage.name=Vex Document Type
+DocumentPerspective.name=Document
+NewDocumentWizard.name=Document
+NewDocumentWizard.desc=Create an XML document using the Vex editor.
+NewPluginProjectWizard.name=Vex Plug-in Project
+NewPluginProjectWizard.desc=Create a new Vex Plug-in project.
+NewWizardCategory.name=Vex
+PluginProjectBuilder.name=Vex Plug-in Builder
+PluginProjectDecorator.name=Vex Plug-in Project Decorator
+PluginProjectNature.name=Vex Plugin Project Nature
+StylePropertyPage.name=Vex Style
+VexCommandCategory.name=Vex XML Editor
+VexEditor.name=Vex XML Editor
+VexEditorContext.name=Editing XML Documents
+VexViewCategory.name=Vex
+
+extensionPoint.doctypes=Vex Document Types
+extensionPoint.styles=Vex Styles
+
+documentActions.label=XML Editing Actions
+
+documentMenu.label=&Document
+insertMenu.label=&Insert
+tableMenu.label=&Table
+rowMenu.label=&Row
+columnMenu.label=&Column
+
+#
+# Actions
+#
+# NOTE: THESE MUST BE KEPT IN SYNC WITH THE SAME BLOCK IN
+# src/net/sf/vex/editor/messages.properties
+#
+# Note: when there are multiple labels for an action, the one named
+# Xxx.label is the command listed in the key bindings preference page.
+#
+ChangeElementAction.label=Change Element
+ChangeElementAction.dynamic.label=&Change <{0}> to...
+ChangeElementAction.menu.label=&Change Element...
+DeleteColumnAction.label=Delete Column
+DeleteRowAction.label=Delete Row
+DuplicateSelectionAction.label=&Duplicate Selection
+InsertColumnAfterAction.label=Insert Column After
+InsertColumnBeforeAction.label=Insert Column Before
+InsertElementAction.label=Insert Element
+InsertElementAction.contextmenu.label=Insert Element...
+InsertElementAction.mainmenu.label=&Element...
+InsertRowAboveAction.label=Insert Row Above
+InsertRowBelowAction.label=Insert Row Below
+MoveColumnLeftAction.label=Move Column Left
+MoveColumnRightAction.label=Move Column Right
+MoveRowDownAction.label=Move Row Down
+MoveRowUpAction.label=Move Row Up
+NextTableCellAction.label=Next Table Cell
+PasteTextAction.label=Paste Text
+PreviousTableCellAction.label=Previous Table Cell
+RemoveElementAction.label=Remove Element
+RemoveElementAction.dynamic.label=Remove <{0}>
+RestoreLastSelectionAction.label=Restore Last Selection
+SplitAction.label=Split Block Element
+SplitItemAction.label=Split Item
+
+#
+# End of Actions
+#
+
+Bundle-Vendor.0 = vex.sf.net
\ No newline at end of file
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/plugin.xml b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/plugin.xml
new file mode 100644
index 0000000..96c79f5
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/plugin.xml
@@ -0,0 +1,444 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<?eclipse version="3.2"?>
+<plugin>
+
+ <extension-point id="doctypes" name="%extensionPoint.doctypes" schema="schema/doctype.exsd"/>
+ <extension-point id="styles" name="%extensionPoint.styles" schema="schema/style.exsd"/>
+
+ <extension
+ point="org.eclipse.ui.actionSets">
+ <actionSet id="org.eclipse.wst.xml.vex.ui.documentActions"
+ label="%documentActions.label"
+ visible="true">
+ <menu id="org.eclipse.wst.xml.vex.ui.documentMenu"
+ label="%documentMenu.label"
+ path="additions">
+ <separator name="insert"/>
+ <separator name="modify"/>
+ <separator name="table"/>
+ </menu>
+ <menu id="org.eclipse.wst.xml.vex.ui.insertMenu"
+ label="%insertMenu.label"
+ path="org.eclipse.wst.xml.vex.ui.documentMenu/insert">
+ <separator name="items"/>
+ </menu>
+ <menu id="org.eclipse.wst.xml.vex.ui.columnMenu"
+ label="%columnMenu.label"
+ path="org.eclipse.wst.xml.vex.ui.documentMenu/table">
+ <separator name="items"/>
+ </menu>
+ <menu id="org.eclipse.wst.xml.vex.ui.rowMenu"
+ label="%rowMenu.label"
+ path="org.eclipse.wst.xml.vex.ui.documentMenu/table">
+ <separator name="items"/>
+ </menu>
+
+ <action id="net.sf.vex.action.PasteTextAction"
+ label="%PasteTextAction.label"
+ menubarPath="edit/cut.ext"
+ class="org.eclipse.wst.xml.vex.ui.internal.editor.VexActionDelegate">
+ </action>
+ <action id="net.sf.vex.action.RestoreLastSelectionAction"
+ label="%RestoreLastSelectionAction.label"
+ menubarPath="edit/editEnd"
+ class="org.eclipse.wst.xml.vex.ui.internal.editor.VexActionDelegate">
+ </action>
+
+ <action id="org.eclipse.wst.xml.vex.ui.action.InsertElementAction"
+ label="%InsertElementAction.mainmenu.label"
+ menubarPath="org.eclipse.wst.xml.vex.ui.documentMenu/org.eclipse.wst.xml.vex.ui.insertMenu/items"
+ class="org.eclipse.wst.xml.vex.ui.internal.editor.VexActionDelegate">
+ </action>
+
+ <action id="net.sf.vex.action.DuplicateSelectionAction"
+ label="%DuplicateSelectionAction.label"
+ menubarPath="org.eclipse.wst.xml.vex.ui.documentMenu/modify"
+ class="org.eclipse.wst.xml.vex.ui.internal.editor.VexActionDelegate"
+ definitionId="net.sf.vex.action.DuplicateSelectionAction">
+ </action>
+ <action id="net.sf.vex.action.RemoveElementAction"
+ label="%RemoveElementAction.label"
+ menubarPath="org.eclipse.wst.xml.vex.ui.documentMenu/modify"
+ class="org.eclipse.wst.xml.vex.ui.internal.editor.VexActionDelegate"
+ definitionId="net.sf.vex.action.RemoveElementAction">
+ </action>
+ <action id="org.eclipse.wst.xml.vex.ui.action.ChangeElementAction"
+ label="%ChangeElementAction.menu.label"
+ menubarPath="org.eclipse.wst.xml.vex.ui.documentMenu/modify"
+ class="org.eclipse.wst.xml.vex.ui.internal.editor.VexActionDelegate">
+ </action>
+
+ <action id="net.sf.vex.action.MoveRowDownAction"
+ label="%MoveRowDownAction.label"
+ menubarPath="org.eclipse.wst.xml.vex.ui.documentMenu/org.eclipse.wst.xml.vex.ui.rowMenu/items"
+ class="org.eclipse.wst.xml.vex.ui.internal.editor.VexActionDelegate">
+ </action>
+ <action id="net.sf.vex.action.MoveRowUpAction"
+ label="%MoveRowUpAction.label"
+ menubarPath="org.eclipse.wst.xml.vex.ui.documentMenu/org.eclipse.wst.xml.vex.ui.rowMenu/items"
+ class="org.eclipse.wst.xml.vex.ui.internal.editor.VexActionDelegate">
+ </action>
+ <action id="net.sf.vex.action.DeleteRowAction"
+ label="%DeleteRowAction.label"
+ menubarPath="org.eclipse.wst.xml.vex.ui.documentMenu/org.eclipse.wst.xml.vex.ui.rowMenu/items"
+ class="org.eclipse.wst.xml.vex.ui.internal.editor.VexActionDelegate">
+ </action>
+ <action id="net.sf.vex.action.InsertRowBelowAction"
+ label="%InsertRowBelowAction.label"
+ menubarPath="org.eclipse.wst.xml.vex.ui.documentMenu/org.eclipse.wst.xml.vex.ui.rowMenu/items"
+ class="org.eclipse.wst.xml.vex.ui.internal.editor.VexActionDelegate">
+ </action>
+ <action id="net.sf.vex.action.InsertRowAboveAction"
+ label="%InsertRowAboveAction.label"
+ menubarPath="org.eclipse.wst.xml.vex.ui.documentMenu/org.eclipse.wst.xml.vex.ui.rowMenu/items"
+ class="org.eclipse.wst.xml.vex.ui.internal.editor.VexActionDelegate">
+ </action>
+
+ <action id="net.sf.vex.action.MoveColumnRightAction"
+ label="%MoveColumnRightAction.label"
+ menubarPath="org.eclipse.wst.xml.vex.ui.documentMenu/org.eclipse.wst.xml.vex.ui.columnMenu/items"
+ class="org.eclipse.wst.xml.vex.ui.internal.editor.VexActionDelegate">
+ </action>
+ <action id="net.sf.vex.action.MoveColumnLeftAction"
+ label="%MoveColumnLeftAction.label"
+ menubarPath="org.eclipse.wst.xml.vex.ui.documentMenu/org.eclipse.wst.xml.vex.ui.columnMenu/items"
+ class="org.eclipse.wst.xml.vex.ui.internal.editor.VexActionDelegate">
+ </action>
+ <action id="net.sf.vex.action.DeleteColumnAction"
+ label="%DeleteColumnAction.label"
+ menubarPath="org.eclipse.wst.xml.vex.ui.documentMenu/org.eclipse.wst.xml.vex.ui.columnMenu/items"
+ class="org.eclipse.wst.xml.vex.ui.internal.editor.VexActionDelegate">
+ </action>
+ <action id="net.sf.vex.action.InsertColumnAfterAction"
+ label="%InsertColumnAfterAction.label"
+ menubarPath="org.eclipse.wst.xml.vex.ui.documentMenu/org.eclipse.wst.xml.vex.ui.columnMenu/items"
+ class="org.eclipse.wst.xml.vex.ui.internal.editor.VexActionDelegate">
+ </action>
+ <action id="net.sf.vex.action.InsertColumnBeforeAction"
+ label="%InsertColumnBeforeAction.label"
+ menubarPath="org.eclipse.wst.xml.vex.ui.documentMenu/org.eclipse.wst.xml.vex.ui.columnMenu/items"
+ class="org.eclipse.wst.xml.vex.ui.internal.editor.VexActionDelegate">
+ </action>
+
+ </actionSet>
+ </extension>
+ <extension
+ point="org.eclipse.ui.editors">
+ <editor
+ name="%VexEditor.name"
+ extensions="htm,html,xhtml,xml,pml"
+ icon="vex16.gif"
+ class="org.eclipse.wst.xml.vex.ui.internal.editor.VexEditor"
+ contributorClass="org.eclipse.wst.xml.vex.ui.internal.editor.VexActionBarContributor"
+ id="org.eclipse.wst.xml.vex.ui.VexEditor">
+ </editor>
+ </extension>
+ <extension
+ point="org.eclipse.ui.newWizards">
+ <category
+ name="%NewWizardCategory.name"
+ id="net.sf.vex.NewWizardCategory">
+ </category>
+ <wizard
+ name="%NewDocumentWizard.name"
+ icon="vex16.gif"
+ category="net.sf.vex.NewWizardCategory"
+ class="org.eclipse.wst.xml.vex.ui.internal.editor.NewDocumentWizard"
+ id="org.eclipse.wst.xml.vex.ui.NewDocumentWizard">
+ <description>%NewDocumentWizard.desc</description>
+ </wizard>
+ </extension>
+ <extension
+ point="org.eclipse.ui.perspectives">
+ <perspective
+ name="%DocumentPerspective.name"
+ icon="vex16.gif"
+ class="org.eclipse.wst.xml.vex.ui.internal.editor.DocumentPerspective"
+ id="org.eclipse.wst.xml.vex.ui.DocumentPerspective">
+ </perspective>
+ </extension>
+ <extension
+ point="org.eclipse.ui.contexts">
+ <context
+ name="%VexEditorContext.name"
+ id="org.eclipse.wst.xml.vex.ui.VexEditorContext"
+ parentId="org.eclipse.ui.textEditorScope">
+ </context>
+ </extension>
+ <extension
+ point="org.eclipse.ui.commands">
+ <category
+ name="%VexCommandCategory.name"
+ id="org.eclipse.wst.xml.vex.ui.VexCommandCategory">
+ </category>
+ <command
+ name="%ChangeElementAction.label"
+ categoryId="org.eclipse.wst.xml.vex.ui.VexCommandCategory"
+ id="net.sf.vex.action.ChangeElementAction">
+ </command>
+ <command
+ name="%DeleteColumnAction.label"
+ categoryId="org.eclipse.wst.xml.vex.ui.VexCommandCategory"
+ id="net.sf.vex.action.DeleteColumnAction">
+ </command>
+ <command
+ name="%DeleteRowAction.label"
+ categoryId="org.eclipse.wst.xml.vex.ui.VexCommandCategory"
+ id="net.sf.vex.action.DeleteRowAction">
+ </command>
+ <command
+ name="%DuplicateSelectionAction.label"
+ categoryId="org.eclipse.wst.xml.vex.ui.VexCommandCategory"
+ id="net.sf.vex.action.DuplicateSelectionAction">
+ </command>
+ <command
+ name="%InsertColumnAfterAction.label"
+ categoryId="org.eclipse.wst.xml.vex.ui.VexCommandCategory"
+ id="net.sf.vex.action.InsertColumnAfterAction">
+ </command>
+ <command
+ name="%InsertColumnBeforeAction.label"
+ categoryId="org.eclipse.wst.xml.vex.ui.VexCommandCategory"
+ id="net.sf.vex.action.InsertColumnBeforeAction">
+ </command>
+ <command
+ name="%InsertElementAction.label"
+ categoryId="org.eclipse.wst.xml.vex.ui.VexCommandCategory"
+ id="org.eclipse.wst.xml.vex.ui.action.InsertElementAction">
+ </command>
+ <command
+ name="%InsertRowAboveAction.label"
+ categoryId="org.eclipse.wst.xml.vex.ui.VexCommandCategory"
+ id="net.sf.vex.action.InsertRowAboveAction">
+ </command>
+ <command
+ name="%InsertRowBelowAction.label"
+ categoryId="org.eclipse.wst.xml.vex.ui.VexCommandCategory"
+ id="net.sf.vex.action.InsertRowBelowAction">
+ </command>
+ <command
+ name="%MoveColumnLeftAction.label"
+ categoryId="org.eclipse.wst.xml.vex.ui.VexCommandCategory"
+ id="net.sf.vex.action.MoveColumnLeftAction">
+ </command>
+ <command
+ name="%MoveColumnRightAction.label"
+ categoryId="org.eclipse.wst.xml.vex.ui.VexCommandCategory"
+ id="net.sf.vex.action.MoveColumnRightAction">
+ </command>
+ <command
+ name="%MoveRowDownAction.label"
+ categoryId="org.eclipse.wst.xml.vex.ui.VexCommandCategory"
+ id="net.sf.vex.action.MoveRowDownAction">
+ </command>
+ <command
+ name="%MoveRowUpAction.label"
+ categoryId="org.eclipse.wst.xml.vex.ui.VexCommandCategory"
+ id="net.sf.vex.action.MoveRowUpAction">
+ </command>
+ <command
+ name="%NextTableCellAction.label"
+ categoryId="org.eclipse.wst.xml.vex.ui.VexCommandCategory"
+ id="net.sf.vex.action.NextTableCellAction">
+ </command>
+ <command
+ name="%PasteTextAction.label"
+ categoryId="org.eclipse.wst.xml.vex.ui.VexCommandCategory"
+ id="net.sf.vex.action.PasteTextAction">
+ </command>
+ <command
+ name="%PreviousTableCellAction.label"
+ categoryId="org.eclipse.wst.xml.vex.ui.VexCommandCategory"
+ id="net.sf.vex.action.PreviousTableCellAction">
+ </command>
+ <command
+ name="%RemoveElementAction.label"
+ categoryId="org.eclipse.wst.xml.vex.ui.VexCommandCategory"
+ id="net.sf.vex.action.RemoveElementAction">
+ </command>
+ <command
+ name="%RestoreLastSelectionAction.label"
+ categoryId="org.eclipse.wst.xml.vex.ui.VexCommandCategory"
+ id="net.sf.vex.action.RestoreLastSelectionAction">
+ </command>
+ <command
+ name="%SplitAction.label"
+ categoryId="org.eclipse.wst.xml.vex.ui.VexCommandCategory"
+ id="net.sf.vex.action.SplitAction">
+ </command>
+ <command
+ name="%SplitItemAction.label"
+ categoryId="org.eclipse.wst.xml.vex.ui.VexCommandCategory"
+ id="net.sf.vex.action.SplitItemAction">
+ </command>
+
+ </extension>
+ <extension
+ point="org.eclipse.ui.views">
+ <category
+ name="%VexViewCategory.name"
+ id="net.sf.vex.views.VexViewCategory">
+ </category>
+ <view
+ name="%DebugView.name"
+ icon="vex16.gif"
+ category="net.sf.vex.views.VexViewCategory"
+ class="org.eclipse.wst.xml.vex.ui.internal.editor.DebugView"
+ id="net.sf.vex.views.debug">
+ </view>
+ </extension>
+ <extension
+ point="org.eclipse.ui.views">
+ <view
+ name="%ConfigurationView.name"
+ icon="vex16.gif"
+ category="net.sf.vex.views.VexViewCategory"
+ class="org.eclipse.wst.xml.vex.ui.internal.config.ConfigurationView"
+ id="net.sf.vex.views.configuration">
+ </view>
+ </extension>
+ <extension
+ id="pluginNature"
+ name="%PluginProjectNature.name"
+ point="org.eclipse.core.resources.natures">
+ <runtime>
+ <run
+ class="org.eclipse.wst.xml.vex.ui.internal.config.PluginProjectNature">
+ </run>
+ </runtime>
+ </extension>
+ <extension
+ point="org.eclipse.ui.newWizards">
+ <wizard
+ name="%NewPluginProjectWizard.name"
+ icon="icons/vex16.gif"
+ category="net.sf.vex.NewWizardCategory"
+ class="org.eclipse.wst.xml.vex.ui.internal.config.NewPluginProjectWizard"
+ project="true"
+ id="org.eclipse.wst.xml.vex.ui.config.NewPluginProjectWizard">
+ <description>%NewPluginProjectWizard.desc</description>
+ </wizard>
+ </extension>
+ <extension
+ id="pluginBuilder"
+ name="%PluginProjectBuilder.name"
+ point="org.eclipse.core.resources.builders">
+ <builder>
+ <run
+ class="org.eclipse.wst.xml.vex.ui.internal.config.PluginProjectBuilder">
+ </run>
+ </builder>
+ </extension>
+ <extension
+ point="org.eclipse.ui.decorators">
+ <decorator
+ lightweight="true"
+ label="%PluginProjectDecorator.name"
+ state="true"
+ class="org.eclipse.wst.xml.vex.ui.internal.config.PluginProjectDecorator"
+ id="org.eclipse.wst.xml.vex.ui.config.vexPluginProjectDecorator">
+ <enablement>
+ <objectClass
+ name="org.eclipse.core.resources.IProject">
+ </objectClass>
+ </enablement>
+ </decorator>
+ </extension>
+ <extension
+ point="org.eclipse.ui.decorators">
+ <decorator
+ lightweight="true"
+ label="%BuildProblemDecorator.name"
+ state="true"
+ class="org.eclipse.wst.xml.vex.ui.internal.config.BuildProblemDecorator"
+ id="org.eclipse.wst.xml.vex.ui.config.buildProblemDecorator">
+ <enablement>
+ <objectClass
+ name="org.eclipse.core.resources.IResource">
+ </objectClass>
+ </enablement>
+ </decorator>
+ </extension>
+ <extension
+ point="org.eclipse.ui.propertyPages">
+ <page
+ objectClass="org.eclipse.core.resources.IFile"
+ name="%DoctypePropertyPage.name"
+ class="org.eclipse.wst.xml.vex.ui.internal.config.DoctypePropertyPage"
+ nameFilter="*.dtd"
+ id="org.eclipse.wst.xml.vex.ui.config.DoctypePropertyPage">
+ <filter
+ name="projectNature"
+ value="org.eclipse.wst.xml.vex.ui.pluginNature">
+ </filter>
+ </page>
+ </extension>
+ <extension
+ point="org.eclipse.ui.propertyPages">
+ <page
+ objectClass="org.eclipse.core.resources.IFile"
+ name="%StylePropertyPage.name"
+ class="org.eclipse.wst.xml.vex.ui.internal.config.StylePropertyPage"
+ nameFilter="*.css"
+ id="org.eclipse.wst.xml.vex.ui.config.StylePropertyPage">
+ <filter
+ name="projectNature"
+ value="org.eclipse.wst.xml.vex.ui.pluginNature">
+ </filter>
+ </page>
+ </extension>
+ <extension
+ point="org.eclipse.ui.bindings">
+
+ <key
+ commandId="net.sf.vex.action.ChangeElementAction"
+ contextId="org.eclipse.wst.xml.vex.ui.VexEditorContext"
+ sequence="Ctrl+Alt+Space"
+ schemeId="org.eclipse.ui.defaultAcceleratorConfiguration">
+ </key>
+ <key
+ commandId="net.sf.vex.action.DuplicateSelectionAction"
+ contextId="org.eclipse.wst.xml.vex.ui.VexEditorContext"
+ sequence="Ctrl+D"
+ schemeId="org.eclipse.ui.defaultAcceleratorConfiguration">
+ </key>
+ <key
+ commandId="org.eclipse.wst.xml.vex.ui.action.InsertElementAction"
+ contextId="org.eclipse.wst.xml.vex.ui.VexEditorContext"
+ sequence="Insert"
+ schemeId="org.eclipse.ui.defaultAcceleratorConfiguration">
+ </key>
+ <key
+ commandId="net.sf.vex.action.NextTableCellAction"
+ contextId="org.eclipse.wst.xml.vex.ui.VexEditorContext"
+ sequence="Tab"
+ schemeId="org.eclipse.ui.defaultAcceleratorConfiguration">
+ </key>
+ <key
+ commandId="net.sf.vex.action.PreviousTableCellAction"
+ contextId="org.eclipse.wst.xml.vex.ui.VexEditorContext"
+ sequence="Shift+Tab"
+ schemeId="org.eclipse.ui.defaultAcceleratorConfiguration">
+ </key>
+ <key
+ commandId="net.sf.vex.action.RemoveElementAction"
+ contextId="org.eclipse.wst.xml.vex.ui.VexEditorContext"
+ sequence="Ctrl+U"
+ schemeId="org.eclipse.ui.defaultAcceleratorConfiguration">
+ </key>
+ <key
+ commandId="net.sf.vex.action.SplitItemAction"
+ contextId="org.eclipse.wst.xml.vex.ui.VexEditorContext"
+ sequence="Shift+Enter"
+ schemeId="org.eclipse.ui.defaultAcceleratorConfiguration">
+ </key>
+ <key
+ commandId="net.sf.vex.action.SplitAction"
+ contextId="org.eclipse.wst.xml.vex.ui.VexEditorContext"
+ sequence="Enter"
+ schemeId="org.eclipse.ui.defaultAcceleratorConfiguration">
+ </key>
+
+ </extension>
+
+</plugin>
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/plugin_fr.properties b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/plugin_fr.properties
new file mode 100644
index 0000000..4c0a97f
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/plugin_fr.properties
@@ -0,0 +1,73 @@
+#
+# Language-specific strings in plugin.xml
+#
+
+plugin.name=Editeur XML Vex
+
+BuildProblemDecorator.name=Problème avec le décorateur du Plug-in Vex
+ConfigurationView.name=Configuration de Vex
+DebugView.name=Debogage de Vex
+DoctypePropertyPage.name=Vex Document Type
+DocumentPerspective.name=Document
+NewDocumentWizard.name=Document
+NewDocumentWizard.desc=Créer un document XML avec l'éditeur Vex
+NewPluginProjectWizard.name=Projet du plug-in Vex
+NewPluginProjectWizard.desc=Créer un nouveau projet du Vex Plug-in
+NewWizardCategory.name=Vex
+PluginProjectBuilder.name=Compilateur du plug-in Vex
+PluginProjectDecorator.name=Décorateur du projet Vex
+PluginProjectNature.name=Nature du projet Vex
+StylePropertyPage.name=Style Vex
+VexCommandCategory.name=Editeur XML Vex
+VexEditor.name=Editeur XML Vex
+VexEditorContext.name=Editer les documents XML
+VexViewCategory.name=Vex
+
+extensionPoint.doctypes=Document Types Vex
+extensionPoint.styles=Styles Vex
+
+documentActions.label=Actions d'édition de Vex
+
+documentMenu.label=&Document
+insertMenu.label=&Insérer
+tableMenu.label=&Table
+
+
+#
+# Actions
+#
+# NOTE: THESE MUST BE KEPT IN SYNC WITH THE SAME BLOCK IN
+# src/net/sf/vex/editor/messages.properties
+#
+# Note: when there are multiple labels for an action, the one named
+# Xxx.label is the command listed in the key bindings preference page.
+#
+ChangeElementAction.label=Changer Element
+ChangeElementAction.dynamic.label=&Changer <{0}> en...
+ChangeElementAction.menu.label=&Changer Element...
+DeleteColumnAction.label=Détruire la colonne
+DeleteRowAction.label=Détruire la ligne
+DuplicateSelectionAction.label=&Dupliquer la selection
+InsertColumnAfterAction.label=Insérer une colonne après
+InsertColumnBeforeAction.label=Insérer une colonne avant
+InsertElementAction.label=Insérer un élement
+InsertElementAction.contextmenu.label=Insérer un élément...
+InsertElementAction.mainmenu.label=&Element...
+InsertRowAboveAction.label=Insérer ligne au dessus
+InsertRowBelowAction.label=Insérer ligne en dessous
+MoveColumnLeftAction.label=Déplacer une colonne à gauche
+MoveColumnRightAction.label=Déplacer une colonne à droite
+MoveRowDownAction.label=Déplacer la ligne vers le bas
+MoveRowUpAction.label=Déplacer la ligne vers le haut
+NextTableCellAction.label=Prochaine Cellule de la table
+PasteTextAction.label=Coller le texte
+PreviousTableCellAction.label=Cellule précédente de la table
+RemoveElementAction.label=Supprimer l'élement
+RemoveElementAction.dynamic.label=Supprimer <{0}>
+RestoreLastSelectionAction.label=Restaurer la dernière selection
+SplitAction.label=Couper le bloc
+SplitItemAction.label=Couper l'item
+
+#
+# End of Actions
+#
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/schema/doctype.exsd b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/schema/doctype.exsd
new file mode 100644
index 0000000..6285162
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/schema/doctype.exsd
@@ -0,0 +1,146 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<!-- Schema file written by PDE -->
+<schema targetNamespace="vex-editor">
+<annotation>
+ <appInfo>
+ <meta.schema plugin="vex-editor" id="doctype" name="Vex DocType"/>
+ </appInfo>
+ <documentation>
+ Registers a new document type with Vex.
+ </documentation>
+ </annotation>
+
+ <element name="extension">
+ <complexType>
+ <sequence>
+ <element ref="doctype"/>
+ </sequence>
+ <attribute name="point" type="string" use="required">
+ <annotation>
+ <documentation>
+
+ </documentation>
+ </annotation>
+ </attribute>
+ <attribute name="id" type="string">
+ <annotation>
+ <documentation>
+
+ </documentation>
+ </annotation>
+ </attribute>
+ <attribute name="name" type="string">
+ <annotation>
+ <documentation>
+
+ </documentation>
+ </annotation>
+ </attribute>
+ </complexType>
+ </element>
+
+ <element name="doctype">
+ <annotation>
+ <appInfo>
+ <meta.element labelAttribute="publicId"/>
+ </appInfo>
+ </annotation>
+ <complexType>
+ <sequence>
+ <element ref="rootElement" minOccurs="0" maxOccurs="unbounded"/>
+ </sequence>
+ <attribute name="publicId" type="string">
+ <annotation>
+ <documentation>
+
+ </documentation>
+ </annotation>
+ </attribute>
+ <attribute name="systemId" type="string">
+ <annotation>
+ <documentation>
+
+ </documentation>
+ </annotation>
+ </attribute>
+ <attribute name="dtd" type="string">
+ <annotation>
+ <documentation>
+
+ </documentation>
+ <appInfo>
+ <meta.attribute kind="resource"/>
+ </appInfo>
+ </annotation>
+ </attribute>
+ <attribute name="outlineProvider" type="string">
+ <annotation>
+ <documentation>
+
+ </documentation>
+ <appInfo>
+ <meta.attribute kind="java" basedOn="net.sf.vex.editor.IOutlineProvider"/>
+ </appInfo>
+ </annotation>
+ </attribute>
+ </complexType>
+ </element>
+
+ <element name="rootElement">
+ <complexType>
+ <attribute name="name" type="string">
+ <annotation>
+ <documentation>
+
+ </documentation>
+ </annotation>
+ </attribute>
+ </complexType>
+ </element>
+
+ <annotation>
+ <appInfo>
+ <meta.section type="since"/>
+ </appInfo>
+ <documentation>
+ [Enter the first release in which this extension point appears.]
+ </documentation>
+ </annotation>
+
+ <annotation>
+ <appInfo>
+ <meta.section type="examples"/>
+ </appInfo>
+ <documentation>
+ [Enter extension point usage example here.]
+ </documentation>
+ </annotation>
+
+ <annotation>
+ <appInfo>
+ <meta.section type="apiInfo"/>
+ </appInfo>
+ <documentation>
+ [Enter API information here.]
+ </documentation>
+ </annotation>
+
+ <annotation>
+ <appInfo>
+ <meta.section type="implementation"/>
+ </appInfo>
+ <documentation>
+ [Enter information about supplied implementation of this extension point.]
+ </documentation>
+ </annotation>
+
+ <annotation>
+ <appInfo>
+ <meta.section type="copyright"/>
+ </appInfo>
+ <documentation>
+
+ </documentation>
+ </annotation>
+
+</schema>
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/schema/style.exsd b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/schema/style.exsd
new file mode 100644
index 0000000..26a3933
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/schema/style.exsd
@@ -0,0 +1,117 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<!-- Schema file written by PDE -->
+<schema targetNamespace="vex-editor">
+<annotation>
+ <appInfo>
+ <meta.schema plugin="vex-editor" id="style" name="Vex Style"/>
+ </appInfo>
+ <documentation>
+ Registers a new document type with Vex.
+ </documentation>
+ </annotation>
+
+ <element name="extension">
+ <complexType>
+ <sequence>
+ <element ref="style"/>
+ </sequence>
+ <attribute name="point" type="string" use="required">
+ <annotation>
+ <documentation>
+
+ </documentation>
+ </annotation>
+ </attribute>
+ <attribute name="id" type="string">
+ <annotation>
+ <documentation>
+
+ </documentation>
+ </annotation>
+ </attribute>
+ <attribute name="name" type="string">
+ <annotation>
+ <documentation>
+
+ </documentation>
+ </annotation>
+ </attribute>
+ </complexType>
+ </element>
+
+ <element name="style">
+ <complexType>
+ <sequence>
+ <element ref="doctypeRef" minOccurs="1" maxOccurs="unbounded"/>
+ </sequence>
+ <attribute name="css" type="string">
+ <annotation>
+ <documentation>
+
+ </documentation>
+ <appInfo>
+ <meta.attribute kind="resource"/>
+ </appInfo>
+ </annotation>
+ </attribute>
+ </complexType>
+ </element>
+
+ <element name="doctypeRef">
+ <complexType>
+ <attribute name="publicId" type="string">
+ <annotation>
+ <documentation>
+
+ </documentation>
+ </annotation>
+ </attribute>
+ </complexType>
+ </element>
+
+ <annotation>
+ <appInfo>
+ <meta.section type="since"/>
+ </appInfo>
+ <documentation>
+ [Enter the first release in which this extension point appears.]
+ </documentation>
+ </annotation>
+
+ <annotation>
+ <appInfo>
+ <meta.section type="examples"/>
+ </appInfo>
+ <documentation>
+ [Enter extension point usage example here.]
+ </documentation>
+ </annotation>
+
+ <annotation>
+ <appInfo>
+ <meta.section type="apiInfo"/>
+ </appInfo>
+ <documentation>
+ [Enter API information here.]
+ </documentation>
+ </annotation>
+
+ <annotation>
+ <appInfo>
+ <meta.section type="implementation"/>
+ </appInfo>
+ <documentation>
+ [Enter information about supplied implementation of this extension point.]
+ </documentation>
+ </annotation>
+
+ <annotation>
+ <appInfo>
+ <meta.section type="copyright"/>
+ </appInfo>
+ <documentation>
+
+ </documentation>
+ </annotation>
+
+</schema>
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/VexPlugin.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/VexPlugin.java
new file mode 100644
index 0000000..8851513
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/VexPlugin.java
@@ -0,0 +1,121 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.ui.internal;
+
+
+
+import org.eclipse.core.resources.IWorkspace;
+import org.eclipse.core.resources.ResourcesPlugin;
+import org.eclipse.core.runtime.Status;
+import org.eclipse.swt.widgets.Display;
+import org.eclipse.ui.plugin.AbstractUIPlugin;
+import org.eclipse.wst.xml.vex.core.internal.core.DisplayDevice;
+import org.eclipse.wst.xml.vex.core.internal.swt.SwtDisplayDevice;
+import org.eclipse.wst.xml.vex.ui.internal.config.ConfigLoaderJob;
+import org.osgi.framework.BundleContext;
+
+/**
+ * The main plugin class to be used in the desktop.
+ */
+public class VexPlugin extends AbstractUIPlugin {
+
+ // The plugin's id
+ public static final String ID = "org.eclipse.wst.xml.vex.ui"; //$NON-NLS-1$
+
+ /**
+ * The constructor.
+ */
+ public VexPlugin() {
+
+ instance = this;
+
+ }
+
+ /**
+ * Asserts that this method is called from the display thread. If not,
+ * an IllegalStateException is thrown.
+ */
+ public static void assertIsDisplayThread() {
+ if (Thread.currentThread() != Display.getDefault().getThread()) {
+ throw new IllegalStateException("This method must be called from the display thread."); //$NON-NLS-1$
+ }
+ }
+
+ /**
+ * Returns the shared instance.
+ */
+ public static VexPlugin getInstance() {
+ return instance;
+ }
+
+ /**
+ * Returns the workspace instance.
+ */
+ public static IWorkspace getWorkspace() {
+ return ResourcesPlugin.getWorkspace();
+ }
+
+ /**
+ * Log an error message without an exception.
+ * @param severity One of the IStatus severity levels, e.g. IStatus.ERROR.
+ * @param message Message describing the error.
+ */
+ public void log(int severity, String message) {
+ this.getLog().log(new Status(severity, ID, 0, message, null));
+ }
+
+ /**
+ * Log an error message.
+ * @param severity One of the IStatus severity levels, e.g. IStatus.ERROR.
+ * @param message Message describing the error.
+ * @param exception Exception related to the error, or null of none.
+ */
+ public void log(int severity, String message, Throwable exception) {
+ this.getLog().log(new Status(severity, "org.eclipse.wst.xml.vex.ui", 0, message, exception)); //$NON-NLS-1$
+ }
+
+ /**
+ * Override the plugin startup to intialize the resource tracker.
+ */
+ public void start(BundleContext bundleContext) throws Exception {
+
+ super.start(bundleContext);
+
+ // TODO Remove DisplayDevice.setCurrent from VexPlugin.start
+ // This has been added to the VexWidget ctor, but the problem is that
+ // when loading an editor, we load the document before creating the
+ // widget, and to do that we need to load the stylesheet, and *this*
+ // needs the DisplayDevice to be set properly.
+ //
+ // One solution might be to do a simplified stylesheet load that only
+ // looks at the display property, which is enough to do space
+ // normalization but doesn't need to look at the display device.
+
+ DisplayDevice.setCurrent(new SwtDisplayDevice());
+
+// boolean configDebug = this.isDebugging() &&
+// "true".equalsIgnoreCase(Platform.getDebugOption(ID + "/debug/config"));
+
+ this.initJob.schedule();
+
+ }
+
+ public void stop(BundleContext context) throws Exception {
+ super.stop(context);
+ }
+
+ //========================================================= PRIVATE
+
+ private static VexPlugin instance;
+
+ private ConfigLoaderJob initJob = new ConfigLoaderJob();
+
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/action/ChangeElementAction.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/action/ChangeElementAction.java
new file mode 100644
index 0000000..5f76571
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/action/ChangeElementAction.java
@@ -0,0 +1,32 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.ui.internal.action;
+
+import org.eclipse.wst.xml.vex.core.internal.action.IVexAction;
+import org.eclipse.wst.xml.vex.core.internal.swt.VexWidget;
+import org.eclipse.wst.xml.vex.core.internal.widget.IVexWidget;
+import org.eclipse.wst.xml.vex.ui.internal.editor.MorphAssistant;
+
+
+/**
+ * Displays the Change Element dialog.
+ */
+public class ChangeElementAction implements IVexAction {
+
+ public void run(IVexWidget vexWidget) {
+ new MorphAssistant().show((VexWidget) vexWidget);
+ }
+
+ public boolean isEnabled(IVexWidget vexWidget) {
+ return true;
+ }
+
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/action/InsertElementAction.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/action/InsertElementAction.java
new file mode 100644
index 0000000..c5e7112
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/action/InsertElementAction.java
@@ -0,0 +1,35 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.ui.internal.action;
+
+import org.eclipse.wst.xml.vex.core.internal.action.IVexAction;
+import org.eclipse.wst.xml.vex.core.internal.swt.VexWidget;
+import org.eclipse.wst.xml.vex.core.internal.widget.IVexWidget;
+import org.eclipse.wst.xml.vex.ui.internal.editor.InsertAssistant;
+
+
+/**
+ * @author john
+ *
+ * TODO To change the template for this generated type comment go to
+ * Window - Preferences - Java - Code Style - Code Templates
+ */
+public class InsertElementAction implements IVexAction {
+
+ public void run(IVexWidget vexWidget) {
+ new InsertAssistant().show((VexWidget) vexWidget);
+ }
+
+ public boolean isEnabled(IVexWidget vexWidget) {
+ return true;
+ }
+
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/action/VexActionAdapter.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/action/VexActionAdapter.java
new file mode 100644
index 0000000..9430f22
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/action/VexActionAdapter.java
@@ -0,0 +1,66 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.ui.internal.action;
+
+
+import org.eclipse.jface.action.Action;
+import org.eclipse.wst.xml.vex.core.internal.action.IVexAction;
+import org.eclipse.wst.xml.vex.core.internal.swt.VexWidget;
+import org.eclipse.wst.xml.vex.ui.internal.editor.Messages;
+import org.eclipse.wst.xml.vex.ui.internal.editor.VexEditor;
+
+/**
+ * Adapts a JFace Action to an IVexAction instance. The ID and definition ID of
+ * the resulting action is set to the classname of the action. Localized
+ * strings for the action are pulled from the classname of the given action. For example,
+ * if the action is "net.sf.vex.action.PasteTextAction", the following properties
+ * are retrieved from plugin.xml:
+ *
+ * <dl>
+ * <dt>PasteTextAction.label</dt>
+ * <dd>The action's label.</dd>
+ * <dt>PasteTextAction.tooltip</dt>
+ * <dd>The action's tooltip.</dd>
+ * </dl>
+ */
+public class VexActionAdapter extends Action {
+
+ /**
+ * Class constructor.
+ * @param editor VexEditor to which the action will apply.
+ * @param action IVexAction to be invoked.
+ */
+ public VexActionAdapter(VexEditor editor, IVexAction action) {
+
+ this.editor = editor;
+ this.action = action;
+
+ String id = action.getClass().getName();
+ int i = id.lastIndexOf("."); //$NON-NLS-1$
+ String key = id.substring(i+1);
+
+ this.setId(id);
+ this.setActionDefinitionId(id);
+ this.setText(Messages.getString(key + ".label")); //$NON-NLS-1$
+ this.setToolTipText(Messages.getString(key + ".tooltip")); //$NON-NLS-1$
+ }
+
+
+ public void run() {
+ VexWidget vexWidget = this.editor.getVexWidget();
+ if (vexWidget != null) {
+ this.action.run(vexWidget);
+ }
+ }
+
+ private VexEditor editor;
+ private IVexAction action;
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/config/BuildProblem.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/config/BuildProblem.java
new file mode 100644
index 0000000..9f5b92c
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/config/BuildProblem.java
@@ -0,0 +1,111 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.ui.internal.config;
+
+/**
+ * Represents a problem found when parsing a configuration resource.
+ */
+public class BuildProblem {
+
+ public static final int SEVERITY_ERROR = 1;
+ public static final int SEVERITY_WARNING = 2;
+
+ /**
+ * Class constructor.
+ */
+ public BuildProblem() {
+
+ }
+
+ /**
+ * Class constructor.
+ * @param severity Severity of the problem: SEVERITY_WARNING or SEVERITY_ERROR
+ * @param resourcePath Path of the resource being built, relative to its
+ * plugin or project.
+ * @param message Description of the problem.
+ * @param lineNumber Line number on which the problem occurred, or -1 if unknown.
+ */
+ public BuildProblem(int severity, String resourcePath, String message, int lineNumber) {
+ this.severity = severity;
+ this.resourcePath = resourcePath;
+ this.message = message;
+ this.lineNumber = lineNumber;
+ }
+
+ /**
+ * Returns the line number on which the error occurred, or -1 if no
+ * line number can be identified.
+ */
+ public int getLineNumber() {
+ return lineNumber;
+ }
+
+ /**
+ * Returns the message describing the problem.
+ */
+ public String getMessage() {
+ return message;
+ }
+
+ /**
+ * Returns the path of the resource relative to the project root.
+ */
+ public String getResourcePath() {
+ return resourcePath;
+ }
+
+ /**
+ * Returns the severity of the problem, either SEVERITY_ERROR or
+ * SEVERITY_WARNING.
+ */
+ public int getSeverity() {
+ return severity;
+ }
+
+ /**
+ * Sets the line number of the problem.
+ * @param lineNumber Line number on which the problem occurred.
+ */
+ public void setLineNumber(int lineNumber) {
+ this.lineNumber = lineNumber;
+ }
+
+ /**
+ * Sets the message describing the problem.
+ * @param message Message describing the problem.
+ */
+ public void setMessage(String message) {
+ this.message = message;
+ }
+
+ /**
+ * Sets the path of the resource that had the problem.
+ * @param resourcePath Path of the resource, relative to the project root.
+ */
+ public void setResourcePath(String resourcePath) {
+ this.resourcePath = resourcePath;
+ }
+
+ /**
+ * Sets the severity of the problem.
+ * @param severity Severity of the problem. Should be one of
+ * SEVERITY_ERROR or SEVERITY_WARNING.
+ */
+ public void setSeverity(int severity) {
+ this.severity = severity;
+ }
+ //==================================================== PRIVATE
+
+ private String resourcePath;
+ private int severity;
+ private String message;
+ private int lineNumber;
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/config/BuildProblemDecorator.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/config/BuildProblemDecorator.java
new file mode 100644
index 0000000..f6b6193
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/config/BuildProblemDecorator.java
@@ -0,0 +1,81 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.ui.internal.config;
+
+import java.net.URL;
+
+
+import org.eclipse.core.resources.IMarker;
+import org.eclipse.core.resources.IResource;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.FileLocator;
+import org.eclipse.core.runtime.Path;
+import org.eclipse.jface.resource.ImageDescriptor;
+import org.eclipse.jface.viewers.IDecoration;
+import org.eclipse.jface.viewers.ILightweightLabelDecorator;
+import org.eclipse.jface.viewers.LabelProvider;
+import org.eclipse.jface.viewers.LabelProviderChangedEvent;
+import org.eclipse.wst.xml.vex.ui.internal.VexPlugin;
+
+/**
+ * Decorates Vex resources that build problems.
+ */
+public class BuildProblemDecorator extends LabelProvider implements ILightweightLabelDecorator {
+
+ public static final String ID = "org.eclipse.wst.xml.vex.ui.internal.config.buildProblemDecorator"; //$NON-NLS-1$
+
+ public void decorate(Object element, IDecoration decoration) {
+
+ if (this.errorIcon == null) {
+ this.loadImageDescriptors();
+ }
+
+ if (element instanceof IResource) {
+ try {
+ IResource resource = (IResource) element;
+ IMarker[] markers = resource.findMarkers(IMarker.PROBLEM, true, 0);
+ if (markers.length > 0) {
+ decoration.addOverlay(this.errorIcon, IDecoration.BOTTOM_LEFT);
+ }
+ } catch (CoreException e) {
+ }
+ }
+ }
+
+ /**
+ * Fire a change notification that the markers on the given resource has changed.
+ * @param resources Array of resources whose markers have changed.
+ */
+ public void update(IResource resource) {
+ this.fireLabelProviderChanged(new LabelProviderChangedEvent(this, resource));
+ }
+
+ /**
+ * Fire a change notification that the markers on the given resources has changed.
+ * @param resources Array of resources whose markers have changed.
+ */
+ public void update(IResource[] resources) {
+ this.fireLabelProviderChanged(new LabelProviderChangedEvent(this, resources));
+ }
+
+ //======================================================== PRIVATE
+
+ private ImageDescriptor errorIcon;
+
+ private void loadImageDescriptors() {
+ URL url = FileLocator.find(
+ VexPlugin.getInstance().getBundle(),
+ new Path("icons/error_co.gif"), //$NON-NLS-1$
+ null);
+ this.errorIcon = ImageDescriptor.createFromURL(url);
+ }
+
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/config/ConfigEvent.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/config/ConfigEvent.java
new file mode 100644
index 0000000..0f45022
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/config/ConfigEvent.java
@@ -0,0 +1,28 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.ui.internal.config;
+
+import java.util.EventObject;
+
+/**
+ * Event indicating a change of configuration items.
+ */
+public class ConfigEvent extends EventObject {
+
+ /**
+ * Class constructor.
+ * @param source Source of the event.
+ */
+ public ConfigEvent(Object source) {
+ super(source);
+ }
+
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/config/ConfigItem.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/config/ConfigItem.java
new file mode 100644
index 0000000..b1a202c
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/config/ConfigItem.java
@@ -0,0 +1,161 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.ui.internal.config;
+
+import java.io.Serializable;
+import java.net.MalformedURLException;
+import java.net.URL;
+
+
+/**
+ * Base class of all configurtion items such as document types and styles.
+ */
+public abstract class ConfigItem implements Comparable, Serializable {
+
+ /**
+ * Class constructor.
+ * @param config VexConfiguration to which this item belongs.
+ */
+ public ConfigItem(ConfigSource config) {
+ this.config = config;
+ }
+
+ public int compareTo(Object o) {
+ return this.getName().compareTo(((ConfigItem) o).getName());
+ }
+
+ /**
+ * Generate a simple identifier for the item that is unique with its
+ * configuration.
+ */
+ public String generateSimpleId() {
+ String base = "id"; //$NON-NLS-1$
+ int i = 1;
+ for (;;) {
+ String id = base + i;
+ if (this.getConfig().getItem(id) == null) {
+ return id;
+ }
+ i++;
+ }
+ }
+
+ /**
+ * Returns the VexConfiguration to which this item belongs.
+ */
+ public ConfigSource getConfig() {
+ return config;
+ }
+
+ /**
+ * Returns the extension point ID of configuration item. This will be
+ * the same for all config items of a given type.
+ */
+ public abstract String getExtensionPointId();
+
+ /**
+ * Returns the simple ID of this item. This is a short string containing
+ * no periods.
+ */
+ public String getSimpleId() {
+ return this.id;
+ }
+
+ /**
+ * Returns the unique ID of this item. The unique ID is formed by concatenating
+ * the ID of the associated VexConfiguration, a period, and the simple ID
+ * of this item.
+ */
+ public String getUniqueId() {
+ return this.id == null ? null : this.getConfig().getUniqueIdentifer() + "." + this.id; //$NON-NLS-1$
+ }
+
+ /**
+ * Returns the human-readable name for this item.
+ */
+ public String getName() {
+ return this.name;
+ }
+
+ /**
+ * Returns the path of the associated resource, if any, relative to
+ * the base directory of the associated VexConfiguration. Returns null
+ * if no resource is associated with this item.
+ */
+ public String getResourcePath() {
+ return this.resourcePath;
+ }
+
+ /**
+ * Returns the URL of the associated resource, if any. This is formed
+ * by appending the resource path to the URL of the associated
+ * VexConfiguration.
+ */
+ public URL getResourceUrl() throws MalformedURLException {
+ if (this.resourcePath == null) {
+ return null;
+ } else {
+ return new URL(this.getConfig().getBaseUrl(), this.getResourcePath());
+ }
+ }
+
+ /**
+ * Returns true if s is null or empty. Convenience method to be used
+ * in isValid().
+ * @param s String to check.
+ */
+ protected boolean isBlank(String s) {
+ return s == null || s.length() == 0;
+ }
+
+ /**
+ * Returns true if this item has sufficient information to be used as
+ * a configuration item. By default, the item must have a simple ID
+ * and a name. Subclasses should override and provide additional checks
+ * as necessary, after calling this base implementation.
+ */
+ public boolean isValid() {
+ return !isBlank(id) && !isBlank(name);
+ }
+
+ /**
+ * Sets the name for this item.
+ * @param name New name for this item.
+ */
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ /**
+ * Sets the resource path for this item.
+ * @param resourcePath New resource path for this item.
+ */
+ public void setResourcePath(String resourcePath) {
+ this.resourcePath = resourcePath;
+ }
+
+ /**
+ * Sets the simple ID for this item. The simple ID should only contain
+ * letters and numbers, and must not contain a period.
+ * @param id New simple ID for this item.
+ */
+ public void setSimpleId(String id) {
+ this.id = id;
+ }
+
+ //==================================================== PRIVATE
+
+ private String id;
+ private String name;
+ private String resourcePath;
+ private ConfigSource config;
+
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/config/ConfigLoaderJob.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/config/ConfigLoaderJob.java
new file mode 100644
index 0000000..22f9b7e
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/config/ConfigLoaderJob.java
@@ -0,0 +1,206 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * John Krasnay - initial API and implementation
+ * Ed Burnette - 7/23/2006 - Changes needed to build on 3.2.
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.ui.internal.config;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.text.MessageFormat;
+
+
+import org.eclipse.core.resources.IProject;
+import org.eclipse.core.resources.IWorkspaceRoot;
+import org.eclipse.core.resources.ResourcesPlugin;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IExtensionRegistry;
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.core.runtime.Platform;
+import org.eclipse.core.runtime.Status;
+import org.eclipse.core.runtime.jobs.Job;
+import org.eclipse.wst.xml.vex.ui.internal.VexPlugin;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.Constants;
+
+/**
+ * Job that loads Vex configuration objects from plugins and plugin projects.
+ */
+public class ConfigLoaderJob extends Job {
+
+ public static final String PLUGIN_CONFIG_SER_PREFIX = ".vexConfig-"; //$NON-NLS-1$
+ public static final String PLUGIN_CONFIG_SER_SUFFIX = ".ser"; //$NON-NLS-1$
+
+
+ /**
+ * Class constructor.
+ */
+ public ConfigLoaderJob() {
+ super(Messages.getString("ConfigLoaderJob.loadingConfig")); //$NON-NLS-1$
+ }
+
+ protected IStatus run(IProgressMonitor monitor) {
+
+ //System.out.println("ConfigLoaderJob starts");
+
+ int pluginCount = Platform.getExtensionRegistry().getNamespaces().length;
+ int projectCount = ResourcesPlugin.getWorkspace().getRoot().getProjects().length;
+
+ monitor.beginTask(Messages.getString("ConfigLoaderJob.loadingConfig"), pluginCount + projectCount); //$NON-NLS-1$
+
+ this.loadPlugins(monitor);
+ this.loadPluginProjects(monitor);
+ ConfigRegistry.getInstance().fireConfigLoaded(new ConfigEvent(this));
+
+ monitor.done();
+
+ //System.out.println("ConfigLoaderJob ends");
+
+ return Status.OK_STATUS;
+ }
+
+ //======================================================= PRIVATE
+
+ /**
+ * Load configurations from all registered plugins.
+ */
+ private void loadPlugins(IProgressMonitor monitor) {
+
+ ConfigRegistry configRegistry = ConfigRegistry.getInstance();
+ IExtensionRegistry extRegistry = Platform.getExtensionRegistry();
+ String[] namespaces = extRegistry.getNamespaces();
+ for (int i = 0; i < namespaces.length; i++) {
+
+ String ns = namespaces[i];
+ Bundle bundle = Platform.getBundle(ns);
+ if (bundle == null)
+ continue;
+
+ String name = (String) bundle.getHeaders().get(Constants.BUNDLE_NAME);
+ monitor.subTask(Messages.getString("ConfigLoaderJob.loading") + name); //$NON-NLS-1$
+
+ File stateDir = Platform.getStateLocation(bundle).toFile();
+ String version = (String) bundle.getHeaders().get(Constants.BUNDLE_VERSION);
+ String serFile = PLUGIN_CONFIG_SER_PREFIX + version + PLUGIN_CONFIG_SER_SUFFIX;
+ File configSerFile = new File(stateDir, serFile);
+
+ ConfigSource source = null;
+ if (configSerFile.exists()) {
+ try {
+ //long start = System.currentTimeMillis();
+ source = loadConfigSourceFromFile(configSerFile);
+ //long end = System.currentTimeMillis();
+ //System.out.println(" load from ser file took " + (end-start) + "ms");
+ } catch (IOException ex) {
+ String message = MessageFormat.format(
+ Messages.getString("ConfigLoaderJob.loadingError"), //$NON-NLS-1$
+ new Object[] { configSerFile });
+ this.log(IStatus.WARNING, message, ex);
+ }
+ }
+
+ if (source == null) {
+
+ source = ConfigPlugin.load(ns);
+
+ if (source != null) {
+ try {
+ saveConfigSourceToFile(source, configSerFile);
+ } catch (IOException ex) {
+ String message = MessageFormat.format(
+ Messages.getString("ConfigLoaderJob.cacheError"), //$NON-NLS-1$
+ new Object[] { configSerFile });
+ this.log(IStatus.WARNING, message, ex);
+ }
+ } else {
+ if (configSerFile.exists()) {
+ configSerFile.delete(); // Used to have a config, but now we don't
+ }
+ }
+
+ }
+
+ if (source != null) {
+ configRegistry.addConfigSource(source);
+ }
+
+ monitor.worked(1);
+ }
+ }
+
+ private static ConfigSource loadConfigSourceFromFile(File file) throws IOException {
+
+ FileInputStream fis = null;
+ try {
+ fis = new FileInputStream(file);
+ ObjectInputStream ois = new ObjectInputStream(fis);
+ return (ConfigSource) ois.readObject();
+ } catch (ClassNotFoundException ex) {
+ throw new IOException(ex.getMessage());
+ } finally {
+ if (fis != null) {
+ try {
+ fis.close();
+ } catch (IOException ex) {
+ }
+ }
+ }
+ }
+
+ private static void saveConfigSourceToFile(ConfigSource config, File file) throws IOException {
+ FileOutputStream fos = null;
+ try {
+ fos = new FileOutputStream(file);
+ ObjectOutputStream oos = new ObjectOutputStream(fos);
+ oos.writeObject(config);
+ } finally {
+ if (fos != null) {
+ try {
+ fos.close();
+ } catch (IOException ex) {
+ }
+ }
+ }
+ }
+
+ /**
+ * Load configurations from all Vex Plugin Projects in the workspace.
+ */
+ private void loadPluginProjects(IProgressMonitor monitor) {
+
+ IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot();
+ IProject[] projects = root.getProjects();
+
+ for (int i = 0; i < projects.length; i++) {
+ try {
+ if (projects[i].isOpen() &&
+ projects[i].hasNature(PluginProjectNature.ID)) {
+ monitor.subTask(Messages.getString("ConfigLoaderJob.loadingProject") + projects[i].getName()); //$NON-NLS-1$
+ PluginProject.load(projects[i]);
+ monitor.worked(1);
+ }
+ } catch (CoreException e) {
+ String message = MessageFormat.format(
+ Messages.getString("ConfigLoaderJob.natureError"), //$NON-NLS-1$
+ new Object[] { projects[i].getName() });
+ VexPlugin.getInstance().log(IStatus.ERROR, message, e);
+ }
+ }
+ }
+
+
+ private void log(int severity, String message, Throwable exception) {
+ VexPlugin.getInstance().log(severity, message, exception);
+ }
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/config/ConfigPlugin.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/config/ConfigPlugin.java
new file mode 100644
index 0000000..33ef9f5
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/config/ConfigPlugin.java
@@ -0,0 +1,78 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.ui.internal.config;
+
+import java.io.IOException;
+import java.net.URL;
+import java.text.MessageFormat;
+
+
+import org.eclipse.core.runtime.IExtension;
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.core.runtime.Platform;
+import org.eclipse.wst.xml.vex.ui.internal.VexPlugin;
+
+/**
+ * A regular Eclipse plugin that provides Vex configuration items.
+ */
+public class ConfigPlugin extends ConfigSource {
+
+ /**
+ * Filename prefix used when serializing the config from an installed
+ * plugin. Since all versions of a plugin share the same persistence
+ * area, we incorporate the plugin version number into the filename.
+ */
+ public static final String SER_FILE_PREFIX = ".vexConfig-"; //$NON-NLS-1$
+
+ /** Filename suffix used when serializing an installed plugin */
+ public static final String SER_FILE_SUFFIX = ".ser"; //$NON-NLS-1$
+
+ protected ConfigPlugin(String namespace) {
+ this.namespace = namespace;
+ }
+
+ public static ConfigPlugin load(String namespace) {
+
+ ConfigPlugin configPlugin = new ConfigPlugin(namespace);
+ configPlugin.setUniqueIdentifer(namespace);
+
+ IExtension[] exts = Platform.getExtensionRegistry().getExtensions(namespace);
+ for (int i = 0; i < exts.length; i++) {
+ IExtension ext = exts[i];
+ try {
+ configPlugin.addItem(
+ ext.getExtensionPointUniqueIdentifier(),
+ ext.getSimpleIdentifier(),
+ ext.getLabel(),
+ ConfigurationElementWrapper.convertArray(ext.getConfigurationElements()));
+ } catch (IOException e) {
+ String message = MessageFormat.format(
+ Messages.getString("ConfigPlugin.loadError"), //$NON-NLS-1$
+ new Object[] { ext.getSimpleIdentifier(), namespace });
+ VexPlugin.getInstance().log(IStatus.ERROR, message, e);
+ return null;
+ }
+ }
+
+ configPlugin.parseResources(null);
+
+ return configPlugin.isEmpty() ? null : configPlugin;
+ }
+
+
+ public URL getBaseUrl() {
+ return Platform.getBundle(namespace).getEntry("plugin.xml"); //$NON-NLS-1$
+ }
+
+ //======================================================= PRIVATE
+
+ private String namespace;
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/config/ConfigRegistry.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/config/ConfigRegistry.java
new file mode 100644
index 0000000..4c7b785
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/config/ConfigRegistry.java
@@ -0,0 +1,331 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.ui.internal.config;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+
+import org.eclipse.core.resources.IProject;
+import org.eclipse.core.resources.IResourceChangeEvent;
+import org.eclipse.core.resources.IResourceChangeListener;
+import org.eclipse.core.resources.IResourceDelta;
+import org.eclipse.core.resources.ResourcesPlugin;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.Platform;
+import org.eclipse.core.runtime.jobs.ILock;
+import org.eclipse.swt.widgets.Display;
+import org.eclipse.wst.xml.vex.core.internal.core.ListenerList;
+
+/**
+ * Singleton registry of configuration sources and listeners.
+ *
+ * The configuration sources may be accessed by multiple threads, and are
+ * protected by a lock. All methods that modify or iterate over config sources
+ * do so after acquiring the lock. Callers that wish to perform multiple
+ * operations as an atomic transaction must lock and unlock the registry as
+ * follows.
+ *
+ * <pre>
+ * ConfigRegistry reg = ConfigRegistry.getInstance();
+ * try {
+ * reg.lock();
+ * // make modifications
+ * } finally {
+ * reg.unlock();
+ * }
+ * </pre>
+ *
+ * <p>This class also maintains a list of ConfigListeners. The
+ * addConfigListener and removeConfigListener methods must be called from
+ * the main UI thread. The fireConfigXXX methods may be called from other
+ * threads; this class will ensure the listeners are called on the UI thread.
+ */
+public class ConfigRegistry {
+
+ /**
+ * Returns the singleton instance of the registry.
+ */
+ public static ConfigRegistry getInstance() {
+ return instance;
+ }
+
+ /**
+ * Add a VexConfiguration to the list of configurations.
+ * @param config VexConfiguration to be added.
+ */
+ public void addConfigSource(ConfigSource config) {
+ try {
+ this.lock();
+ this.configs.add(config);
+ } finally {
+ this.unlock();
+ }
+ }
+
+ /**
+ * Adds a ConfigChangeListener to the notification list.
+ * @param listener Listener to be added.
+ */
+ public void addConfigListener(IConfigListener listener) {
+ this.configListeners.add(listener);
+ }
+
+ /**
+ * Call the configChanged method on all registered ConfigChangeListeners.
+ * The listeners are called from the display thread, even if this method
+ * was called from another thread.
+ * @param e ConfigEvent to be fired.
+ */
+ public void fireConfigChanged(final ConfigEvent e) {
+ if (this.isConfigLoaded()) {
+ Runnable runnable = new Runnable() {
+ public void run() {
+ configListeners.fireEvent("configChanged", e); //$NON-NLS-1$
+ }
+ };
+
+ Display display = Display.getDefault();
+ if (display.getThread() == Thread.currentThread()) {
+ runnable.run();
+ } else {
+ display.asyncExec(runnable);
+ }
+ }
+ }
+
+ /**
+ * Call the configLoaded method on all registered ConfigChangeListeners
+ * from the display thread.
+ * This method is called from the ConfigLoaderJob thread.
+ * @param e ConfigEvent to be fired.
+ */
+ public void fireConfigLoaded(final ConfigEvent e) {
+ Runnable runnable = new Runnable() {
+ public void run() {
+ configLoaded = true;
+ configListeners.fireEvent("configLoaded", e); //$NON-NLS-1$
+ }
+ };
+
+ Display.getDefault().asyncExec(runnable);
+ }
+
+ /**
+ * Returns an array of all config item factories.
+ */
+ public IConfigItemFactory[] getAllConfigItemFactories() {
+ List f = this.configItemFactories;
+ return (IConfigItemFactory[]) f.toArray(new IConfigItemFactory[f.size()]);
+ }
+
+ /**
+ * Returns an array of all registered ConfigItem objects implementing
+ * the given extension point.
+ * @param extensionPoint ID of the desired extension point.
+ */
+ public List getAllConfigItems(String extensionPoint) {
+ try {
+ this.lock();
+ List items = new ArrayList();
+ for (Iterator it = this.configs.iterator(); it.hasNext(); ) {
+ ConfigSource config = (ConfigSource) it.next();
+ items.addAll(config.getValidItems(extensionPoint));
+ }
+ return items;
+ } finally {
+ this.unlock();
+ }
+ }
+
+ /**
+ * Returns a list of all registered ConfigSource objects.
+ * @return
+ */
+ public List getAllConfigSources() {
+ try {
+ this.lock();
+ List result = new ArrayList();
+ result.addAll(this.configs);
+ return result;
+ } finally {
+ this.unlock();
+ }
+ }
+
+ /**
+ * Returns a specific configuration item given an extension point id
+ * and the item's id. Returns null if either the extension point or
+ * the item is not found.
+ *
+ * @param extensionPoint ID of the desired extension point.
+ * @param id ID of the desired item.
+ */
+ public ConfigItem getConfigItem(String extensionPoint, String id) {
+ try {
+ this.lock();
+ List items = this.getAllConfigItems(extensionPoint);
+ for (Iterator it = items.iterator(); it.hasNext(); ) {
+ ConfigItem item = (ConfigItem) it.next();
+ if (item.getUniqueId().equals(id)) {
+ return item;
+ }
+ }
+ return null;
+ } finally {
+ this.unlock();
+ }
+ }
+
+ /**
+ * Returns the IConfigItemFactory object for the given extension point
+ * or null if none exists.
+ * @param extensionPointId Extension point ID for which to search.
+ */
+ public IConfigItemFactory getConfigItemFactory(String extensionPointId) {
+ for (Iterator it = this.configItemFactories.iterator(); it.hasNext();) {
+ IConfigItemFactory factory = (IConfigItemFactory) it.next();
+ if (factory.getExtensionPointId().equals(extensionPointId)) {
+ return factory;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Returns true if the Vex configuration has been loaded.
+ *
+ * @see org.eclipse.wst.xml.vex.ui.internal.config.ConfigLoaderJob
+ */
+ public boolean isConfigLoaded() {
+ return this.configLoaded;
+ }
+
+
+ /**
+ * Locks the registry for modification or iteration over its config sources.
+ */
+ public void lock() {
+ this.lock.acquire();
+ }
+
+
+ /**
+ * Remove a VexConfiguration from the list of configs.
+ * @param config VexConfiguration to remove.
+ */
+ public void removeConfigSource(ConfigSource config) {
+ try {
+ this.lock();
+ this.configs.remove(config);
+ } finally {
+ this.unlock();
+ }
+ }
+
+ /**
+ * Removes a ConfigChangeListener from the notification list.
+ * @param listener Listener to be removed.
+ */
+ public void removeConfigListener(IConfigListener listener) {
+ this.configListeners.remove(listener);
+ }
+
+
+
+ /**
+ * Unlocks the registry.
+ */
+ public void unlock() {
+ this.lock.release();
+ }
+
+ //======================================================== PRIVATE
+
+ private static ConfigRegistry instance = new ConfigRegistry();
+
+ private ILock lock = Platform.getJobManager().newLock();
+ private List configs = new ArrayList();
+ private ListenerList configListeners = new ListenerList(IConfigListener.class, ConfigEvent.class);
+ private boolean configLoaded = false;
+ private List configItemFactories = new ArrayList();
+
+
+ /**
+ * Class constructor. All initialization is performed here.
+ */
+ private ConfigRegistry() {
+ this.configItemFactories.add(new DoctypeFactory());
+ this.configItemFactories.add(new StyleFactory());
+ // TODO do we ever unregister this?
+ ResourcesPlugin.getWorkspace().addResourceChangeListener(this.resourceChangeListener);
+ }
+
+ private IResourceChangeListener resourceChangeListener = new IResourceChangeListener() {
+
+ public void resourceChanged(final IResourceChangeEvent event) {
+
+ //System.out.println("resourceChanged, type is " + event.getType() + ", resource is " + event.getResource());
+
+ if (event.getType() == IResourceChangeEvent.PRE_CLOSE ||
+ event.getType() == IResourceChangeEvent.PRE_DELETE) {
+ PluginProject pp = PluginProject.get((IProject) event.getResource());
+ if (pp != null) {
+ //System.out.println(" removing project from config registry");
+ removeConfigSource(pp);
+ fireConfigChanged(new ConfigEvent(this));
+ }
+ } else if (event.getType() == IResourceChangeEvent.POST_CHANGE) {
+ IResourceDelta[] resources = event.getDelta().getAffectedChildren();
+ for (int i = 0; i < resources.length; i++) {
+ final IResourceDelta delta = resources[i];
+ if (delta.getResource() instanceof IProject) {
+ final IProject project = (IProject) delta.getResource();
+
+ //System.out.println("Project " + project.getName() + " changed, isOpen is " + project.isOpen());
+
+ PluginProject pluginProject = PluginProject.get(project);
+
+ boolean hasPluginProjectNature = false;
+ try {
+ hasPluginProjectNature = project.hasNature(PluginProjectNature.ID);
+ } catch (CoreException ex) {
+ // yup, sometimes checked exceptions really blow
+ }
+
+ if (!project.isOpen() && pluginProject != null) {
+
+ //System.out.println(" closing project: " + project.getName());
+ removeConfigSource(pluginProject);
+ fireConfigChanged(new ConfigEvent(this));
+
+ } else if (project.isOpen() && pluginProject == null && hasPluginProjectNature) {
+
+ //System.out.println(" newly opened project: " + project.getName() + ", rebuilding");
+
+ // Must be run in another thread, since the workspace is locked here
+ Runnable runnable = new Runnable() {
+ public void run() {
+ PluginProject.load(project);
+ }
+ };
+ Display.getDefault().asyncExec(runnable);
+ } else {
+ //System.out.println(" no action taken");
+ }
+ }
+ }
+ }
+ }
+ };
+
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/config/ConfigSource.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/config/ConfigSource.java
new file mode 100644
index 0000000..6a092b1
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/config/ConfigSource.java
@@ -0,0 +1,268 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.ui.internal.config;
+
+import java.io.IOException;
+import java.io.Serializable;
+import java.net.URL;
+import java.text.MessageFormat;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.wst.xml.vex.ui.internal.VexPlugin;
+
+
+/**
+ * A plugin or plugin project that contributes ConfigItems. This class and all
+ * configuration items added to it must be serializable, since it is persisted
+ * across Vex invocations due to the expense of reparsing the configuration
+ * items.
+ */
+public abstract class ConfigSource implements Serializable {
+
+ /**
+ * Adds the given item to the configuration.
+ * @param item ConfigItem to be added.
+ */
+ public void addItem(ConfigItem item) {
+ this.items.add(item);
+ }
+
+ /**
+ * Creates a configuration item and adds it to this configuration.
+ * If the given extension point does not have a factory registered
+ * in VexPlugin, no action is taken and null is returned.
+ *
+ * @param extensionPoint Extension point of the item to be added.
+ * @param simpleIdentifier Simple (i.e. no dots) identifier of the item.
+ * @param name Name of the item.
+ * @param configElements Array of IConfigElement objects representing the item's settings.
+ * @return The newly created ConfigItem, or null if extensionPoint is
+ * not recognized.
+ * @throws IOException
+ */
+ public ConfigItem addItem(
+ String extensionPoint,
+ String simpleIdentifier,
+ String name,
+ IConfigElement[] configElements) throws IOException {
+
+ IConfigItemFactory factory = ConfigRegistry.getInstance().getConfigItemFactory(extensionPoint);
+ if (factory != null) {
+ ConfigItem item = factory.createItem(this, configElements);
+ item.setSimpleId(simpleIdentifier);
+ item.setName(name);
+ this.addItem(item);
+ return item;
+ } else {
+ return null;
+ }
+
+ }
+
+ /**
+ * Removes the given item from the configuration.
+ * @param item ConfigItem to be removed.
+ */
+ public void remove(ConfigItem item) {
+ items.remove(item);
+ }
+
+ /**
+ * Remove all items from this configuration.
+ */
+ public void removeAllItems() {
+ this.items.clear();
+ }
+
+ /**
+ * Remove all parsed resources from this configuration.
+ */
+ public void removeAllResources() {
+ this.parsedResources.clear();
+ }
+
+ /**
+ * Remove the resource associated with the given URI from the resource
+ * cache. The factory must handle any of the following scenarios.
+ *
+ * <ul>
+ * <li>The URI represents the primary resource associated with a
+ * configuration item.</li>
+ * <li>The URI is a secondary resource associated with a primary
+ * resource. In this case the primary resource is removed.</li>
+ * <li>The URI has nothing to do with a configuration item,
+ * in which case no action is taken.</li>
+ * </ul>
+ *
+ * To fully implement this method, the factory must interact with the
+ * parser and track which secondary resources are associated with
+ * which primaries.
+ *
+ * @param uri Relative URI of the resource to remove.
+ */
+ public void removeResource(String uri) {
+ this.parsedResources.remove(uri); // TODO Respect secondary resources
+ }
+
+
+ /**
+ * Returns a list of all items in this configuration.
+ */
+ public List getAllItems() {
+ return items;
+ }
+
+ /**
+ * Returns all ConfigItems of the given type registered with this
+ * configuration.
+ * @param type The type of ConfigItem to return.
+ */
+ public Collection getAllItems(String type) {
+ List items = new ArrayList();
+ for (Iterator it = this.items.iterator(); it.hasNext();) {
+ ConfigItem item = (ConfigItem) it.next();
+ if (item.getExtensionPointId().equals(type)) {
+ items.add(item);
+ }
+ }
+ return items;
+ }
+
+ /**
+ * Returns the base URL of this factory. This is used to resolve
+ * relative URLs in config items
+ */
+ public abstract URL getBaseUrl();
+
+
+ /**
+ * Returns a particular item from the configuration. Returns null if no
+ * matching item could be found.
+ * @param simpleId Simple ID of the item to return.
+ */
+ public ConfigItem getItem(String simpleId) {
+ for (Iterator it = this.items.iterator(); it.hasNext();) {
+ ConfigItem item = (ConfigItem) it.next();
+ if (item.getSimpleId() != null && item.getSimpleId().equals(simpleId)) {
+ return item;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Returns the item for the resource with the given path relative
+ * to the plugin or project. May return null if no such item exists.
+ * @param resourcePath Path of the resource.
+ */
+ public ConfigItem getItemForResource(String resourcePath) {
+ for (Iterator it = this.items.iterator(); it.hasNext();) {
+ ConfigItem item = (ConfigItem) it.next();
+ if (item.getResourcePath().equals(resourcePath)) {
+ return item;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Returns the parsed resource object for the given URI, or null of
+ * none exists.
+ * @param uri URI of the resource, relative to the base URL of this configuration.
+ */
+ public Object getParsedResource(String uri) {
+ return this.parsedResources.get(uri);
+ }
+
+ /**
+ * Returns the unique identifier of this configuration. This is the same
+ * as the ID of the plugin that defines the configuration.
+ */
+ public String getUniqueIdentifer() {
+ return this.id;
+ }
+
+ /**
+ * Returns all ConfigItems of the given type for which isValid returns
+ * true.
+ * @param type The type of ConfigItem to return.
+ */
+ public Collection getValidItems(String type) {
+ Collection allItems = this.getAllItems(type);
+ List validItems = new ArrayList();
+ for (Iterator it = allItems.iterator(); it.hasNext();) {
+ ConfigItem item = (ConfigItem) it.next();
+ if (item.isValid()) {
+ validItems.add(item);
+ }
+ }
+ return validItems;
+ }
+
+ /**
+ * Returns true if there are no items in this configuration.
+ */
+ public boolean isEmpty() {
+ return this.items.isEmpty();
+ }
+
+ /**
+ * Parses all resources required by the registered items.
+ * @param problemHandler Handler for build problems. May be null.
+ */
+ public void parseResources(IBuildProblemHandler problemHandler) {
+ for (Iterator it = this.items.iterator(); it.hasNext();) {
+ ConfigItem item = (ConfigItem) it.next();
+ String uri = item.getResourcePath();
+ if (!this.parsedResources.containsKey(uri)) {
+ IConfigItemFactory factory = ConfigRegistry.getInstance().getConfigItemFactory(item.getExtensionPointId());
+ Object parsedResource;
+ try {
+ parsedResource = factory.parseResource(this.getBaseUrl(), uri, problemHandler);
+ this.parsedResources.put(uri, parsedResource);
+ } catch (IOException ex) {
+ String message = MessageFormat.format(
+ Messages.getString("ConfigSource.errorParsingUri"),
+ new Object[] { uri });
+ VexPlugin.getInstance().log(IStatus.ERROR, message, ex); //$NON-NLS-1$
+ }
+ }
+ }
+ }
+
+ /**
+ * Sets the unique identifier of this configuration.
+ * @param id New identifier for this configuration.
+ */
+ public void setUniqueIdentifer(String id) {
+ this.id = id;
+ }
+
+ //==================================================== PRIVATE
+
+ // Globally-unique identifier of this configuration
+ // == the plugin id.
+ private String id;
+
+ // all config items in this configuration
+ private List items = new ArrayList();
+
+ // map String URI => parsed resource
+ private Map parsedResources = new HashMap();
+
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/config/ConfigurationElement.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/config/ConfigurationElement.java
new file mode 100644
index 0000000..1ba36eb
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/config/ConfigurationElement.java
@@ -0,0 +1,129 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.ui.internal.config;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Lightweight implementation of the IConfigurationElement interface. This
+ * class is used by config item factories when re-creating the configuration
+ * elements corresponding to a given config item.
+ */
+public class ConfigurationElement implements IConfigElement {
+
+ /**
+ * Class constructor.
+ */
+ public ConfigurationElement() {
+ }
+
+ /**
+ * Class constructor.
+ * @param name Name of the element.
+ */
+ public ConfigurationElement(String name) {
+ this.name = name;
+ }
+
+ /**
+ * Adds a new child to this element.
+ * @param child child to be added.
+ */
+ public void addChild(IConfigElement child) {
+ this.children.add(child);
+ }
+
+ public String getAttribute(String name) {
+ return (String) this.attributes.get(name);
+ }
+
+ public String[] getAttributeNames() {
+ Set keys = this.attributes.keySet();
+ return (String[]) keys.toArray(new String[keys.size()]);
+ }
+
+ public IConfigElement[] getChildren() {
+ return (IConfigElement[]) this.children.toArray(new IConfigElement[this.children.size()]);
+ }
+
+ public IConfigElement[] getChildren(String name) {
+ List kids = new ArrayList();
+ for (Iterator it = this.children.iterator(); it.hasNext();) {
+ IConfigElement child = (IConfigElement) it.next();
+ if (child.getName().equals(name)) {
+ kids.add(child);
+ }
+ }
+ return (IConfigElement[]) kids.toArray(new IConfigElement[kids.size()]);
+ }
+
+ public String getName() {
+ return this.name;
+ }
+
+ public String getValue() {
+ return this.value;
+ }
+
+ /**
+ * Sets the given attribute. If value is null, the attribute is removed
+ * from the element.
+ *
+ * @param name Name of the attribute.
+ * @param value Value of the attribute.
+ */
+ public void setAttribute(String name, String value) {
+ if (value == null) {
+ this.attributes.remove(name);
+ } else {
+ this.attributes.put(name, value);
+ }
+ }
+
+ /**
+ * Sets the children of this element given an array of IConfigElement
+ * objects.
+ * @param children Children of this element.
+ */
+ public void setChildren(IConfigElement[] children) {
+ this.children.clear();
+ this.children.addAll(Arrays.asList(children));
+ }
+
+ /**
+ * Sets the name of the element.
+ * @param name Name of the element.
+ */
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ /**
+ * Sets the value of the element.
+ * @param value Value of the element.
+ */
+ public void setValue(String value) {
+ this.value = value;
+ }
+
+ //==================================================== PRIVATE
+
+ private String name;
+ private String value;
+ private Map attributes = new HashMap();
+ private List children = new ArrayList();
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/config/ConfigurationElementWrapper.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/config/ConfigurationElementWrapper.java
new file mode 100644
index 0000000..361a0a0
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/config/ConfigurationElementWrapper.java
@@ -0,0 +1,69 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.ui.internal.config;
+
+import org.eclipse.core.runtime.IConfigurationElement;
+
+/**
+ * Wrapper for IConfigurationElement that implements IConfigElement.
+ */
+public class ConfigurationElementWrapper implements IConfigElement {
+
+ /**
+ * Class constructor.
+ * @param element Element to be wrapped.
+ */
+ public ConfigurationElementWrapper(IConfigurationElement element) {
+ this.element = element;
+ }
+
+ public String getAttribute(String name) {
+ return this.element.getAttribute(name);
+ }
+
+ public String[] getAttributeNames() {
+ return this.element.getAttributeNames();
+ }
+
+ public IConfigElement[] getChildren() {
+ return convertArray(this.element.getChildren());
+ }
+
+ public IConfigElement[] getChildren(String name) {
+ return convertArray(this.element.getChildren(name));
+ }
+
+ public String getName() {
+ return this.element.getName();
+ }
+
+ public String getValue() {
+ return this.element.getValue();
+ }
+
+ /**
+ * Wraps each element in an array of IConfigurationElement objects with
+ * a ConfigurationElementWrapper and returns the result.
+ * @param elements Array of elements to be wrapped.
+ */
+ public static IConfigElement[] convertArray(IConfigurationElement[] elements) {
+ IConfigElement[] ret = new IConfigElement[elements.length];
+ for (int i = 0; i < elements.length; i++) {
+ ret[i] = new ConfigurationElementWrapper(elements[i]);
+ }
+ return ret;
+ }
+
+ //=================================================== PRIVATE
+
+ private IConfigurationElement element;
+
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/config/ConfigurationView.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/config/ConfigurationView.java
new file mode 100644
index 0000000..3e3627a
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/config/ConfigurationView.java
@@ -0,0 +1,134 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.ui.internal.config;
+
+import java.util.Collections;
+import java.util.List;
+
+import org.eclipse.jface.viewers.ITreeContentProvider;
+import org.eclipse.jface.viewers.LabelProvider;
+import org.eclipse.jface.viewers.TreeViewer;
+import org.eclipse.jface.viewers.Viewer;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Label;
+import org.eclipse.ui.part.ViewPart;
+
+/**
+ * View showing all configuration items defined in Vex.
+ */
+public class ConfigurationView extends ViewPart {
+
+ public void createPartControl(Composite parent) {
+
+ this.parentControl = parent;
+
+ ConfigRegistry registry = ConfigRegistry.getInstance();
+
+ registry.addConfigListener(this.configListener);
+
+ if (registry.isConfigLoaded()) {
+ this.createTreeViewer();
+ } else {
+ this.loadingLabel = new Label(parent, SWT.NONE);
+ this.loadingLabel.setText(Messages.getString("ConfigurationView.loading")); //$NON-NLS-1$
+ }
+
+ }
+
+ public void dispose() {
+ super.dispose();
+ ConfigRegistry.getInstance().removeConfigListener(this.configListener);
+ }
+
+ public void setFocus() {
+ if (this.treeViewer != null) {
+ this.treeViewer.getTree().setFocus();
+ }
+ }
+
+ //===================================================== PRIVATE
+
+ private Composite parentControl;
+
+ private Label loadingLabel;
+
+ private TreeViewer treeViewer;
+
+ private void createTreeViewer() {
+ this.treeViewer = new TreeViewer(this.parentControl, SWT.SINGLE);
+ this.treeViewer.setContentProvider(new ContentProvider());
+ this.treeViewer.setLabelProvider(new MyLabelProvider());
+ this.treeViewer.setAutoExpandLevel(2);
+ this.treeViewer.setInput(ConfigRegistry.getInstance());
+ }
+
+ private static class ContentProvider implements ITreeContentProvider {
+
+ public Object[] getChildren(Object parentElement) {
+ if (parentElement instanceof IConfigItemFactory) {
+ IConfigItemFactory factory = (IConfigItemFactory) parentElement;
+ List items = ConfigRegistry.getInstance().getAllConfigItems(factory.getExtensionPointId());
+ Collections.sort(items);
+ return items.toArray();
+ } else {
+ return null;
+ }
+ }
+
+ public Object getParent(Object element) {
+ if (element instanceof ConfigItem) {
+ ConfigItem item = (ConfigItem) element;
+ return ConfigRegistry.getInstance().getConfigItemFactory(item.getExtensionPointId());
+ } else {
+ return ConfigRegistry.getInstance();
+ }
+ }
+
+ public boolean hasChildren(Object element) {
+ return element instanceof IConfigItemFactory;
+ }
+
+ public Object[] getElements(Object inputElement) {
+ return ConfigRegistry.getInstance().getAllConfigItemFactories();
+ }
+
+ public void dispose() {
+ }
+
+ public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
+ }
+
+ }
+
+ private static class MyLabelProvider extends LabelProvider {
+
+ public String getText(Object element) {
+ if (element instanceof IConfigItemFactory) {
+ return ((IConfigItemFactory) element).getPluralName();
+ } else {
+ return ((ConfigItem) element).getName();
+ }
+ }
+ }
+
+ private IConfigListener configListener = new IConfigListener() {
+ public void configChanged(ConfigEvent e) {
+ treeViewer.refresh();
+ }
+
+ public void configLoaded(ConfigEvent e) {
+ loadingLabel.dispose();
+ createTreeViewer();
+ parentControl.layout();
+ }
+ };
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/config/DoctypeFactory.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/config/DoctypeFactory.java
new file mode 100644
index 0000000..01744bf
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/config/DoctypeFactory.java
@@ -0,0 +1,107 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.ui.internal.config;
+
+import java.io.IOException;
+import java.net.URL;
+
+import org.eclipse.wst.xml.vex.core.internal.dom.DTDValidator;
+
+
+import com.wutka.dtd.DTDParseException;
+
+/**
+ * Factory for DocumentType objects.
+ */
+public class DoctypeFactory implements IConfigItemFactory {
+
+ public IConfigElement[] createConfigurationElements(ConfigItem item) {
+ DocumentType doctype = (DocumentType) item;
+ ConfigurationElement doctypeElement = new ConfigurationElement(ELT_DOCTYPE);
+ doctypeElement.setAttribute(ATTR_PUBLIC_ID, doctype.getPublicId());
+ doctypeElement.setAttribute(ATTR_SYSTEM_ID, doctype.getSystemId());
+ doctypeElement.setAttribute(ATTR_DTD, doctype.getResourcePath());
+ doctypeElement.setAttribute(ATTR_OUTLINE_PROVIDER, doctype.getOutlineProvider());
+
+ String[] names = doctype.getRootElements();
+ for (int i = 0; i < names.length; i++) {
+ String name = names[i];
+ ConfigurationElement rootElement = new ConfigurationElement(ELT_ROOT_ELEMENT);
+ rootElement.setAttribute(ATTR_NAME, name);
+ doctypeElement.addChild(rootElement);
+ }
+
+ return new IConfigElement[] { doctypeElement };
+ }
+
+ public ConfigItem createItem(ConfigSource config, IConfigElement[] configElements) throws IOException {
+ if (configElements.length < 1) {
+ return null;
+ }
+ IConfigElement configElement = configElements[0];
+ DocumentType doctype = new DocumentType(config);
+ doctype.setPublicId(configElement.getAttribute(ATTR_PUBLIC_ID));
+ doctype.setSystemId(configElement.getAttribute(ATTR_SYSTEM_ID));
+ doctype.setResourcePath(configElement.getAttribute(ATTR_DTD));
+ doctype.setOutlineProvider(configElement.getAttribute(ATTR_OUTLINE_PROVIDER));
+
+ IConfigElement[] rootElementRefs = configElement.getChildren();
+ String[] rootElements = new String[rootElementRefs.length];
+ for (int i = 0; i < rootElementRefs.length; i++) {
+ rootElements[i] = rootElementRefs[i].getAttribute("name"); //$NON-NLS-1$
+ }
+ doctype.setRootElements(rootElements);
+
+ return doctype;
+ }
+
+ public String getExtensionPointId() {
+ return DocumentType.EXTENSION_POINT;
+ }
+
+ public String[] getFileExtensions() {
+ return EXTS;
+ }
+
+ public String getPluralName() {
+ return Messages.getString("DoctypeFactory.pluralName"); //$NON-NLS-1$
+ }
+
+ public Object parseResource(URL baseUrl, String resourcePath, IBuildProblemHandler problemHandler) throws IOException {
+ try {
+ return DTDValidator.create(new URL(baseUrl, resourcePath));
+ } catch (DTDParseException ex) {
+ if (problemHandler != null) {
+ BuildProblem problem = new BuildProblem();
+ problem.setSeverity(BuildProblem.SEVERITY_ERROR);
+ problem.setResourcePath(resourcePath);
+ problem.setMessage(ex.getMessage());
+ problem.setLineNumber(ex.getLineNumber());
+ problemHandler.foundProblem(problem);
+ }
+ throw ex;
+ }
+ }
+
+ //=================================================== PRIVATE
+
+ private static final String[] EXTS = new String[] { "dtd" }; //$NON-NLS-1$
+
+ private static final String ELT_DOCTYPE = "doctype"; //$NON-NLS-1$
+ private static final String ATTR_OUTLINE_PROVIDER = "outlineProvider"; //$NON-NLS-1$
+ private static final String ATTR_DTD = "dtd"; //$NON-NLS-1$
+ private static final String ATTR_SYSTEM_ID = "systemId"; //$NON-NLS-1$
+ private static final String ATTR_PUBLIC_ID = "publicId"; //$NON-NLS-1$
+
+ private static final String ELT_ROOT_ELEMENT = "rootElement"; //$NON-NLS-1$
+ private static final String ATTR_NAME = "name"; //$NON-NLS-1$
+
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/config/DoctypePropertyPage.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/config/DoctypePropertyPage.java
new file mode 100644
index 0000000..fb9108b
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/config/DoctypePropertyPage.java
@@ -0,0 +1,321 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.ui.internal.config;
+
+import java.text.MessageFormat;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+import java.util.TreeSet;
+
+
+import org.eclipse.core.resources.IFile;
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.layout.FillLayout;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.swt.widgets.Label;
+import org.eclipse.swt.widgets.Table;
+import org.eclipse.swt.widgets.TableItem;
+import org.eclipse.swt.widgets.Text;
+import org.eclipse.ui.dialogs.PropertyPage;
+import org.eclipse.wst.xml.vex.core.internal.dom.Validator;
+import org.eclipse.wst.xml.vex.ui.internal.VexPlugin;
+
+/**
+ * Property page for .dtd files.
+ */
+public class DoctypePropertyPage extends PropertyPage {
+
+ protected Control createContents(final Composite parent) {
+
+ pane = new Composite(parent, SWT.NONE);
+
+ createPropertySheet();
+
+ configListener = new IConfigListener() {
+
+ public void configChanged(ConfigEvent e) {
+
+ // This is fired when we open properties for a new doctype
+ // and we force it to be re-built to get a validator
+ // from which we get our list of prospective root elements.
+
+ String resourcePath = ((IFile) getElement()).getProjectRelativePath().toString();
+
+ ConfigSource config = getPluginProject();
+
+ doctype = (DocumentType) config.getItemForResource(resourcePath);
+
+ populateRootElements();
+ }
+
+ public void configLoaded(final ConfigEvent e) {
+
+ setMessage(getTitle());
+ populateDoctype();
+ setValid(true);
+
+ try { // force an incremental build
+ getPluginProject().writeConfigXml();
+ } catch (Exception ex) {
+ String message = MessageFormat.format(
+ Messages.getString("DoctypePropertyPage.errorWritingConfig"), //$NON-NLS-1$
+ new Object[] { PluginProject.PLUGIN_XML });
+ VexPlugin.getInstance().log(IStatus.ERROR, message, ex);
+ }
+
+ }
+ };
+
+ ConfigRegistry.getInstance().addConfigListener(configListener);
+
+ if (ConfigRegistry.getInstance().isConfigLoaded()) {
+
+ populateDoctype();
+ populateRootElements();
+
+ } else {
+
+ setValid(false);
+
+ setMessage(Messages.getString("DoctypePropertyPage.loading")); //$NON-NLS-1$
+
+ }
+
+ return pane;
+ }
+
+ private void createPropertySheet() {
+
+ GridLayout layout = new GridLayout();
+ layout.numColumns = 2;
+ pane.setLayout(layout);
+ GridData gd;
+
+ Label label;
+
+ label = new Label(pane, SWT.NONE);
+ label.setText(Messages.getString("DoctypePropertyPage.name")); //$NON-NLS-1$
+ this.nameText = new Text(pane, SWT.BORDER);
+ gd = new GridData();
+ gd.widthHint = NAME_WIDTH;
+ this.nameText.setLayoutData(gd);
+
+ label = new Label(pane, SWT.NONE);
+ label.setText(Messages.getString("DoctypePropertyPage.publicId")); //$NON-NLS-1$
+ this.publicIdText = new Text(pane, SWT.BORDER);
+ gd = new GridData();
+ gd.grabExcessHorizontalSpace = true;
+ gd.horizontalAlignment = GridData.FILL;
+ this.publicIdText.setLayoutData(gd);
+
+ label = new Label(pane, SWT.NONE);
+ label.setText(Messages.getString("DoctypePropertyPage.systemId")); //$NON-NLS-1$
+ this.systemIdText = new Text(pane, SWT.BORDER);
+ gd = new GridData();
+ gd.grabExcessHorizontalSpace = true;
+ gd.horizontalAlignment = GridData.FILL;
+ this.systemIdText.setLayoutData(gd);
+
+ final String resourcePath = ((IFile) this.getElement())
+ .getProjectRelativePath().toString();
+
+ final ConfigSource config = this.getPluginProject();
+
+ this.doctype = (DocumentType) config.getItemForResource(resourcePath);
+ if (this.doctype == null) {
+ this.doctype = new DocumentType(config);
+ this.doctype.setResourcePath(resourcePath);
+ config.addItem(this.doctype);
+ }
+
+ // Generate a simple ID for this one if necessary
+ if (this.doctype.getSimpleId() == null
+ || this.doctype.getSimpleId().length() == 0) {
+ this.doctype.setSimpleId(this.doctype.generateSimpleId());
+ }
+
+ // need to do GridLayout and GridData for this guy them fill with items
+
+ label = new Label(pane, SWT.NONE);
+ label.setText(Messages.getString("DoctypePropertyPage.rootElements")); //$NON-NLS-1$
+
+ gd = new GridData();
+ //gd.widthHint = COLUMN_1_WIDTH;
+ gd.verticalAlignment = GridData.BEGINNING;
+ gd.horizontalSpan = 2;
+ label.setLayoutData(gd);
+
+ final Composite tablePane = new Composite(pane, SWT.BORDER);
+
+ gd = new GridData(GridData.FILL_BOTH);
+ gd.heightHint = 200;
+ gd.horizontalSpan = 2;
+ tablePane.setLayoutData(gd);
+
+ final FillLayout fillLayout = new FillLayout();
+ tablePane.setLayout(fillLayout);
+
+ this.rootElementsTable = new Table(tablePane, SWT.CHECK);
+ }
+
+ /**
+ * Returns the PluginProject associated with this resource.
+ *
+ * @return
+ */
+ public PluginProject getPluginProject() {
+ IFile file = (IFile) this.getElement();
+ return PluginProject.get(file.getProject());
+ }
+
+ public boolean performOk() {
+
+ performApply();
+
+ return super.performOk();
+ }
+
+ public void performApply() {
+
+ this.doctype.setName(this.nameText.getText());
+ this.doctype.setPublicId(this.publicIdText.getText());
+ this.doctype.setSystemId(this.systemIdText.getText());
+
+ // collect root Elements from the rootElementsTable
+
+ final TableItem[] tia = this.rootElementsTable.getItems();
+
+ final ArrayList selectedRootElements = new ArrayList();
+
+ for (int i = 0; i < tia.length; i++) {
+ if (tia[i].getChecked()) {
+ selectedRootElements.add(tia[i].getText());
+ }
+ }
+
+ final String[] selectedRootElementsArray = new String[selectedRootElements
+ .size()];
+
+ for (int i = 0; i < selectedRootElementsArray.length; i++) {
+ selectedRootElementsArray[i] = (String) selectedRootElements.get(i);
+ }
+
+ this.doctype.setRootElements(selectedRootElementsArray);
+
+ try {
+ this.getPluginProject().writeConfigXml();
+ } catch (Exception ex) {
+ String message = MessageFormat.format(
+ Messages.getString("DoctypePropertyPage.errorWritingConfig"), //$NON-NLS-1$
+ new Object[] { PluginProject.PLUGIN_XML });
+ VexPlugin.getInstance().log(IStatus.ERROR, message, ex);
+ }
+
+ ConfigRegistry.getInstance().fireConfigChanged(new ConfigEvent(this));
+ }
+
+ public void performDefaults() {
+
+ super.performDefaults();
+
+ populateDoctype();
+
+ populateRootElements();
+ }
+
+ public void dispose() {
+ super.dispose();
+
+ if (this.configListener != null) {
+ ConfigRegistry.getInstance().removeConfigListener(
+ this.configListener);
+ }
+ }
+
+ //======================================================= PRIVATE
+
+ private DocumentType doctype;
+
+ private static final int NAME_WIDTH = 150;
+
+ private Composite pane;
+
+ private Text nameText;
+
+ private Text publicIdText;
+
+ private Text systemIdText;
+
+ private Table rootElementsTable;
+
+ private IConfigListener configListener;
+
+ private void populateDoctype() {
+ this.setText(this.nameText, this.doctype.getName());
+ this.setText(this.publicIdText, this.doctype.getPublicId());
+ this.setText(this.systemIdText, this.doctype.getSystemId());
+ }
+
+ /*
+ *
+ */
+
+ private void populateRootElements() {
+
+ final String resourcePath = ((IFile) this.getElement())
+ .getProjectRelativePath().toString();
+
+ final Validator validator = (Validator) ((ConfigSource) this
+ .getPluginProject()).getParsedResource(resourcePath);
+
+ if (validator != null) {
+
+ final List list = Arrays.asList(doctype.getRootElements());
+ final Set selectedRootElements = new TreeSet(list);
+
+ rootElementsTable.removeAll();
+
+ final java.util.List l = new ArrayList(validator
+ .getValidRootElements());
+ Collections.sort(l);
+ for (int i = 0; i < l.size(); i++) {
+
+ TableItem item1 = new TableItem(rootElementsTable, SWT.NONE);
+ item1.setText((String) l.get(i));
+
+ if (selectedRootElements.contains((String) l.get(i))) {
+ item1.setChecked(true);
+ }
+ }
+ } else {
+
+ try {
+ this.getPluginProject().writeConfigXml();
+ } catch (Exception ex) {
+ String message = MessageFormat.format(
+ Messages.getString("DoctypePropertyPage.errorWritingConfig"), //$NON-NLS-1$
+ new Object[] { PluginProject.PLUGIN_XML });
+ VexPlugin.getInstance().log(IStatus.ERROR, message, ex);
+ }
+ }
+ }
+
+ private void setText(Text textBox, String s) {
+ textBox.setText(s == null ? "" : s); //$NON-NLS-1$
+ }
+}
\ No newline at end of file
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/config/DocumentType.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/config/DocumentType.java
new file mode 100644
index 0000000..68dbb06
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/config/DocumentType.java
@@ -0,0 +1,167 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.ui.internal.config;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+import org.eclipse.wst.xml.vex.core.internal.dom.Validator;
+import org.eclipse.wst.xml.vex.ui.internal.editor.VexEditor;
+
+
+/**
+ * A registered document type.
+ */
+public class DocumentType extends ConfigItem implements Comparable {
+
+ public static final String EXTENSION_POINT = "org.eclipse.wst.xml.vex.ui.doctypes"; //$NON-NLS-1$
+
+ public DocumentType(ConfigSource config) {
+ super(config);
+ }
+
+ /**
+ * Return a DocumentType for the given publicId. Returns null if no
+ * document type was found that matches the public ID.
+ * @param publicId Public ID for which to search.
+ */
+ public static DocumentType getDocumentType(String publicId) {
+ ConfigRegistry registry = ConfigRegistry.getInstance();
+ List doctypes = registry.getAllConfigItems(DocumentType.EXTENSION_POINT);
+ for (Iterator it = doctypes.iterator(); it.hasNext(); ) {
+ DocumentType doctype = (DocumentType) it.next();
+ if (doctype.getPublicId().equals(publicId)) {
+ return doctype;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Return a list of document types for which there is at least one
+ * registered style.
+ */
+ public static DocumentType[] getDocumentTypesWithStyles() {
+ // TODO quite inefficent, try caching results, clearing the cache upon config changes.
+ ConfigRegistry registry = ConfigRegistry.getInstance();
+ List withStyles = new ArrayList();
+ List doctypes = registry.getAllConfigItems(DocumentType.EXTENSION_POINT);
+ for (Iterator it = doctypes.iterator(); it.hasNext();) {
+ DocumentType doctype = (DocumentType) it.next();
+ if (VexEditor.findStyleForDoctype(doctype.getPublicId()) != null) {
+ withStyles.add(doctype);
+ }
+ }
+ return (DocumentType[]) withStyles.toArray(new DocumentType[withStyles.size()]);
+ }
+
+
+ /**
+ * Returns the name of the class that generates an outline for this
+ * document type. This class must implement
+ * org.eclipse.ui.views.contentoutline.IContentOutlinePage. Normally,
+ * classes will extend net.sf.vex.editor.AbstractContentOutlinePage.
+ * Returns null if this document type was not supplied by a plugin,
+ * or if the the contentOutlinePage attribute was not set.
+ */
+ public String getOutlineProvider() {
+ return outlineProvider;
+ }
+
+ /**
+ * Returns the public ID of the document type.
+ */
+ public String getPublicId() {
+ return publicId;
+ }
+
+ /**
+ * Returns the system ID of the document type.
+ */
+ public String getSystemId() {
+ return systemId;
+ }
+
+ public String getExtensionPointId() {
+ return EXTENSION_POINT;
+ }
+
+ /**
+ * Sets the name of the class that defines the content outline of the
+ * document.
+ * @param contentOutlinePage Name of a class implementing IContentOutlinePage.
+ */
+ public void setOutlineProvider(String contentOutlinePage) {
+ this.outlineProvider = contentOutlinePage;
+ }
+
+ /**
+ * Sets the public ID of the document type. The public ID is the unique
+ * identifier of the document type.
+ * @param publicId new public ID of the document type.
+ */
+ public void setPublicId(String publicId) {
+ this.publicId = publicId;
+ }
+
+ /**
+ * Sets the system ID of the document type. This is used when creating
+ * new documents but ignored otherwise.
+ * @param systemId new system ID for the document type.
+ */
+ public void setSystemId(String systemId) {
+ this.systemId = systemId;
+ }
+
+ public Validator getValidator() {
+ return (Validator) this.getConfig().getParsedResource(this.getResourcePath());
+ }
+
+ public boolean isValid() {
+ return super.isValid() &&
+ !isBlank(publicId) &&
+ !isBlank(systemId) &&
+ this.getValidator() != null;
+ }
+
+ public String toString() {
+ return this.getName();
+ }
+
+ /**
+ * Returns a list of valid root elements for this document type. If
+ * no root elements have been declared, returns an empty array.
+ */
+ public String[] getRootElements() {
+ return rootElements;
+ }
+
+ /**
+ * Sets the list of valid root elements for this document type.
+ */
+ public void setRootElements(String[] rootElements) {
+ if (rootElements == null) {
+ throw new IllegalArgumentException();
+ }
+ this.rootElements = rootElements;
+ }
+
+ //==================================================== PRIVATE
+
+ private static final String[] EMPTY_STRING_ARRAY = new String[0];
+
+ private String publicId;
+ private String systemId;
+ private String outlineProvider;
+ private String[] rootElements = EMPTY_STRING_ARRAY;
+
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/config/DomConfigurationElement.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/config/DomConfigurationElement.java
new file mode 100644
index 0000000..7f8efff
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/config/DomConfigurationElement.java
@@ -0,0 +1,90 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.ui.internal.config;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+import org.w3c.dom.Text;
+
+/**
+ * Implements IConfigurationElement against a W3C DOM Element object.
+ */
+public class DomConfigurationElement implements IConfigElement {
+
+ public DomConfigurationElement(Element element) {
+ this.element = element;
+ }
+
+ public String getAttribute(String name) {
+ return this.element.getAttribute(name); // TODO translate from resource bundle
+ }
+
+ public String[] getAttributeNames() {
+ int n = this.element.getAttributes().getLength();
+ String[] names = new String[n];
+ for (int i = 0; i < n; i++) {
+ Node node = this.element.getAttributes().item(i);
+ names[i] = node.getLocalName();
+ }
+ return names;
+ }
+
+ public IConfigElement[] getChildren() {
+ return this.getChildren(null);
+ }
+
+ public IConfigElement[] getChildren(String name) {
+ List children = new ArrayList();
+ NodeList list = this.element.getChildNodes();
+ for (int i = 0; i < list.getLength(); i++) {
+ Node node = list.item(i);
+ if (node instanceof Element) {
+ if (name == null || name.equals(node.getNodeName())) {
+ children.add(node);
+ }
+ }
+ }
+
+ int n = children.size();
+ IConfigElement[] childArray = new IConfigElement[n];
+ for (int i = 0; i < n; i++) {
+ childArray[i] = new DomConfigurationElement((Element) children.get(i));
+ }
+
+ return childArray;
+ }
+
+ public String getName() {
+ return element.getLocalName();
+ }
+
+ public String getValue() {
+ StringBuffer sb = new StringBuffer();
+
+ NodeList list = this.element.getChildNodes();
+ for (int i = 0; i < list.getLength(); i++) {
+ Node node = list.item(i);
+ if (node instanceof Text) {
+ sb.append(node.getNodeValue());
+ }
+ }
+ return sb.toString();
+ }
+
+ //===================================================== PRIVATE
+
+ private Element element;
+
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/config/IBuildProblemHandler.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/config/IBuildProblemHandler.java
new file mode 100644
index 0000000..ff7f450
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/config/IBuildProblemHandler.java
@@ -0,0 +1,24 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.ui.internal.config;
+
+/**
+ * Interface through which a resource parser notifies an interested party
+ * of build problems.
+ */
+public interface IBuildProblemHandler {
+
+ /**
+ * Called by the parser when a problem is found while parsing a resource.
+ * @param problem Details of the problem.
+ */
+ public void foundProblem(BuildProblem problem);
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/config/IConfigElement.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/config/IConfigElement.java
new file mode 100644
index 0000000..ba54de2
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/config/IConfigElement.java
@@ -0,0 +1,54 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.ui.internal.config;
+
+
+/**
+ * Represents the XML element for a Vex config item in plugin.xml.
+ * Vex-specific replacement for the Eclipse IConfigurationElement class.
+ * We need this because we are not supposed to implement IConfigurationElement,
+ * and in fact it changed and broke us from Eclipse 3.0 -> 3.1.
+ */
+public interface IConfigElement {
+
+ /**
+ * Returns the value of the given attribute.
+ * @param name Name of the attribute for which to return a name.
+ * @return
+ */
+ public String getAttribute(String name);
+
+ /**
+ * Returns an array of all the attributes defined by this element.
+ */
+ public String[] getAttributeNames();
+
+ /**
+ * Returns an array of the children of this element.
+ */
+ public IConfigElement[] getChildren();
+
+ /**
+ * Returns an array of the children of this element with the given name.
+ * @param name Name of children to search for.
+ */
+ public IConfigElement[] getChildren(String name);
+
+ /**
+ * Returns the name of this element.
+ */
+ public String getName();
+
+ /**
+ * Returns the value of this element.
+ */
+ public String getValue();
+}
\ No newline at end of file
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/config/IConfigItemFactory.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/config/IConfigItemFactory.java
new file mode 100644
index 0000000..d384db2
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/config/IConfigItemFactory.java
@@ -0,0 +1,75 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.ui.internal.config;
+
+import java.io.IOException;
+import java.net.URL;
+
+/**
+ * Manager of a set of configuration items and their associated resources.
+ * One concrete implementation of this interface will exist for each kind
+ * of configuration item, e.g. a DoctypeManager, StyleManager, etc.
+ * Further, each Vex plugin and Vex plugin project will have one of
+ * each kind of manager. All managers are registered with the VexPlugin
+ * class.
+ */
+public interface IConfigItemFactory {
+
+ /**
+ * Returns an array of configuration elements needed to re-create the given
+ * item. If no configuration elements are necessary, the method may return
+ * null instead of an empty array. This is essentially the inverse of
+ * the createItem method.
+ *
+ * @param item ConfigItem for which to create configuration elements.
+ */
+ public IConfigElement[] createConfigurationElements(ConfigItem item);
+
+ /**
+ * Creates an item and adds it to the given configuration.
+ * @param config Configuration that owns the item.
+ * @param configElements Details of the configuration item from the
+ * plugin manifest.
+ */
+ public ConfigItem createItem(ConfigSource config, IConfigElement[] configElements) throws IOException;
+
+ /**
+ * Returns the ID of the extension point that defines this type
+ * of configuration item.
+ */
+ public String getExtensionPointId();
+
+ /**
+ * Returns an array of file extension for resources that apply to this type
+ * of configuration item. The returned strings should <i>not</i> have
+ * leading dots.
+ */
+ public String[] getFileExtensions();
+
+ /**
+ * Returns the pluralized name of the type of configuration item managed
+ * by this factory. For example, "Document Types".
+ * @return
+ */
+ public String getPluralName();
+
+ /**
+ * Parse a resource for this type of configuration item. Implementations
+ * must not fail if passed a null problem handler.
+ *
+ * @param baseUrl Base URL of the project or plugin containing the resource.
+ * @param resourcePath Path of the resource relative to the base URL.
+ * @param problemHandler Problem handler, or null if the caller does
+ * not require build problem reporting.
+ */
+ public Object parseResource(URL baseUrl, String resourcePath, IBuildProblemHandler problemHandler) throws IOException;
+
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/config/IConfigListener.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/config/IConfigListener.java
new file mode 100644
index 0000000..42ecab4
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/config/IConfigListener.java
@@ -0,0 +1,38 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.ui.internal.config;
+
+import java.util.EventListener;
+
+/**
+ * Interface through which Vex notifies UI components that configuration
+ * items such as doctypes and styles have been added, removed, or changed.
+ * Implementations of this
+ * interface should be registered with the VexPlugin instance. All calls to
+ * implementations occur on the UI thread.
+ */
+public interface IConfigListener extends EventListener {
+
+ /**
+ * Called when one or more configuration items are added, removed, or
+ * changed.
+ * @param e ConfigEvent containing details of the change.
+ */
+ public void configChanged(ConfigEvent e);
+
+ /**
+ * Called when the Vex configuration is first loaded by the ConfigLoaderJob.
+ * This method is guaranteed to be called before the first call to
+ * configChanged.
+ * @param e ConfigEvent containing details of the change.
+ */
+ public void configLoaded(ConfigEvent e);
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/config/Messages.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/config/Messages.java
new file mode 100644
index 0000000..80e8c34
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/config/Messages.java
@@ -0,0 +1,50 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.ui.internal.config;
+
+import java.text.MessageFormat;
+import java.util.MissingResourceException;
+import java.util.ResourceBundle;
+
+
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.wst.xml.vex.ui.internal.VexPlugin;
+
+/**
+ * Handler for language-specific strings in Vex.
+ */
+public class Messages {
+
+ private static ResourceBundle resources;
+
+ private Messages() {
+ }
+
+ /**
+ * Returns the language-specific string for the given key,
+ * or the key itself if not found.
+ */
+ public static String getString(String key) {
+ if (resources == null) {
+ resources = ResourceBundle.getBundle("org.eclipse.wst.xml.vex.ui.internal.config.messages"); //$NON-NLS-1$
+ }
+
+ try {
+ return resources.getString(key);
+ } catch (MissingResourceException ex) {
+ String message = Messages.getString("Messages.cantFindResource"); //$NON-NLS-1$
+ VexPlugin.getInstance().log(IStatus.WARNING,
+ MessageFormat.format(message, new Object[] { key }));
+ return key;
+ }
+ }
+
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/config/NewPluginProjectWizard.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/config/NewPluginProjectWizard.java
new file mode 100644
index 0000000..6d45c1d
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/config/NewPluginProjectWizard.java
@@ -0,0 +1,95 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.ui.internal.config;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.PrintStream;
+
+
+import org.eclipse.core.resources.IFile;
+import org.eclipse.core.resources.IProject;
+import org.eclipse.core.resources.IProjectDescription;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.jface.viewers.IStructuredSelection;
+import org.eclipse.ui.IWorkbench;
+import org.eclipse.ui.ide.IDE;
+import org.eclipse.ui.wizards.newresource.BasicNewProjectResourceWizard;
+import org.eclipse.wst.xml.vex.ui.internal.VexPlugin;
+
+/**
+ * Wizard for creating a new Vex Plugin Project.
+ */
+public class NewPluginProjectWizard extends BasicNewProjectResourceWizard {
+
+ public void init(IWorkbench workbench, IStructuredSelection currentSelection) {
+ super.init(workbench, currentSelection);
+
+ this.setWindowTitle(Messages.getString("NewPluginProjectWizard.title")); //$NON-NLS-1$
+ }
+
+ public boolean performFinish() {
+
+ boolean success = super.performFinish();
+ if (success) {
+ try {
+ this.createVexPluginXml();
+ this.registerVexPluginNature();
+ PluginProject.load(this.getNewProject());
+ } catch (CoreException e) {
+ VexPlugin.getInstance().log(IStatus.ERROR, Messages.getString("NewPluginProjectWizard.createError"), e); //$NON-NLS-1$
+ success = false;
+ }
+
+
+ }
+
+ return success;
+ }
+
+ //====================================================== PRIVATE
+
+ private void createVexPluginXml() throws CoreException {
+
+ IProject project = this.getNewProject();
+
+ ByteArrayOutputStream baos;
+ PrintStream out;
+
+ baos = new ByteArrayOutputStream();
+ out = new PrintStream(baos);
+
+ out.println("<?xml version='1.0'?>"); //$NON-NLS-1$
+ out.println("<plugin>"); //$NON-NLS-1$
+ out.println("</plugin>"); //$NON-NLS-1$
+ out.close();
+
+ IFile pluginXml = project.getFile(PluginProject.PLUGIN_XML);
+ pluginXml.create(new ByteArrayInputStream(baos.toByteArray()), true, null);
+
+ // By default open the Default Text Editor for vex-plugin.xml.
+ // This isn't perfect, because the Vex icon is still shown, but
+ // it'll do until we create a custom editor.
+ IDE.setDefaultEditor(pluginXml, "org.eclipse.ui.DefaultTextEditor"); //$NON-NLS-1$
+ }
+
+ private void registerVexPluginNature() throws CoreException {
+ IProject project = this.getNewProject();
+ IProjectDescription description = project.getDescription();
+ String[] natures = description.getNatureIds();
+ String[] newNatures = new String[natures.length + 1];
+ System.arraycopy(natures, 0, newNatures, 0, natures.length);
+ newNatures[natures.length] = PluginProjectNature.ID;
+ description.setNatureIds(newNatures);
+ project.setDescription(description, null);
+ }
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/config/PluginProject.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/config/PluginProject.java
new file mode 100644
index 0000000..d91c653
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/config/PluginProject.java
@@ -0,0 +1,322 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.ui.internal.config;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.io.OutputStreamWriter;
+import java.io.PrintWriter;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.text.MessageFormat;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.FactoryConfigurationError;
+import javax.xml.parsers.ParserConfigurationException;
+
+
+import org.eclipse.core.resources.IFile;
+import org.eclipse.core.resources.IProject;
+import org.eclipse.core.resources.IncrementalProjectBuilder;
+import org.eclipse.core.resources.ResourcesPlugin;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.wst.xml.vex.core.internal.dom.DocumentWriter;
+import org.eclipse.wst.xml.vex.ui.internal.VexPlugin;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+import org.xml.sax.SAXException;
+
+/**
+ * Represents a Vex plugin project.
+ */
+public class PluginProject extends ConfigSource {
+
+ public static final String PLUGIN_XML = "vex-plugin.xml"; //$NON-NLS-1$
+ public static final String PROJECT_CONFIG_SER = ".vexConfig.ser"; //$NON-NLS-1$
+
+ /**
+ * Class constructor.
+ * @param config VexConfiguration associated with this project.
+ */
+ protected PluginProject(IProject project) {
+ this.projectPath = project.getFullPath().toString();
+ }
+
+ /**
+ * Remove the .vexConfig.ser state in which the project state is stored.
+ */
+ public void cleanState() throws CoreException {
+ IFile configSer = this.getProject().getFile(PROJECT_CONFIG_SER);
+ configSer.delete(true, null);
+ }
+
+ /**
+ * Factory method that returns the plugin project for the given
+ * IProject. If the given project does not have the Vex plugin
+ * project nature, null is returned. PluginProject instances are
+ * cached so they can be efficiently returned.
+ *
+ * @param project IProject for which to return the PluginProject.
+ * @return
+ * @throws CoreException
+ */
+ public static PluginProject get(IProject project) {
+
+ List sources = ConfigRegistry.getInstance().getAllConfigSources();
+ for (Iterator it = sources.iterator(); it.hasNext();) {
+ ConfigSource source = (ConfigSource) it.next();
+ if (source instanceof PluginProject) {
+ if (project.equals(((PluginProject) source).getProject())) {
+ return (PluginProject) source;
+ }
+ }
+ }
+ return null;
+ }
+
+ public URL getBaseUrl() {
+ try {
+ return this.getProject().getLocation().toFile().toURL();
+ } catch (MalformedURLException e) {
+ throw new RuntimeException(Messages.getString("PluginProject.malformedUrl"), e); //$NON-NLS-1$
+ }
+ }
+ /**
+ * Returns the IProject associated with this plugin project.
+ */
+ public IProject getProject() {
+ return ResourcesPlugin.getWorkspace().getRoot().getProject(this.projectPath);
+ }
+
+ /**
+ * Loads the project from it's serialized state file and registers it with
+ * the ConfigRegistry. If the serialized state cannot be loaded, a new
+ * PluginProject is created and the builder is launched.
+ */
+ public static PluginProject load(IProject project) {
+
+ try {
+ if (!project.isOpen() || !project.hasNature(PluginProjectNature.ID)) {
+ String message = MessageFormat.format(
+ Messages.getString("PluginProject.notPluginProject"), //$NON-NLS-1$
+ new Object[] { project.getName() });
+ throw new IllegalArgumentException(message);
+ }
+ } catch (CoreException e) {
+ String message = MessageFormat.format(
+ Messages.getString("PluginProject.notPluginProject"), //$NON-NLS-1$
+ new Object[] { project.getName() });
+ throw new IllegalArgumentException(message);
+ }
+
+ IFile serFile = project.getFile(PROJECT_CONFIG_SER);
+
+ PluginProject pluginProject = null;
+ if (serFile.exists()) {
+ try {
+ ObjectInputStream ois = new ObjectInputStream(serFile.getContents());
+ pluginProject = (PluginProject) ois.readObject();
+ } catch (Exception ex) {
+ String message = MessageFormat.format(
+ Messages.getString("PluginProject.loadingError"), //$NON-NLS-1$
+ new Object[] { serFile });
+ VexPlugin.getInstance().log(IStatus.WARNING, message, ex);
+ }
+ }
+
+ boolean rebuild = false;
+
+ if (pluginProject == null) {
+ rebuild = true;
+ pluginProject = new PluginProject(project);
+ }
+
+ ConfigRegistry registry = ConfigRegistry.getInstance();
+ registry.addConfigSource(pluginProject);
+ registry.fireConfigChanged(new ConfigEvent(PluginProject.class));
+
+ if (rebuild) {
+ try {
+ project.build(IncrementalProjectBuilder.FULL_BUILD, null);
+
+ } catch (Exception ex) {
+ String message = MessageFormat.format(
+ Messages.getString("PluginProject.buildError"), //$NON-NLS-1$
+ new Object[] { project.getName() });
+ VexPlugin.getInstance().log(IStatus.ERROR, message, ex);
+ }
+ }
+
+ return pluginProject;
+ }
+
+ /**
+ * Re-parses the vex-plugin.xml file.
+ */
+ public void parseConfigXml() throws SAXException, IOException {
+
+ DocumentBuilder builder;
+ try {
+ builder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
+ } catch (ParserConfigurationException e) {
+ throw new RuntimeException(e);
+ } catch (FactoryConfigurationError e) {
+ throw new RuntimeException(e);
+ }
+
+ this.removeAllItems();
+
+ URL url = new URL(this.getBaseUrl(), PluginProject.PLUGIN_XML);
+ Document doc = builder.parse(url.toString());
+
+ Element root = doc.getDocumentElement();
+
+ this.setUniqueIdentifer(root.getAttribute("id")); //$NON-NLS-1$
+
+ NodeList nodeList = doc.getElementsByTagName("extension"); //$NON-NLS-1$
+
+ for (int i = 0; i < nodeList.getLength(); i++) {
+ Element element = (Element) nodeList.item(i);
+ String extPoint = element.getAttribute("point"); //$NON-NLS-1$
+ String id = element.getAttribute("id"); //$NON-NLS-1$
+ String name = element.getAttribute("name"); //$NON-NLS-1$
+
+ List configElementList = new ArrayList();
+ NodeList childList = element.getChildNodes();
+ for (int j = 0; j < childList.getLength(); j++) {
+ Node child = childList.item(j);
+ if (child instanceof Element) {
+ configElementList.add(child);
+ }
+ }
+
+ IConfigElement[] configElements = new IConfigElement[configElementList.size()];
+ for (int j = 0; j < configElementList.size(); j++) {
+ configElements[j] = new DomConfigurationElement((Element) configElementList.get(j));
+ }
+
+ this.addItem(extPoint, id, name, configElements);
+ }
+
+ }
+
+ /**
+ * Saves the state of this project into .vexConfig.ser.
+ */
+ public void saveState() throws CoreException, IOException {
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ ObjectOutputStream oos = new ObjectOutputStream(baos);
+ oos.writeObject(this);
+ ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
+ IFile configSer = this.getProject().getFile(PROJECT_CONFIG_SER);
+ if (configSer.exists()) {
+ configSer.setContents(bais, true, false, null);
+ } else {
+ configSer.create(bais, true, null);
+ configSer.setDerived(true);
+ }
+
+ }
+
+ /**
+ * Writes this configuraton to the file vex-config.xml in the project.
+ */
+ public void writeConfigXml() throws CoreException, IOException {
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ PrintWriter out = new PrintWriter(new OutputStreamWriter(baos, "utf-8")); //$NON-NLS-1$
+
+ ConfigurationElement root = new ConfigurationElement("plugin"); //$NON-NLS-1$
+ for (Iterator it = this.getAllItems().iterator(); it.hasNext();) {
+ ConfigItem item = (ConfigItem) it.next();
+ ConfigurationElement extElement = new ConfigurationElement("extension"); //$NON-NLS-1$
+ extElement.setAttribute("id", item.getSimpleId()); //$NON-NLS-1$
+ extElement.setAttribute("name", item.getName()); //$NON-NLS-1$
+ extElement.setAttribute("point", item.getExtensionPointId()); //$NON-NLS-1$
+ IConfigItemFactory factory = ConfigRegistry.getInstance().getConfigItemFactory(item.getExtensionPointId());
+ extElement.setChildren(factory.createConfigurationElements(item));
+ root.addChild(extElement);
+ }
+ writeElement(root, out, 0);
+
+ out.close();
+
+ InputStream inputStream = new ByteArrayInputStream(baos.toByteArray());
+
+ IFile file = this.getProject().getFile(PLUGIN_XML);
+ if (file.exists()) {
+ file.setContents(inputStream, true, false, null);
+ } else {
+ file.create(inputStream, true, null);
+ }
+ }
+
+ //=========================================================== PRIVATE
+
+ private String projectPath;
+ /** Filename used when serializing in a Vex plugin project */
+ public static final String SER_FILE = ".vexConfig.ser"; //$NON-NLS-1$
+
+ private static void writeElement(IConfigElement element, PrintWriter out, int level) {
+ StringBuffer elementIndent = new StringBuffer();
+ for (int i = 0; i < level; i++) {
+ elementIndent.append(" "); //$NON-NLS-1$
+ }
+ StringBuffer elementPrefix = new StringBuffer();
+ elementPrefix.append("<"); //$NON-NLS-1$
+ elementPrefix.append(element.getName());
+
+ StringBuffer attributeIndent = new StringBuffer(elementIndent.toString());
+ for (int i = 0; i < elementPrefix.length() + 1; i++) {
+ attributeIndent.append(" "); //$NON-NLS-1$
+ }
+
+ out.print(elementIndent.toString());
+ out.print(elementPrefix.toString());
+ String[] attributeNames = element.getAttributeNames();
+ for (int i = 0; i < attributeNames.length; i++) {
+ String attributeName = attributeNames[i];
+ if (i > 0) {
+ out.println();
+ out.print(attributeIndent);
+ } else {
+ out.print(" "); //$NON-NLS-1$
+ }
+
+ out.print(attributeName);
+ out.print("=\""); //$NON-NLS-1$
+ out.print(DocumentWriter.escape(element.getAttribute(attributeName)));
+ out.print("\""); //$NON-NLS-1$
+ }
+ out.println(">"); //$NON-NLS-1$
+
+ IConfigElement[] children = element.getChildren();
+ for (int i = 0; i < children.length; i++) {
+ writeElement(children[i], out, level+1);
+ }
+
+ out.print(elementIndent);
+ out.print("</"); //$NON-NLS-1$
+ out.print(element.getName());
+ out.println(">"); //$NON-NLS-1$
+ }
+
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/config/PluginProjectBuilder.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/config/PluginProjectBuilder.java
new file mode 100644
index 0000000..bba01a3
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/config/PluginProjectBuilder.java
@@ -0,0 +1,248 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.ui.internal.config;
+
+import java.io.IOException;
+import java.text.MessageFormat;
+import java.util.Map;
+
+
+import org.eclipse.core.resources.IMarker;
+import org.eclipse.core.resources.IProject;
+import org.eclipse.core.resources.IResource;
+import org.eclipse.core.resources.IResourceDelta;
+import org.eclipse.core.resources.IResourceDeltaVisitor;
+import org.eclipse.core.resources.IncrementalProjectBuilder;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.core.runtime.Path;
+import org.eclipse.ui.IDecoratorManager;
+import org.eclipse.ui.PlatformUI;
+import org.eclipse.wst.xml.vex.ui.internal.VexPlugin;
+import org.xml.sax.SAXParseException;
+
+/**
+ * Parses and registers Vex configuration objects in a Vex Plug-in project.
+ */
+public class PluginProjectBuilder extends IncrementalProjectBuilder {
+
+ public static final String ID = "org.eclipse.wst.xml.vex.ui.pluginBuilder"; //$NON-NLS-1$
+
+ public PluginProjectBuilder() {
+ }
+
+ protected IProject[] build(int kind, Map args, IProgressMonitor monitor)
+ throws CoreException {
+
+ IProject project = this.getProject();
+
+ final PluginProject pluginProject = PluginProject.get(project);
+
+ if (pluginProject == null) {
+ String message = MessageFormat.format(
+ Messages.getString("PluginProjectBuilder.notConfigSource"), //$NON-NLS-1$
+ new Object[] { project.getName() });
+ VexPlugin.getInstance().log(IStatus.ERROR, message);
+ return null;
+ }
+
+ boolean parseConfigXml;
+
+ IResourceDelta delta = this.getDelta(project);
+
+ if (kind == FULL_BUILD || delta == null) {
+
+ //System.out.println("PluginProjectBuilder.build (full) starts for project " + project.getName());
+
+ this.clean(null);
+ parseConfigXml = true;
+
+ } else { // incremental or auto build
+
+ //System.out.println("PluginProjectBuilder.build (incremental) starts for project " + project.getName());
+
+ parseConfigXml = (delta.findMember(new Path(PluginProject.PLUGIN_XML)) != null);
+
+ // If a resource is deleted, renamed, or moved, we'll update the
+ // config, but only if we're not going to parse it.
+ final boolean canUpdateConfig = !parseConfigXml;
+
+ IResourceDeltaVisitor visitor = new IResourceDeltaVisitor() {
+ public boolean visit(IResourceDelta delta) throws CoreException {
+ IResource resource = delta.getResource();
+ String path = resource.getProjectRelativePath().toString();
+ pluginProject.removeResource(path);
+
+ if (delta.getKind() == IResourceDelta.REMOVED) {
+
+ ConfigItem item = pluginProject.getItemForResource(path);
+
+ if (item == null) {
+ return true;
+ }
+
+ if (canUpdateConfig && (delta.getFlags() & IResourceDelta.MOVED_TO) > 0) {
+ // Resource was moved.
+ String newPath = delta.getMovedToPath().removeFirstSegments(1).toString();
+ item.setResourcePath(newPath);
+ } else {
+ // Resource deleted, so let's nuke the item from the config
+ pluginProject.remove(item);
+ }
+
+ try {
+ pluginProject.writeConfigXml();
+ } catch (Exception ex) {
+ String message = MessageFormat.format(
+ Messages.getString("PluginProjectBuilder.cantSaveFile"), //$NON-NLS-1$
+ new Object[] { PluginProject.PLUGIN_XML });
+
+ VexPlugin.getInstance().log(IStatus.ERROR, message, ex);
+ }
+
+ try {
+ // If auto-build is on this is unnecessary since
+ // another build will be triggered by us saving
+ // vex-plugin.xml above. This is just here in case
+ // we're not auto-building
+ pluginProject.saveState();
+ } catch (Exception ex) {
+ String message = MessageFormat.format(
+ Messages.getString("PluginProjectBuilder.cantSaveFile"), //$NON-NLS-1$
+ new Object[] { PluginProject.PROJECT_CONFIG_SER });
+ VexPlugin.getInstance().log(IStatus.WARNING, message, ex);
+ }
+ }
+
+ return true;
+ }
+ };
+
+ delta.accept(visitor);
+ }
+
+ IMarker[] oldMarkers = project.findMarkers(IMarker.PROBLEM, true, IResource.DEPTH_INFINITE);
+ IResource[] markedResources = new IResource[oldMarkers.length];
+ for (int i = 0; i < markedResources.length; i++) {
+ markedResources[i] = oldMarkers[i].getResource();
+ }
+
+ project.deleteMarkers(IMarker.PROBLEM, true, IResource.DEPTH_INFINITE);
+ this.getBuildProblemDecorator().update(markedResources);
+
+ ConfigRegistry registry = ConfigRegistry.getInstance();
+
+ try {
+ registry.lock();
+
+ if (parseConfigXml) {
+ IResource pluginXmlResource = this.getProject().getFile(PluginProject.PLUGIN_XML);
+ try {
+ if (pluginXmlResource.exists()) {
+ pluginProject.parseConfigXml();
+ } else {
+ pluginProject.removeAllItems();
+ String message = MessageFormat.format(
+ Messages.getString("PluginProjectBuilder.missingFile"), //$NON-NLS-1$
+ new Object[] { PluginProject.PLUGIN_XML });
+ this.flagError(this.getProject(), message);
+ }
+ } catch (SAXParseException ex) {
+ this.flagError(pluginXmlResource, ex.getLocalizedMessage(), ex.getLineNumber());
+ } catch (Exception ex) {
+ String message = MessageFormat.format(
+ Messages.getString("PluginProjectBuilder.parseError"), //$NON-NLS-1$
+ new Object[] { PluginProject.PLUGIN_XML });
+ VexPlugin.getInstance().log(IStatus.ERROR, message, ex); //$NON-NLS-1$
+ this.flagError(pluginXmlResource, ex.getLocalizedMessage());
+ }
+ }
+
+ IBuildProblemHandler problemHandler = new IBuildProblemHandler() {
+ public void foundProblem(BuildProblem problem) {
+ try {
+ IResource resource = getProject().getFile(problem.getResourcePath());
+ flagError(resource, problem.getMessage(), problem.getLineNumber());
+ } catch (CoreException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+ }
+ };
+
+ pluginProject.parseResources(problemHandler);
+
+ // Write new config to SER file.
+ try {
+ pluginProject.saveState();
+ } catch (IOException ex) {
+ String message = MessageFormat.format(
+ Messages.getString("PluginProjectBuilder.cantSaveConfig"), //$NON-NLS-1$
+ new Object[] { project.getName() });
+ VexPlugin.getInstance().log(IStatus.WARNING, message, ex);
+ }
+ } finally {
+ registry.unlock();
+ }
+
+ registry.fireConfigChanged(new ConfigEvent(this));
+
+ return null;
+ }
+
+
+ protected void clean(IProgressMonitor monitor) throws CoreException {
+ ConfigRegistry registry = ConfigRegistry.getInstance();
+ try {
+ registry.lock();
+ PluginProject pluginProject = PluginProject.get(this.getProject());
+ if (pluginProject != null) {
+ pluginProject.cleanState();
+ pluginProject.removeAllItems();
+ pluginProject.removeAllResources();
+ registry.fireConfigChanged(new ConfigEvent(this));
+ }
+ } finally {
+ registry.unlock();
+ }
+ }
+
+ //======================================================== PRIVATE
+
+ private BuildProblemDecorator buildProblemDecorator;
+
+ private void flagError(IResource resource, String message) throws CoreException {
+ flagError(resource, message, -1);
+ }
+
+ private void flagError(IResource resource, String message, int lineNumber) throws CoreException {
+ IMarker marker = resource.createMarker(IMarker.PROBLEM);
+ if (marker.exists()) {
+ marker.setAttribute(IMarker.MESSAGE, message);
+ marker.setAttribute(IMarker.PRIORITY, IMarker.PRIORITY_HIGH);
+ marker.setAttribute(IMarker.SEVERITY, IMarker.SEVERITY_ERROR);
+ if (lineNumber > 0) {
+ marker.setAttribute(IMarker.LINE_NUMBER, lineNumber);
+ }
+ this.getBuildProblemDecorator().update(resource);
+ }
+ }
+
+ private BuildProblemDecorator getBuildProblemDecorator() {
+ if (this.buildProblemDecorator == null) {
+ IDecoratorManager dm = PlatformUI.getWorkbench().getDecoratorManager();
+ this.buildProblemDecorator = (BuildProblemDecorator) dm.getBaseLabelProvider(BuildProblemDecorator.ID);
+ }
+ return this.buildProblemDecorator;
+ }
+
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/config/PluginProjectDecorator.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/config/PluginProjectDecorator.java
new file mode 100644
index 0000000..f570745
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/config/PluginProjectDecorator.java
@@ -0,0 +1,73 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.ui.internal.config;
+
+import java.net.URL;
+
+
+import org.eclipse.core.resources.IProject;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.FileLocator;
+import org.eclipse.core.runtime.Path;
+import org.eclipse.jface.resource.ImageDescriptor;
+import org.eclipse.jface.viewers.IDecoration;
+import org.eclipse.jface.viewers.ILabelProviderListener;
+import org.eclipse.jface.viewers.ILightweightLabelDecorator;
+import org.eclipse.wst.xml.vex.ui.internal.VexPlugin;
+
+/**
+ * Decorates Vex projects with the Vex logo.
+ */
+public class PluginProjectDecorator implements ILightweightLabelDecorator {
+
+ public void decorate(Object element, IDecoration decoration) {
+
+ if (this.vexIcon == null) {
+ this.loadImageDescriptors();
+ }
+
+ if (element instanceof IProject) {
+ try {
+ IProject project = (IProject) element;
+ if (project.hasNature(PluginProjectNature.ID)) {
+ decoration.addOverlay(this.vexIcon, IDecoration.TOP_RIGHT);
+ }
+ } catch (CoreException e) {
+ }
+ }
+ }
+
+ public void addListener(ILabelProviderListener listener) {
+ }
+
+ public void dispose() {
+ }
+
+ public boolean isLabelProperty(Object element, String property) {
+ return false;
+ }
+
+ public void removeListener(ILabelProviderListener listener) {
+ }
+
+ //======================================================== PRIVATE
+
+ private ImageDescriptor vexIcon;
+
+ private void loadImageDescriptors() {
+ URL url = FileLocator.find(
+ VexPlugin.getInstance().getBundle(),
+ new Path("icons/vex8.gif"), //$NON-NLS-1$
+ null);
+ this.vexIcon = ImageDescriptor.createFromURL(url);
+ }
+
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/config/PluginProjectNature.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/config/PluginProjectNature.java
new file mode 100644
index 0000000..73abadc
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/config/PluginProjectNature.java
@@ -0,0 +1,75 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.ui.internal.config;
+
+import org.eclipse.core.resources.ICommand;
+import org.eclipse.core.resources.IMarker;
+import org.eclipse.core.resources.IProject;
+import org.eclipse.core.resources.IProjectDescription;
+import org.eclipse.core.resources.IProjectNature;
+import org.eclipse.core.runtime.CoreException;
+
+/**
+ * Project nature that defines Vex Plugin projects.
+ */
+public class PluginProjectNature implements IProjectNature {
+
+ public static final String ID = "org.eclipse.wst.xml.vex.ui.pluginNature"; //$NON-NLS-1$
+
+ public void configure() throws CoreException {
+ this.registerBuilder();
+ }
+
+ public void deconfigure() throws CoreException {
+ //System.out.println("deconfiguring " + project.getName());
+ project.deleteMarkers(IMarker.PROBLEM, true, 1);
+ }
+
+ public IProject getProject() {
+ return this.project;
+ }
+
+ public void setProject(IProject project) {
+ this.project = project;
+ }
+
+ //====================================================== PRIVATE
+
+ private IProject project;
+
+
+ private void registerBuilder() throws CoreException {
+ IProjectDescription desc = project.getDescription();
+ ICommand[] commands = desc.getBuildSpec();
+ boolean found = false;
+
+ for (int i = 0; i < commands.length; ++i) {
+ if (commands[i].getBuilderName().equals(PluginProjectBuilder.ID)) {
+ found = true;
+ break;
+ }
+ }
+ if (!found) {
+ //add builder to project
+ ICommand command = desc.newCommand();
+ command.setBuilderName(PluginProjectBuilder.ID);
+ ICommand[] newCommands = new ICommand[commands.length + 1];
+
+ // Add it before other builders.
+ System.arraycopy(commands, 0, newCommands, 1, commands.length);
+ newCommands[0] = command;
+ desc.setBuildSpec(newCommands);
+ project.setDescription(desc, null);
+ }
+
+
+ }
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/config/Style.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/config/Style.java
new file mode 100644
index 0000000..2841c95
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/config/Style.java
@@ -0,0 +1,125 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.ui.internal.config;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+
+import org.eclipse.wst.xml.vex.core.internal.css.StyleSheet;
+import org.eclipse.wst.xml.vex.core.internal.layout.BoxFactory;
+
+
+/**
+ * Represents the combination of a style sheet and a box factory that defines
+ * the styling of an XML document during editing.
+ */
+public class Style extends ConfigItem {
+
+ public static final String EXTENSION_POINT = "org.eclipse.wst.xml.vex.ui.styles"; //$NON-NLS-1$
+
+ public Style(ConfigSource config) {
+ super(config);
+ }
+
+ /**
+ * Adds the public ID of a document type to which the style applies.
+ * @param publicId public ID of the document type
+ */
+ public void addDocumentType(String publicId) {
+ this.publicIds.add(publicId);
+ }
+
+ /**
+ * Returns true if this style applies to the documents with the given type.
+ * @param publicId public ID of the document type being sought
+ */
+ public boolean appliesTo(String publicId) {
+ return this.publicIds.contains(publicId);
+ }
+
+ /**
+ * Returns the box factory used to generate boxes for document elements.
+ */
+ public BoxFactory getBoxFactory() {
+ return boxFactory;
+ }
+
+ /**
+ * Returns a set of public IDs of all document types supported by
+ * this style.
+ */
+ public Set getDocumentTypes() {
+ return Collections.unmodifiableSet(this.publicIds);
+ }
+
+ /**
+ * Returns an array of all styles applicable to the given public Id.
+ * @param publicId Public ID for which to find styles.
+ */
+ public static Style[] getStylesForDoctype(String publicId) {
+ ConfigRegistry registry = ConfigRegistry.getInstance();
+ List styles = new ArrayList();
+ List allStyles = registry.getAllConfigItems(Style.EXTENSION_POINT);
+ for (Iterator it = allStyles.iterator(); it.hasNext(); ) {
+ Style style = (Style) it.next();
+ if (style.appliesTo(publicId)) {
+ styles.add(style);
+ }
+ }
+ return (Style[]) styles.toArray(new Style[styles.size()]);
+ }
+
+ /**
+ * Returns the style sheet from which element styles are taken.
+ */
+ public StyleSheet getStyleSheet() {
+ return (StyleSheet) this.getConfig().getParsedResource(this.getResourcePath());
+ }
+
+ public String getExtensionPointId() {
+ return EXTENSION_POINT;
+ }
+
+ /**
+ * Disassociates this style from all document types.
+ */
+ public void removeAllDocumentTypes() {
+ this.publicIds.clear();
+ }
+
+ /**
+ * Removes the public ID of a document type to which the style
+ * no longer applies.
+ * @param publicId public ID of the document type
+ */
+ public void removeDocumentType(String publicId) {
+ this.publicIds.remove(publicId);
+ }
+
+ /**
+ * Sets the box factory used to generate boxes for document elements.
+ * @param factory the new box factory.
+ */
+ public void setBoxFactory(BoxFactory factory) {
+ boxFactory = factory;
+ }
+
+ //===================================================== PRIVATE
+
+
+ private BoxFactory boxFactory;
+ private Set publicIds = new HashSet();
+
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/config/StyleFactory.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/config/StyleFactory.java
new file mode 100644
index 0000000..422c48d
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/config/StyleFactory.java
@@ -0,0 +1,118 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.ui.internal.config;
+
+import java.io.IOException;
+import java.net.URL;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+
+
+import org.eclipse.wst.xml.vex.core.internal.css.StyleSheetReader;
+import org.w3c.css.sac.CSSParseException;
+
+/**
+ * Factory for style objects.
+ */
+public class StyleFactory implements IConfigItemFactory {
+
+ /**
+ * Returns all styles for a particular doctype.
+ * @param publicId Public ID of the desired doctype.
+ * @return List of Style objects.
+ */
+ public static List getStylesForDoctype(String publicId) {
+// List result = new ArrayList();
+// Iterator it = this.getAll().iterator();
+// while (it.hasNext()) {
+// Style style = (Style) it.next();
+// if (style.getDocumentTypes().contains(publicId)) {
+// result.add(style);
+// }
+// }
+//
+// return result;
+ return null;
+ }
+
+ public IConfigElement[] createConfigurationElements(ConfigItem item) {
+ Style style = (Style) item;
+ ConfigurationElement element = new ConfigurationElement("style"); //$NON-NLS-1$
+ element.setAttribute("css", style.getResourcePath()); //$NON-NLS-1$
+ Set doctypes = style.getDocumentTypes();
+ for (Iterator it = doctypes.iterator(); it.hasNext();) {
+ String publicId = (String) it.next();
+ ConfigurationElement child = new ConfigurationElement("doctypeRef"); //$NON-NLS-1$
+ child.setAttribute("publicId", publicId); //$NON-NLS-1$
+ element.addChild(child);
+ }
+ return new IConfigElement[] { element };
+ }
+
+
+
+ public ConfigItem createItem(ConfigSource config, IConfigElement[] configElements) throws IOException {
+
+ if (configElements.length < 1) {
+ return null;
+ }
+ IConfigElement configElement = configElements[0];
+
+ Style style = new Style(config);
+ style.setResourcePath(configElement.getAttribute("css")); //$NON-NLS-1$
+
+ IConfigElement[] doctypeRefs = configElement.getChildren();
+
+ for (int j = 0; j < doctypeRefs.length; j++) {
+ style.addDocumentType(doctypeRefs[j].getAttribute("publicId")); //$NON-NLS-1$
+ }
+
+ return style;
+ }
+
+
+ public String getExtensionPointId() {
+ return Style.EXTENSION_POINT;
+ }
+
+
+ public String[] getFileExtensions() {
+ return EXTS;
+ }
+
+
+ public String getPluralName() {
+ return Messages.getString("StyleFactory.pluralName"); //$NON-NLS-1$
+ }
+
+ public Object parseResource(URL baseUrl, String resourcePath, IBuildProblemHandler problemHandler) throws IOException {
+ try {
+ return new StyleSheetReader().read(new URL(baseUrl, resourcePath));
+ } catch (CSSParseException ex) {
+ if (problemHandler != null) {
+ BuildProblem problem = new BuildProblem();
+ problem.setSeverity(BuildProblem.SEVERITY_ERROR);
+ problem.setResourcePath(ex.getURI());
+ problem.setMessage(ex.getMessage());
+ problem.setLineNumber(ex.getLineNumber());
+ problemHandler.foundProblem(problem);
+ }
+ throw ex;
+
+ }
+ }
+
+ //=================================================== PRIVATE
+
+ private static final String[] EXTS = new String[] { "css" }; //$NON-NLS-1$
+
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/config/StylePropertyPage.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/config/StylePropertyPage.java
new file mode 100644
index 0000000..71fd9af
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/config/StylePropertyPage.java
@@ -0,0 +1,249 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.ui.internal.config;
+
+import java.text.MessageFormat;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+import java.util.TreeSet;
+
+
+import org.eclipse.core.resources.IFile;
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.layout.FillLayout;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.swt.widgets.Label;
+import org.eclipse.swt.widgets.Table;
+import org.eclipse.swt.widgets.TableItem;
+import org.eclipse.swt.widgets.Text;
+import org.eclipse.ui.dialogs.PropertyPage;
+import org.eclipse.wst.xml.vex.ui.internal.VexPlugin;
+
+/**
+ * Property page for .css files.
+ */
+public class StylePropertyPage extends PropertyPage {
+
+ protected Control createContents(Composite parent) {
+
+ pane = new Composite(parent, SWT.NONE);
+
+ createPropertySheet();
+
+ configListener = new IConfigListener() {
+
+ public void configChanged( final ConfigEvent e) {
+ populateDoctypes();
+ }
+
+ public void configLoaded(final ConfigEvent e) {
+ setMessage( getTitle() );
+ populateStyle();
+ setValid(true);
+
+ try { // force an incremental build
+ getPluginProject().writeConfigXml();
+ } catch (Exception ex) {
+ String message = MessageFormat.format(
+ Messages.getString("StylePropertyPage.writeError"), //$NON-NLS-1$
+ new Object[] { PluginProject.PLUGIN_XML });
+ VexPlugin.getInstance().log(IStatus.ERROR, message, ex);
+ }
+
+ }
+ };
+
+ ConfigRegistry.getInstance().addConfigListener( configListener );
+
+
+ if( ConfigRegistry.getInstance().isConfigLoaded() ){
+
+ populateStyle();
+ populateDoctypes();
+
+ } else {
+
+ setValid(false);
+
+ setMessage(Messages.getString("StylePropertyPage.loading")); //$NON-NLS-1$
+
+ }
+
+ return pane;
+ }
+
+ private void createPropertySheet() {
+
+ GridLayout layout = new GridLayout();
+ layout.numColumns = 2;
+ pane.setLayout(layout);
+ GridData gd;
+
+ Label label;
+
+ label = new Label(pane, SWT.NONE);
+ label.setText(Messages.getString("StylePropertyPage.name")); //$NON-NLS-1$
+ this.nameText = new Text(pane, SWT.BORDER);
+ gd = new GridData();
+ gd.widthHint = NAME_WIDTH;
+ this.nameText.setLayoutData(gd);
+
+ final String resourcePath = ((IFile) this.getElement()).getProjectRelativePath().toString();
+
+ final ConfigSource config = this.getPluginProject();
+
+ this.style = (Style) config.getItemForResource(resourcePath);
+ if (this.style == null) {
+ this.style = new Style(config);
+ this.style.setResourcePath(resourcePath);
+ config.addItem(this.style);
+ }
+
+ // Generate a simple ID for this one if necessary
+ if (this.style.getSimpleId() == null || this.style.getSimpleId().length() == 0) {
+ this.style.setSimpleId(this.style.generateSimpleId());
+ }
+
+ label = new Label(pane, SWT.NONE);
+ label.setText(Messages.getString("StylePropertyPage.doctypes")); //$NON-NLS-1$
+ gd = new GridData();
+ gd.horizontalSpan = 2;
+ label.setLayoutData(gd);
+
+ final Composite tablePane = new Composite(pane, SWT.BORDER );
+
+ gd = new GridData(GridData.FILL_BOTH);
+ gd.heightHint = 200;
+ gd.horizontalSpan = 2;
+ tablePane.setLayoutData(gd);
+
+ final FillLayout fillLayout = new FillLayout();
+ tablePane.setLayout(fillLayout);
+
+ this.doctypesTable = new Table( tablePane, SWT.CHECK );
+
+ }
+
+ /**
+ * Returns the PluginProject associated with this resource.
+ * @return
+ */
+ public PluginProject getPluginProject() {
+ IFile file = (IFile) this.getElement();
+ return PluginProject.get(file.getProject());
+ }
+
+ public boolean performOk() {
+
+ performApply();
+
+ return super.performOk();
+ }
+
+ public void performApply() {
+
+ this.style.setName(this.nameText.getText());
+
+ List doctypeList = ConfigRegistry.getInstance().getAllConfigItems( DocumentType.EXTENSION_POINT);
+ Collections.sort( doctypeList );
+
+ final ArrayList selectedDoctypes = new ArrayList();
+ final TableItem[] tia = this.doctypesTable.getItems();
+
+ for (int i = 0; i < tia.length; i++) {
+ if( tia[i].getChecked() ) {
+ selectedDoctypes.add( tia[i].getText());
+ }
+ }
+
+ this.style.removeAllDocumentTypes();
+
+ for (int i = 0; i < doctypeList.size(); i++) {
+ if( selectedDoctypes.contains( ((DocumentType)doctypeList.get(i)).getName() ) ) {
+ this.style.addDocumentType( ((DocumentType)doctypeList.get(i)).getPublicId() );
+ }
+ }
+
+ try {
+ this.getPluginProject().writeConfigXml();
+ } catch (Exception e) {
+ String message = MessageFormat.format(
+ Messages.getString("StylePropertyPage.writeError"), //$NON-NLS-1$
+ new Object[] { PluginProject.PLUGIN_XML });
+ VexPlugin.getInstance().log(IStatus.ERROR, message, e);
+ }
+
+ ConfigRegistry.getInstance().fireConfigChanged(new ConfigEvent(this));
+ }
+
+ protected void performDefaults() {
+
+ super.performDefaults();
+
+ populateStyle();
+
+ populateDoctypes();
+
+ }
+
+ public void dispose() {
+ super.dispose();
+
+ if (this.configListener != null) {
+ ConfigRegistry.getInstance().removeConfigListener(this.configListener);
+ }
+ }
+
+ //======================================================= PRIVATE
+
+ private Style style;
+ private static final int NAME_WIDTH = 150;
+
+ private Composite pane;
+ private Text nameText;
+ private Table doctypesTable;
+
+ private IConfigListener configListener;
+
+ private void populateStyle() {
+ this.setText(this.nameText, this.style.getName());
+
+ }
+
+ private void populateDoctypes() {
+
+ final Set selectedDoctypes = new TreeSet( this.style.getDocumentTypes() );
+ doctypesTable.removeAll();
+
+ List doctypeList = ConfigRegistry.getInstance().getAllConfigItems( DocumentType.EXTENSION_POINT);
+ Collections.sort( doctypeList );
+ for (int i = 0; i < doctypeList.size(); i++) {
+
+ TableItem item1 = new TableItem( doctypesTable, SWT.NONE );
+ item1.setText( ((DocumentType) doctypeList.get(i)).getName() );
+ if( selectedDoctypes.contains( ((DocumentType)doctypeList.get(i)).getPublicId() ) ) {
+
+ item1.setChecked(true);
+ }
+ }
+ }
+
+ private void setText(Text textBox, String s) {
+ textBox.setText(s == null ? "" : s); //$NON-NLS-1$
+ }
+
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/config/messages.properties b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/config/messages.properties
new file mode 100644
index 0000000..74e6197
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/config/messages.properties
@@ -0,0 +1,45 @@
+# *******************************************************************************
+# * Copyright (c) 2004, 2008 John Krasnay and others.
+# * All rights reserved. This program and the accompanying materials
+# * are made available under the terms of the Eclipse Public License v1.0
+# * which accompanies this distribution, and is available at
+# * http://www.eclipse.org/legal/epl-v10.html
+# *
+# * Contributors:
+# * John Krasnay - initial API and implementation
+# *******************************************************************************
+
+Messages.cantFindResource=Can''t find resource string {0}
+
+StyleFactory.pluralName=Styles
+StylePropertyPage.loading=The Configuration is being loaded ...
+StylePropertyPage.name=Name:
+StylePropertyPage.doctypes=Document Types:
+ConfigLoaderJob.loadingConfig=Loading Vex configuration
+ConfigLoaderJob.loading=Loading
+ConfigPlugin.loadError=Unable to load {0} from project {1}
+ConfigLoaderJob.loadingError=Error loading {0}. Rebuilding from scratch.
+ConfigSource.errorParsingUri=Error parsing
+ConfigLoaderJob.cacheError=Unable to cache configuration in {0}
+ConfigurationView.loading=Loading...
+ConfigLoaderJob.loadingProject=Loading project {0}
+ConfigLoaderJob.natureError=Error determining nature of project {0}
+DoctypeFactory.pluralName=Document Types
+DoctypePropertyPage.errorWritingConfig=Error writing {0}
+DoctypePropertyPage.loading=The Configuration is being loaded ...
+DoctypePropertyPage.name=Name:
+DoctypePropertyPage.publicId=Public ID:
+DoctypePropertyPage.systemId=System ID:
+DoctypePropertyPage.rootElements=Root Elements:
+NewPluginProjectWizard.title=New Vex Plug-in Project
+NewPluginProjectWizard.createError=Error creating plug-in project
+PluginProject.malformedUrl=Project base URL is malformed.
+PluginProject.notPluginProject=Project {0} is not a Vex plugin project.
+PluginProjectBuilder.notConfigSource=Internal error: project {0} is not registered as a config source.
+PluginProjectBuilder.cantSaveFile=Unable to save {0}.
+PluginProjectBuilder.missingFile=Missing {0}.
+PluginProjectBuilder.parseError=Error parsing {0}.
+PluginProjectBuilder.cantSaveConfig=Unable to save VexConfiguration for project {0}.
+PluginProject.loadingError=Error loading {0}. Rebuilding from scratch.
+PluginProject.buildError=Error building project {0}.
+StylePropertyPage.writeError=Error writing {0}.
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/config/messages_fr.properties b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/config/messages_fr.properties
new file mode 100644
index 0000000..e16b092
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/config/messages_fr.properties
@@ -0,0 +1,44 @@
+# *******************************************************************************
+# * Copyright (c) 2004, 2008 John Krasnay and others.
+# * All rights reserved. This program and the accompanying materials
+# * are made available under the terms of the Eclipse Public License v1.0
+# * which accompanies this distribution, and is available at
+# * http://www.eclipse.org/legal/epl-v10.html
+# *
+# * Contributors:
+# * John Krasnay - initial API and implementation
+# *******************************************************************************
+Messages.cantFindResource=Ne peut pas trouver la ressource : {0}
+
+StyleFactory.pluralName=Styles
+StylePropertyPage.loading=La configuration a été chargée ...
+StylePropertyPage.name=Nom:
+StylePropertyPage.doctypes=Types de Document:
+ConfigLoaderJob.loadingConfig=Chargement de la configuration de Vex
+ConfigLoaderJob.loading=Chargement
+ConfigPlugin.loadError=Impossible de charger {0} du projet {1}
+ConfigLoaderJob.loadingError=Erreur au chargement {0}. Reconstuction.
+ConfigSource.errorParsingUri=Erreur d'analyse
+ConfigLoaderJob.cacheError=Impossible de mettre en cache la configuration pour {0}
+ConfigurationView.loading=Chargement...
+ConfigLoaderJob.loadingProject=Chargement du projet {0}
+ConfigLoaderJob.natureError=Erreur lors de la détermination du projet {0}
+DoctypeFactory.pluralName=Types de Document
+DoctypePropertyPage.errorWritingConfig=Erreur à l'écriture {0}
+DoctypePropertyPage.loading=La configuration est chargée ...
+DoctypePropertyPage.name=Nom:
+DoctypePropertyPage.publicId=Public ID:
+DoctypePropertyPage.systemId=System ID:
+DoctypePropertyPage.rootElements=Elements Racines:
+NewPluginProjectWizard.title=Nouveau projet Vex
+NewPluginProjectWizard.createError=Erreur à la création du projet Vex
+PluginProject.malformedUrl=L'URL de base du projet est incorrecte
+PluginProject.notPluginProject=Le projet {0} n'est pas un projet Vex.
+PluginProjectBuilder.notConfigSource=Erreur interne: project {0} is not registered as a config source.
+PluginProjectBuilder.cantSaveFile=Impossible de sauver le fichier {0}.
+PluginProjectBuilder.missingFile={0} Manquant.
+PluginProjectBuilder.parseError=Erreur à l'analyse {0}.
+PluginProjectBuilder.cantSaveConfig=Impossible de sauver VexConfiguration pour le projet {0}.
+PluginProject.loadingError=Erreur au chargement {0}. Reconstuction.
+PluginProject.buildError=Erreur à la construction du projet {0}.
+StylePropertyPage.writeError=Erreur à l'écriture {0}.
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/config/vex-config-1.0.dtd b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/config/vex-config-1.0.dtd
new file mode 100644
index 0000000..2e7e701
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/config/vex-config-1.0.dtd
@@ -0,0 +1,86 @@
+<!--
+
+ *******************************************************************************
+ * Copyright (c) 2003, 2008 John Krasnay and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * John Krasnay - initial API and implementation
+ *******************************************************************************
+
+<?xml version='1.0'?>
+<!DOCTYPE doctype PUBLIC
+ "-//Vex//DTD Vex Configuration 1.0//EN"
+ "http://vex.sourceforge.net/dtd/vex-config-1.0.dtd">
+
+-->
+
+<!--
+
+ doctype
+
+ Defines a document type. The following attributes are supported.
+
+ publicId - Unique public identifier of the document type. Typically, the
+ the format is -//(organization)//(name and version//EN
+
+ dtd - Absolute or relative URL to the related DTD file.
+
+
+-->
+
+<!ELEMENT doctype EMPTY>
+<!ATTLIST doctype
+ publicId CDATA #REQUIRED
+ systemId CDATA #REQUIRED
+ dtd CDATA #REQUIRED
+>
+
+
+<!--
+
+ doctypeRef
+
+ Defines a reference to a document type. The following attributes are
+ supported.
+
+ publicId - Public ID of the referenced document type.
+
+-->
+
+<!ELEMENT doctypeRef EMPTY>
+<!ATTLIST doctypeRef
+ publicId CDATA #REQUIRED
+>
+
+
+<!--
+
+ style
+
+ Defines a presentation for a document. The following attributes are supported.
+
+ id - Unique identifier of the style. This should follow Java
+ package naming conventions, e.g. net.sf.vex.styles.docbook.plain
+
+ name - A brief name for the style.
+
+ css - Absolute or relative URL to the related CSS file.
+
+ This element contains one or more doctypeRef elements indicating the
+ doctypes supported by this style.
+
+-->
+
+<!ELEMENT style (doctypeRef+)>
+<!ATTLIST style
+ id ID #REQUIRED
+ name CDATA #REQUIRED
+ css CDATA #REQUIRED
+>
+
+
+
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/editor/Association.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/editor/Association.java
new file mode 100644
index 0000000..5d8bfce
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/editor/Association.java
@@ -0,0 +1,161 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.ui.internal.editor;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+
+/**
+ * Data structure that uses two maps to efficiently implement a many-to-many
+ * relationship.
+ */
+public class Association {
+
+ /**
+ * Adds a relationship between two objects.
+ *
+ * @param left Object on the left side of the relationship.
+ * @param right Object on the right side of the relationship.
+ */
+ public void add(Object left, Object right) {
+ Collection rights = (Collection) l2r.get(left);
+ if (rights == null) {
+ rights = new ArrayList();
+ l2r.put(left, rights);
+ }
+ rights.add(right);
+
+ Collection lefts = (Collection) r2l.get(right);
+ if (lefts == null) {
+ lefts = new ArrayList();
+ r2l.put(right, lefts);
+ }
+ lefts.add(left);
+ }
+
+ /**
+ * Returns a collection of objects on the left side of the association
+ * for a given object on the right.
+ *
+ * @param right Object for which to return associated objects.
+ */
+ public Collection getLeftsForRight(Object right) {
+ Collection lefts = (Collection) r2l.get(right);
+ if (lefts == null) {
+ return Collections.EMPTY_LIST;
+ } else {
+ return Collections.unmodifiableCollection(lefts);
+ }
+ }
+
+ /**
+ * Returns a collection of objects on the right side of the association
+ * for a given object on the left.
+ *
+ * @param left Object for which to return associated objects.
+ */
+ public Collection getRightsForLeft(Object left) {
+ Collection rights = (Collection) l2r.get(left);
+ if (rights == null) {
+ return Collections.EMPTY_LIST;
+ } else {
+ return Collections.unmodifiableCollection(rights);
+ }
+ }
+
+ /**
+ * Removes an association between two objects. Returns silently if this
+ * is not an existing relationship.
+ *
+ * @param left Object on the left side of the relationship.
+ * @param right Object on the right side of the relationship.
+ */
+ public void remove(Object left, Object right) {
+
+ Collection rights = (Collection) l2r.get(left);
+ if (rights != null) {
+ rights.remove(right);
+ if (rights.size() == 0) {
+ l2r.remove(left);
+ }
+ }
+
+ Collection lefts = (Collection) r2l.get(right);
+ if (lefts != null) {
+ lefts.remove(left);
+ if (lefts.size() == 0) {
+ r2l.remove(right);
+ }
+ }
+
+ }
+
+ /**
+ * Removes all relationships for a given object on the left side.
+ *
+ * @param left Object for which to remove a relationship.
+ */
+ public void removeLeft(Object left) {
+
+ Collection rights = (Collection) l2r.get(left);
+ if (rights == null) {
+ return;
+ }
+
+ l2r.remove(left);
+
+ for (Iterator it = rights.iterator(); it.hasNext(); ) {
+ Object right = it.next();
+ Collection lefts = (Collection) r2l.get(right);
+ lefts.remove(left);
+ if (lefts.isEmpty()) {
+ r2l.remove(right);
+ }
+ }
+ }
+
+ /**
+ * Removes all relationships for a given object on the right side.
+ *
+ * @param right Object for which to remove a relationship.
+ */
+ public void removeRight(Object right) {
+
+ Collection lefts = (Collection) r2l.get(right);
+ if (lefts == null) {
+ return;
+ }
+
+ r2l.remove(right);
+
+ for (Iterator it = lefts.iterator(); it.hasNext(); ) {
+ Object left = it.next();
+ Collection rights = (Collection) l2r.get(left);
+ rights.remove(right);
+ if (rights.isEmpty()) {
+ l2r.remove(left);
+ }
+ }
+ }
+
+
+ //==================================================== PRIVATE
+
+ // maps left => list of associated rights
+ private Map l2r = new HashMap();
+
+ // maps right => list of associated lefts
+ private Map r2l = new HashMap();
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/editor/ContentAssistant.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/editor/ContentAssistant.java
new file mode 100644
index 0000000..c341c7c
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/editor/ContentAssistant.java
@@ -0,0 +1,268 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.ui.internal.editor;
+
+
+import org.eclipse.jface.action.IAction;
+import org.eclipse.jface.dialogs.IDialogSettings;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.ControlEvent;
+import org.eclipse.swt.events.ControlListener;
+import org.eclipse.swt.events.DisposeEvent;
+import org.eclipse.swt.events.DisposeListener;
+import org.eclipse.swt.events.KeyEvent;
+import org.eclipse.swt.events.KeyListener;
+import org.eclipse.swt.events.ModifyEvent;
+import org.eclipse.swt.events.ModifyListener;
+import org.eclipse.swt.events.MouseEvent;
+import org.eclipse.swt.events.MouseListener;
+import org.eclipse.swt.events.SelectionEvent;
+import org.eclipse.swt.events.SelectionListener;
+import org.eclipse.swt.events.ShellEvent;
+import org.eclipse.swt.events.ShellListener;
+import org.eclipse.swt.graphics.Rectangle;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.widgets.Shell;
+import org.eclipse.swt.widgets.Text;
+import org.eclipse.swt.widgets.Tree;
+import org.eclipse.swt.widgets.TreeItem;
+import org.eclipse.wst.xml.vex.core.internal.swt.VexWidget;
+import org.eclipse.wst.xml.vex.ui.internal.VexPlugin;
+
+
+/**
+ * Class that owns the content assistant window, the popup where the user
+ * can select elements or other content to insert. This class is a singleton
+ * owned by VexPlugin. It should not be instantiated directly.
+ */
+public abstract class ContentAssistant {
+
+ /**
+ * Class constructor.
+ */
+ public ContentAssistant() {
+ }
+
+ /**
+ * Returns the list of actions to be displayed in this assistant.
+ * @param vexWidget VexWidget to which the actions apply.
+ */
+ public abstract IAction[] getActions(VexWidget widget);
+
+ /**
+ * Returns the title to be displayed in the assistant's title bar.
+ * @param vexWidget VexWidget for which we are displaying the assistant.
+ */
+ public abstract String getTitle(VexWidget widget);
+
+ /**
+ * Show the content assitant window for the given editor.
+ * @param vexWidget VexWidget for which we are displaying the assistant.
+ */
+ public void show(VexWidget vexWidget) {
+
+ this.actions = this.getActions(vexWidget);
+
+ Shell parent = vexWidget.getShell();
+ this.assistantShell = new Shell(parent, SWT.DIALOG_TRIM | SWT.RESIZE | SWT.MODELESS);
+ this.assistantShell.setText(this.getTitle(vexWidget));
+ this.assistantShell.addControlListener(this.controlListener);
+ this.assistantShell.addDisposeListener(this.disposeListener);
+ this.assistantShell.addShellListener(this.shellListener);
+
+ GridLayout layout = new GridLayout();
+ layout.numColumns = 1;
+ this.assistantShell.setLayout(layout);
+ GridData gd;
+
+ this.textWidget = new Text(this.assistantShell, SWT.SINGLE);
+ this.textWidget.addModifyListener(this.modifyListener);
+
+ gd = new GridData();
+ gd.grabExcessHorizontalSpace = true;
+ gd.horizontalAlignment = GridData.FILL;
+ this.textWidget.setLayoutData(gd);
+ this.textWidget.addKeyListener(this.keyListener);
+ this.textWidget.addModifyListener(this.modifyListener);
+
+ this.treeWidget = new Tree(assistantShell, SWT.SINGLE);
+ this.treeWidget.addKeyListener(this.keyListener);
+ this.treeWidget.addMouseListener(this.mouseListener);
+ this.treeWidget.addSelectionListener(this.selectionListener);
+
+ gd = new GridData();
+ gd.grabExcessHorizontalSpace = true;
+ gd.horizontalAlignment = GridData.FILL;
+ gd.grabExcessVerticalSpace = true;
+ gd.verticalAlignment = GridData.FILL;
+ this.treeWidget.setLayoutData(gd);
+
+ this.assistantShell.setBounds(this.getSize());
+ this.repopulateList();
+ this.mouseDown = false;
+ this.assistantShell.open();
+
+
+ }
+
+ //======================================================== PRIVATE
+
+ private static final String SETTINGS_SECTION = "contentAssistant"; //$NON-NLS-1$
+ private static final String SETTINGS_X = "x"; //$NON-NLS-1$
+ private static final String SETTINGS_Y = "y"; //$NON-NLS-1$
+ private static final String SETTINGS_WIDTH = "width"; //$NON-NLS-1$
+ private static final String SETTINGS_HEIGHT = "height"; //$NON-NLS-1$
+
+ private Shell assistantShell;
+ private Text textWidget;
+ private Tree treeWidget;
+ private IAction[] actions;
+ private boolean mouseDown;
+
+ /**
+ * Perform the action that is currently selected in the tree view, if any,
+ * and dispose the assistant shell.
+ */
+ private void doAction() {
+ TreeItem[] items = treeWidget.getSelection();
+ if (items.length > 0) {
+ IAction action = (IAction) items[0].getData();
+ action.run();
+ }
+ assistantShell.dispose();
+ }
+
+ private IDialogSettings getSettings() {
+ IDialogSettings rootSettings = VexPlugin.getInstance().getDialogSettings();
+ IDialogSettings settings = rootSettings.getSection(SETTINGS_SECTION);
+ if (settings == null) {
+ settings = rootSettings.addNewSection(SETTINGS_SECTION);
+ }
+ return settings;
+ }
+
+ private Rectangle getSize() {
+ IDialogSettings settings = this.getSettings();
+ int x = 100;
+ int y = 100;
+ int width = 200;
+ int height = 300;
+ if (settings.get(SETTINGS_X) != null) {
+ x = settings.getInt(SETTINGS_X);
+ y = settings.getInt(SETTINGS_Y);
+ width = settings.getInt(SETTINGS_WIDTH);
+ height = settings.getInt(SETTINGS_HEIGHT);
+ }
+ return new Rectangle(x, y, width, height);
+ }
+
+ private void repopulateList() {
+ String prefix = this.textWidget.getText();
+ this.treeWidget.removeAll();
+ TreeItem first = null;
+ for (int i = 0; i < this.actions.length; i++) {
+ IAction action = this.actions[i];
+ if (action.getText().startsWith(prefix)) {
+ TreeItem item = new TreeItem(this.treeWidget, SWT.NONE);
+ if (first == null) {
+ first = item;
+ }
+ item.setData(action);
+ item.setText(action.getText());
+ }
+ }
+
+ if (first != null) {
+ this.treeWidget.setSelection(new TreeItem[] { first });
+ }
+ }
+
+ private void saveSize() {
+ IDialogSettings settings = this.getSettings();
+ Rectangle bounds = this.assistantShell.getBounds();
+ settings.put(SETTINGS_X, bounds.x);
+ settings.put(SETTINGS_Y, bounds.y);
+ settings.put(SETTINGS_WIDTH, bounds.width);
+ settings.put(SETTINGS_HEIGHT, bounds.height);
+ }
+
+ private ControlListener controlListener = new ControlListener() {
+ public void controlMoved(ControlEvent e) {
+ saveSize();
+ }
+ public void controlResized(ControlEvent e) {
+ saveSize();
+ }
+ };
+
+ private DisposeListener disposeListener = new DisposeListener() {
+ public void widgetDisposed(DisposeEvent e) {
+ assistantShell = null;
+ }
+ };
+
+ private KeyListener keyListener = new KeyListener() {
+ public void keyPressed(KeyEvent e) {
+ if (e.keyCode == 13) {
+ doAction();
+ } else if (e.widget == textWidget && e.keyCode == SWT.ARROW_DOWN) {
+ treeWidget.setFocus();
+ }
+
+ }
+ public void keyReleased(KeyEvent e) {
+ }
+ };
+
+ private ModifyListener modifyListener = new ModifyListener() {
+ public void modifyText(ModifyEvent e) {
+ repopulateList();
+ }
+ };
+
+ private MouseListener mouseListener = new MouseListener() {
+ public void mouseDoubleClick(MouseEvent e) {
+ }
+ public void mouseDown(MouseEvent e) {
+ mouseDown = true;
+ }
+ public void mouseUp(MouseEvent e) {
+ mouseDown = false;
+ }
+
+ };
+
+ private SelectionListener selectionListener = new SelectionListener() {
+ public void widgetSelected(SelectionEvent e) {
+ // If selected with the mouse, we do the selected action.
+ if (mouseDown) {
+ doAction();
+ }
+ }
+ public void widgetDefaultSelected(SelectionEvent e) {
+ }
+ };
+
+ private ShellListener shellListener = new ShellListener() {
+ public void shellActivated(ShellEvent e) {
+ }
+ public void shellClosed(ShellEvent e) {
+ }
+ public void shellDeactivated(ShellEvent e) {
+ assistantShell.dispose();
+ }
+ public void shellDeiconified(ShellEvent e) {
+ }
+ public void shellIconified(ShellEvent e) {
+ }
+ };
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/editor/DebugView.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/editor/DebugView.java
new file mode 100644
index 0000000..befb559
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/editor/DebugView.java
@@ -0,0 +1,81 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.ui.internal.editor;
+
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.swt.widgets.Label;
+import org.eclipse.ui.IActionBars;
+import org.eclipse.ui.IWorkbenchPart;
+import org.eclipse.ui.PartInitException;
+import org.eclipse.ui.part.IPage;
+import org.eclipse.ui.part.IPageBookViewPage;
+import org.eclipse.ui.part.IPageSite;
+import org.eclipse.ui.part.PageBook;
+import org.eclipse.ui.part.PageBookView;
+
+/**
+ * A view that shows stats about the current Vex editor as a debugging aid.
+ */
+public class DebugView extends PageBookView {
+
+ protected IPage createDefaultPage(PageBook book) {
+ IPageBookViewPage page = new IPageBookViewPage() {
+ public void createControl(Composite parent) {
+ this.label = new Label(parent, SWT.NONE);
+ this.label.setText(Messages.getString("DebugView.noActiveEditor")); //$NON-NLS-1$
+ }
+ public void dispose() {
+ }
+ public Control getControl() {
+ return this.label;
+ }
+ public IPageSite getSite() {
+ return this.site;
+ }
+ public void init(IPageSite site) throws PartInitException {
+ this.site = site;
+ }
+ public void setActionBars(IActionBars actionBars) {
+ }
+ public void setFocus() {
+ }
+
+ private IPageSite site;
+ private Label label;
+ };
+
+ initPage(page);
+ page.createControl(getPageBook());
+ return page;
+ }
+
+ protected PageRec doCreatePage(IWorkbenchPart part) {
+ DebugViewPage page = new DebugViewPage((VexEditor) part);
+ initPage(page);
+ page.createControl(getPageBook());
+ return new PageRec(part, page);
+ }
+
+ protected void doDestroyPage(IWorkbenchPart part, PageRec pageRecord) {
+ pageRecord.page.dispose();
+ }
+
+ protected IWorkbenchPart getBootstrapPart() {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ protected boolean isImportant(IWorkbenchPart part) {
+ return (part instanceof VexEditor);
+ }
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/editor/DebugViewPage.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/editor/DebugViewPage.java
new file mode 100644
index 0000000..7df63b7
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/editor/DebugViewPage.java
@@ -0,0 +1,300 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.ui.internal.editor;
+
+import java.lang.reflect.Field;
+
+
+import org.eclipse.jface.viewers.ISelectionChangedListener;
+import org.eclipse.jface.viewers.SelectionChangedEvent;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.custom.ScrolledComposite;
+import org.eclipse.swt.events.ControlEvent;
+import org.eclipse.swt.events.ControlListener;
+import org.eclipse.swt.events.MouseEvent;
+import org.eclipse.swt.events.MouseMoveListener;
+import org.eclipse.swt.events.SelectionEvent;
+import org.eclipse.swt.events.SelectionListener;
+import org.eclipse.swt.layout.FillLayout;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.widgets.Button;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.swt.widgets.Label;
+import org.eclipse.swt.widgets.Table;
+import org.eclipse.swt.widgets.TableColumn;
+import org.eclipse.swt.widgets.TableItem;
+import org.eclipse.ui.IActionBars;
+import org.eclipse.ui.PartInitException;
+import org.eclipse.ui.part.IPageBookViewPage;
+import org.eclipse.ui.part.IPageSite;
+import org.eclipse.wst.xml.vex.core.internal.core.Caret;
+import org.eclipse.wst.xml.vex.core.internal.core.Rectangle;
+import org.eclipse.wst.xml.vex.core.internal.layout.Box;
+import org.eclipse.wst.xml.vex.core.internal.swt.VexWidget;
+import org.eclipse.wst.xml.vex.core.internal.widget.HostComponent;
+import org.eclipse.wst.xml.vex.core.internal.widget.VexWidgetImpl;
+
+/**
+ * Page in the debug view.
+ */
+class DebugViewPage implements IPageBookViewPage {
+
+ public DebugViewPage(VexEditor vexEditor) {
+ this.vexEditor = vexEditor;
+ }
+
+ public void createControl(Composite parent) {
+
+ this.composite = new Composite(parent, SWT.NONE);
+ this.composite.setLayout(new FillLayout());
+
+ if (this.vexEditor.isLoaded()) {
+ this.createDebugPanel();
+ } else {
+ this.loadingLabel = new Label(this.composite, SWT.NONE);
+ this.loadingLabel.setText("Loading...");
+ }
+
+ this.vexEditor.getEditorSite().getSelectionProvider().addSelectionChangedListener(this.selectionListener);
+ }
+
+ public void dispose() {
+ if (this.vexWidget != null && !this.vexWidget.isDisposed()) {
+ this.vexWidget.removeMouseMoveListener(this.mouseMoveListener);
+ }
+ this.vexEditor.getEditorSite().getSelectionProvider().removeSelectionChangedListener(this.selectionListener);
+ }
+
+ public Control getControl() {
+ return this.composite;
+ }
+
+ public IPageSite getSite() {
+ return this.site;
+ }
+
+ public void init(IPageSite site) throws PartInitException {
+ this.site = site;
+ }
+
+ public void setActionBars(IActionBars actionBars) {
+ }
+
+ public void setFocus() {
+ }
+
+ //================================================== PRIVATE
+
+ private static final int X = 1;
+ private static final int Y = 2;
+ private static final int WIDTH = 3;
+ private static final int HEIGHT = 4;
+
+ private static Field implField;
+ private static Field rootBoxField;
+ private static Field caretField;
+ private static Field hostComponentField;
+
+ static {
+ try {
+ implField = VexWidget.class.getDeclaredField("impl");
+ implField.setAccessible(true);
+ rootBoxField = VexWidgetImpl.class.getDeclaredField("rootBox");
+ rootBoxField.setAccessible(true);
+ caretField = VexWidgetImpl.class.getDeclaredField("caret");
+ caretField.setAccessible(true);
+ hostComponentField = VexWidgetImpl.class.getDeclaredField("hostComponent");
+ hostComponentField.setAccessible(true);
+ } catch (Exception e) {
+ // TODO: handle exception
+ }
+ }
+
+ private IPageSite site;
+ private VexEditor vexEditor;
+ private VexWidget vexWidget;
+ private VexWidgetImpl impl;
+ private Composite composite;
+
+ private Label loadingLabel;
+
+ private Table table;
+ private TableItem documentItem;
+ private TableItem viewportItem;
+ private TableItem caretOffsetItem;
+ private TableItem caretAbsItem;
+ private TableItem caretRelItem;
+ private TableItem mouseAbsItem;
+ private TableItem mouseRelItem;
+
+ private void createDebugPanel() {
+
+ if (this.loadingLabel != null) {
+ this.loadingLabel.dispose();
+ this.loadingLabel = null;
+ }
+
+ this.vexWidget = this.vexEditor.getVexWidget();
+ try {
+ this.impl = (VexWidgetImpl) implField.get(this.vexWidget);
+ } catch (IllegalArgumentException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ } catch (IllegalAccessException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+
+ GridLayout layout = new GridLayout();
+ layout.numColumns = 1;
+ composite.setLayout(layout);
+ GridData gd;
+
+ ScrolledComposite sc = new ScrolledComposite(this.composite, SWT.V_SCROLL);
+ this.table = new Table(sc, SWT.NONE);
+ this.table.setHeaderVisible(true);
+ sc.setContent(table);
+ sc.setExpandHorizontal(true);
+ sc.setExpandVertical(true);
+ gd = new GridData();
+ gd.grabExcessHorizontalSpace = true;
+ gd.horizontalAlignment = GridData.FILL;
+ sc.setLayoutData(gd);
+
+ TableColumn column;
+ column = new TableColumn(this.table, SWT.LEFT);
+ column.setText("Item");
+ column = new TableColumn(this.table, SWT.RIGHT);
+ column.setText("X");
+ column = new TableColumn(this.table, SWT.RIGHT);
+ column.setText("Y");
+ column = new TableColumn(this.table, SWT.RIGHT);
+ column.setText("Width");
+ column = new TableColumn(this.table, SWT.RIGHT);
+ column.setText("Height");
+
+ this.table.addControlListener(this.controlListener);
+
+ this.documentItem = new TableItem(this.table, SWT.NONE);
+ this.documentItem.setText(0, "Document");
+ this.viewportItem = new TableItem(this.table, SWT.NONE);
+ this.viewportItem.setText(0, "Viewport");
+ this.caretOffsetItem = new TableItem(this.table, SWT.NONE);
+ this.caretOffsetItem.setText(0, "Caret Offset");
+ this.caretAbsItem = new TableItem(this.table, SWT.NONE);
+ this.caretAbsItem.setText(0, "Caret Abs.");
+ this.caretRelItem = new TableItem(this.table, SWT.NONE);
+ this.caretRelItem.setText(0, "Caret Rel.");
+ this.mouseAbsItem = new TableItem(this.table, SWT.NONE);
+ this.mouseAbsItem.setText(0, "Mouse Abs.");
+ this.mouseRelItem = new TableItem(this.table, SWT.NONE);
+ this.mouseRelItem.setText(0, "Mouse Rel.");
+
+ Button updateButton = new Button(composite, SWT.PUSH);
+ updateButton.setText("Refresh");
+ updateButton.addSelectionListener(new SelectionListener() {
+ public void widgetSelected(SelectionEvent e) {
+ repopulate();
+ }
+ public void widgetDefaultSelected(SelectionEvent e) {
+ }
+ });
+
+ this.composite.layout();
+
+ this.vexWidget.addMouseMoveListener(this.mouseMoveListener);
+
+ this.repopulate();
+
+ }
+
+
+ private ISelectionChangedListener selectionListener = new ISelectionChangedListener() {
+ public void selectionChanged(SelectionChangedEvent event) {
+ if (vexWidget == null) {
+ createDebugPanel();
+ }
+ repopulate();
+ }
+ };
+
+ private ControlListener controlListener = new ControlListener() {
+ public void controlMoved(ControlEvent e) {
+ }
+ public void controlResized(ControlEvent e) {
+ int width = table.getSize().x;
+ int numWidth = Math.round(width * 0.125f);
+ table.getColumn(0).setWidth(width / 2);
+ table.getColumn(1).setWidth(numWidth);
+ table.getColumn(2).setWidth(numWidth);
+ table.getColumn(3).setWidth(numWidth);
+ }
+ };
+
+ private MouseMoveListener mouseMoveListener = new MouseMoveListener() {
+
+ public void mouseMove(MouseEvent e) {
+ Rectangle rect = new Rectangle(e.x, e.y, 0, 0);
+ Rectangle viewport = getViewport();
+ setItemFromRect(mouseAbsItem, rect);
+ setItemRel(mouseRelItem, viewport, rect);
+ }
+
+ };
+
+ private Rectangle getCaretBounds() {
+ Caret caret = (Caret) this.getFieldValue(caretField, this.impl);
+ return caret.getBounds();
+ }
+
+ private Rectangle getRootBoxBounds() {
+ Box rootBox = (Box) this.getFieldValue(rootBoxField, this.impl);
+ return new Rectangle(rootBox.getX(), rootBox.getY(), rootBox.getWidth(), rootBox.getHeight());
+ }
+
+ private Rectangle getViewport() {
+ HostComponent hc = (HostComponent) this.getFieldValue(hostComponentField, this.impl);
+ return hc.getViewport();
+ }
+
+ private Object getFieldValue(Field field, Object o) {
+ try {
+ return field.get(o);
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ private void repopulate() {
+ setItemFromRect(this.documentItem, this.getRootBoxBounds());
+ Rectangle viewport = this.getViewport();
+ this.caretOffsetItem.setText(1, Integer.toString(this.impl.getCaretOffset()));
+ setItemFromRect(this.viewportItem, viewport);
+ setItemFromRect(this.caretAbsItem, this.getCaretBounds());
+ setItemRel(this.caretRelItem, viewport, this.getCaretBounds());
+ }
+
+ private static void setItemFromRect(TableItem item, Rectangle rect) {
+ item.setText(X, Integer.toString(rect.getX()));
+ item.setText(Y, Integer.toString(rect.getY()));
+ item.setText(WIDTH, Integer.toString(rect.getWidth()));
+ item.setText(HEIGHT, Integer.toString(rect.getHeight()));
+ }
+
+ private static void setItemRel(TableItem item, Rectangle viewport, Rectangle rect) {
+ item.setText(X, Integer.toString(rect.getX() - viewport.getX()));
+ item.setText(Y, Integer.toString(rect.getY() - viewport.getY()));
+ item.setText(WIDTH, Integer.toString(rect.getWidth()));
+ item.setText(HEIGHT, Integer.toString(rect.getHeight()));
+ }
+}
\ No newline at end of file
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/editor/DefaultOutlineProvider.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/editor/DefaultOutlineProvider.java
new file mode 100644
index 0000000..4099afe
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/editor/DefaultOutlineProvider.java
@@ -0,0 +1,123 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.ui.internal.editor;
+
+import java.util.ArrayList;
+import java.util.List;
+
+
+import org.eclipse.jface.viewers.IBaseLabelProvider;
+import org.eclipse.jface.viewers.ITreeContentProvider;
+import org.eclipse.jface.viewers.LabelProvider;
+import org.eclipse.jface.viewers.Viewer;
+import org.eclipse.wst.xml.vex.core.internal.css.StyleSheet;
+import org.eclipse.wst.xml.vex.core.internal.dom.Document;
+import org.eclipse.wst.xml.vex.core.internal.dom.Element;
+import org.eclipse.wst.xml.vex.core.internal.dom.IWhitespacePolicy;
+import org.eclipse.wst.xml.vex.core.internal.widget.CssWhitespacePolicy;
+
+/**
+ * Default implementation of IOutlineProvider. Simply displays all block-level
+ * elements.
+ */
+public class DefaultOutlineProvider implements IOutlineProvider {
+
+ public void init(VexEditor editor) {
+ StyleSheet ss = editor.getVexWidget().getStyleSheet();
+ this.whitespacePolicy = new CssWhitespacePolicy(ss);
+ this.contentProvider = new ContentProvider();
+ this.labelProvider = new LabelProvider() {
+ public String getText(Object o) {
+ Element e = (Element) o;
+ String s = e.getText();
+ if (s.length() > 30) {
+ s = s.substring(0, 30) + "..."; //$NON-NLS-1$
+ }
+ return e.getName() + ": " + s; //$NON-NLS-1$
+ }
+ };
+
+ }
+
+ public ITreeContentProvider getContentProvider() {
+ return this.contentProvider;
+ }
+
+ public IBaseLabelProvider getLabelProvider() {
+ return this.labelProvider;
+ }
+
+ public Element getOutlineElement(Element child) {
+ Element element = child;
+ while (element != null) {
+ if (this.whitespacePolicy.isBlock(element)) {
+ return element;
+ }
+ element = element.getParent();
+ }
+ return element;
+ }
+
+ //====================================================== PRIVATE
+
+ private IWhitespacePolicy whitespacePolicy;
+ private ITreeContentProvider contentProvider;
+ private IBaseLabelProvider labelProvider;
+
+ private class ContentProvider implements ITreeContentProvider {
+
+ public Object[] getChildren(Object parentElement) {
+ List blockChildren = new ArrayList();
+ Element[] children = ((Element) parentElement).getChildElements();
+ for (int i = 0; i < children.length; i++) {
+ if (whitespacePolicy.isBlock(children[i])) {
+ blockChildren.add(children[i]);
+ }
+ }
+ return blockChildren.toArray();
+ }
+
+ public Object getParent(Object element) {
+ return ((Element) element).getParent();
+ }
+
+ public boolean hasChildren(Object o) {
+ return this.hasBlockChild((Element) o);
+ }
+
+ public Object[] getElements(Object inputElement) {
+ return new Object[] { ((Document) inputElement).getRootElement() };
+ //return this.getChildren(inputElement);
+ }
+
+ public void dispose() {
+ }
+
+ public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
+ // TODO Auto-generated method stub
+
+ }
+
+ //====================================================== PRIVATE
+
+ private boolean hasBlockChild(Element element) {
+ Element[] children = element.getChildElements();
+ for (int i = 0; i < children.length; i++) {
+ if (whitespacePolicy.isBlock(children[i])) {
+ return true;
+ }
+ }
+ return false;
+ }
+ }
+
+
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/editor/DocumentFileCreationPage.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/editor/DocumentFileCreationPage.java
new file mode 100644
index 0000000..ae68ad1
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/editor/DocumentFileCreationPage.java
@@ -0,0 +1,51 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.ui.internal.editor;
+
+import java.io.InputStream;
+
+import org.eclipse.jface.viewers.IStructuredSelection;
+import org.eclipse.ui.dialogs.WizardNewFileCreationPage;
+
+/**
+ * Wizard page for selecting the file name for a new Vex document.
+ */
+public class DocumentFileCreationPage extends WizardNewFileCreationPage {
+
+ /**
+ * Class constructor. Supplies a title and description to the superclass.
+ * @param pageName name of the page
+ * @param selection selection active when the wizard was started
+ */
+ public DocumentFileCreationPage(String pageName, IStructuredSelection selection) {
+ super(pageName, selection);
+ this.setTitle(Messages.getString("DocumentFileCreationPage.title")); //$NON-NLS-1$
+ this.setDescription(Messages.getString("DocumentFileCreationPage.desc")); //$NON-NLS-1$
+ }
+
+ /**
+ * Returns the initial contents of the file. The initial contents
+ * are set by the wizard via the {@link setInitialContents} method.
+ */
+ protected InputStream getInitialContents() {
+ return this.initialContents;
+ }
+
+ /**
+ * Sets the initial contents to be used when the document is created.
+ * @param initialContents initial contents for the new document.
+ */
+ public void setInitialContents(InputStream initialContents) {
+ this.initialContents = initialContents;
+ }
+
+ private InputStream initialContents;
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/editor/DocumentOutlinePage.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/editor/DocumentOutlinePage.java
new file mode 100644
index 0000000..a9ddd90
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/editor/DocumentOutlinePage.java
@@ -0,0 +1,245 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.ui.internal.editor;
+
+import java.text.MessageFormat;
+
+
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.core.runtime.Platform;
+import org.eclipse.jface.viewers.ISelection;
+import org.eclipse.jface.viewers.ISelectionChangedListener;
+import org.eclipse.jface.viewers.SelectionChangedEvent;
+import org.eclipse.jface.viewers.StructuredSelection;
+import org.eclipse.jface.viewers.TreeViewer;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.layout.FillLayout;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.swt.widgets.Label;
+import org.eclipse.swt.widgets.TreeItem;
+import org.eclipse.ui.part.IPageSite;
+import org.eclipse.ui.part.Page;
+import org.eclipse.ui.views.contentoutline.IContentOutlinePage;
+import org.eclipse.wst.xml.vex.core.internal.dom.Element;
+import org.eclipse.wst.xml.vex.core.internal.swt.VexWidget;
+import org.eclipse.wst.xml.vex.ui.internal.VexPlugin;
+import org.eclipse.wst.xml.vex.ui.internal.config.DocumentType;
+import org.osgi.framework.Bundle;
+
+/**
+ * Outline page for documents. Determination of the outline itself is deferred
+ * to a doctype-specific implementation of IOutlineProvider.
+ */
+public class DocumentOutlinePage extends Page implements IContentOutlinePage {
+
+ public void createControl(Composite parent) {
+
+ this.composite = new Composite(parent, SWT.NONE);
+ this.composite.setLayout(new FillLayout());
+
+ if (this.vexEditor.isLoaded()) {
+ this.showTreeViewer();
+ } else {
+ this.showLabel(Messages.getString("DocumentOutlinePage.loading")); //$NON-NLS-1$
+ }
+
+ }
+
+ public void dispose() {
+ this.vexEditor.removeVexEditorListener(this.vexEditorListener);
+ this.vexEditor.getEditorSite().getSelectionProvider().removeSelectionChangedListener(this.selectionListener);
+ }
+
+ public Control getControl() {
+ return this.composite;
+ }
+
+ public void init(IPageSite pageSite) {
+ super.init(pageSite);
+ this.vexEditor = (VexEditor) pageSite.getPage().getActiveEditor();
+ this.vexEditor.addVexEditorListener(this.vexEditorListener);
+ this.vexEditor.getEditorSite().getSelectionProvider().addSelectionChangedListener(this.selectionListener);
+ }
+
+ public void setFocus() {
+ if (this.treeViewer != null) {
+ treeViewer.getControl().setFocus();
+ }
+ }
+
+ public void addSelectionChangedListener(ISelectionChangedListener listener) {
+ this.selectionProvider.addSelectionChangedListener(listener);
+ }
+
+ public ISelection getSelection() {
+ return this.selectionProvider.getSelection();
+ }
+
+ /**
+ * Returns the TreeViewer associated with this page. May return null,
+ * if VexPlugin has not yet loaded its configuration.
+ */
+ public TreeViewer getTreeViewer() {
+ return this.treeViewer;
+ }
+
+ public void removeSelectionChangedListener(
+ ISelectionChangedListener listener) {
+ this.selectionProvider.removeSelectionChangedListener(listener);
+
+ }
+
+ public void setSelection(ISelection selection) {
+ this.selectionProvider.setSelection(selection);
+ }
+
+ //===================================================== PRIVATE
+
+ private Composite composite;
+
+ private Label label;
+ private TreeViewer treeViewer;
+
+ private VexEditor vexEditor;
+
+ private IOutlineProvider outlineProvider;
+
+ private SelectionProvider selectionProvider = new SelectionProvider();
+
+ private void showLabel(String message) {
+
+ if (this.treeViewer != null) {
+ this.treeViewer.removeSelectionChangedListener(this.selectionListener);
+ this.treeViewer.getTree().dispose();
+ this.treeViewer = null;
+ }
+
+ if (this.label == null) {
+ this.label = new Label(this.composite, SWT.NONE);
+ this.label.setText(message);
+ this.composite.layout(true);
+ }
+
+ this.label.setText(message);
+ }
+
+ private void showTreeViewer() {
+
+ if (this.treeViewer != null) {
+ return;
+ }
+
+ if (this.label != null) {
+ this.label.dispose();
+ this.label = null;
+ }
+
+ this.treeViewer = new TreeViewer(this.composite, SWT.NONE);
+ this.composite.layout();
+
+ DocumentType doctype = this.vexEditor.getDocumentType();
+
+ if (doctype == null) {
+ return;
+ }
+
+ String ns = doctype.getConfig().getUniqueIdentifer();
+ Bundle bundle = Platform.getBundle(ns);
+ String providerClassName = doctype.getOutlineProvider();
+ if (bundle != null && providerClassName != null) {
+ try {
+ Class clazz = bundle.loadClass(providerClassName);
+ this.outlineProvider = (IOutlineProvider) clazz.newInstance();
+ } catch (Exception ex) {
+ String message = Messages.getString("DocumentOutlinePage.loadingError"); //$NON-NLS-1$
+ VexPlugin.getInstance().log(IStatus.WARNING,
+ MessageFormat.format(message, new Object[] { providerClassName, ns, ex }));
+ }
+ }
+
+ if (this.outlineProvider == null) {
+ this.outlineProvider = new DefaultOutlineProvider();
+ }
+
+
+ this.outlineProvider.init(this.vexEditor);
+
+ this.treeViewer.setContentProvider(this.outlineProvider.getContentProvider());
+ this.treeViewer.setLabelProvider(this.outlineProvider.getLabelProvider());
+ this.treeViewer.setAutoExpandLevel(2);
+
+ this.treeViewer.setInput(this.vexEditor.getVexWidget().getDocument());
+
+ this.treeViewer.addSelectionChangedListener(this.selectionListener);
+
+ }
+
+ /**
+ * Receives selection changed events from both our TreeViewer and
+ * the VexWidget. Generally, we use this to synchronize the selection
+ * between the two, but we also do the following...
+ *
+ * - when a notification comes from VexWidget, we create the treeViewer
+ * if needed (that is, if the part was created before VexPlugin was
+ * done loading its configuration.
+ *
+ * - notifications from the TreeViewer are passed on to our
+ * SelectionChangedListeners.
+ */
+ private ISelectionChangedListener selectionListener = new ISelectionChangedListener() {
+ public void selectionChanged(SelectionChangedEvent event) {
+
+ if (event.getSource() instanceof VexWidget) {
+
+ VexWidget vexWidget = (VexWidget) event.getSource();
+ if (vexWidget.isFocusControl() && getTreeViewer() != null) {
+ Element element = vexWidget.getCurrentElement();
+ Element outlineElement = outlineProvider.getOutlineElement(element);
+ getTreeViewer().refresh(outlineElement);
+ getTreeViewer().setSelection(new StructuredSelection(outlineElement), true);
+ }
+
+ } else {
+
+ // it's our tree control being selected
+ TreeViewer treeViewer = (TreeViewer) event.getSource();
+ if (treeViewer.getTree().isFocusControl()) {
+ TreeItem[] selected = treeViewer.getTree().getSelection();
+ if (selected.length > 0) {
+
+ Element element = (Element) selected[0].getData();
+ VexWidget vexWidget = vexEditor.getVexWidget();
+
+ // Moving to the end of the element first is a cheap
+ // way to make sure we end up with the
+ // caret at the top of the viewport
+ vexWidget.moveTo(element.getEndOffset());
+ vexWidget.moveTo(element.getStartOffset() + 1);
+
+ }
+ }
+ }
+ }
+ };
+
+ private IVexEditorListener vexEditorListener = new IVexEditorListener() {
+
+ public void documentLoaded(VexEditorEvent event) {
+ showTreeViewer();
+ }
+
+ public void documentUnloaded(VexEditorEvent event) {
+ showLabel(Messages.getString("DocumentOutlinePage.reloading")); //$NON-NLS-1$
+ }
+
+ };
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/editor/DocumentPerspective.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/editor/DocumentPerspective.java
new file mode 100644
index 0000000..d3460d4
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/editor/DocumentPerspective.java
@@ -0,0 +1,63 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.ui.internal.editor;
+
+import org.eclipse.ui.IFolderLayout;
+import org.eclipse.ui.IPageLayout;
+import org.eclipse.ui.IPerspectiveFactory;
+
+
+/**
+ * Implements the Document perspective.
+ */
+public class DocumentPerspective implements IPerspectiveFactory {
+
+ public void createInitialLayout(IPageLayout layout) {
+ defineActions(layout);
+ defineLayout(layout);
+ }
+
+ /**
+ * Defines the initial actions for a page.
+ */
+ public void defineActions(IPageLayout layout) {
+ // Add "new wizards".
+ layout.addNewWizardShortcut("org.eclipse.wst.xml.vex.ui.NewDocumentWizard");//$NON-NLS-1$
+ layout.addNewWizardShortcut("org.eclipse.ui.wizards.new.folder");//$NON-NLS-1$
+ layout.addNewWizardShortcut("org.eclipse.ui.wizards.new.file");//$NON-NLS-1$
+
+ layout.addShowViewShortcut(IPageLayout.ID_RES_NAV);
+ layout.addShowViewShortcut(IPageLayout.ID_OUTLINE);
+ layout.addShowViewShortcut(IPageLayout.ID_PROP_SHEET);
+
+// layout.addActionSet(IPageLayout.ID_NAVIGATE_ACTION_SET);
+ }
+
+ /**
+ * Defines the initial layout for a page.
+ */
+ public void defineLayout(IPageLayout layout) {
+
+ String editorArea = layout.getEditorArea();
+
+ IFolderLayout topLeft = layout.createFolder("topLeft", IPageLayout.LEFT, (float)0.2, editorArea);//$NON-NLS-1$
+ topLeft.addView(IPageLayout.ID_RES_NAV);
+
+ IFolderLayout topRight = layout.createFolder("topRight", IPageLayout.RIGHT, (float)0.75, editorArea);//$NON-NLS-1$
+ topRight.addView(IPageLayout.ID_OUTLINE);
+
+ IFolderLayout bottomRight = layout.createFolder("bottomRight", IPageLayout.BOTTOM, (float)0.5, "topRight");//$NON-NLS-1$ //$NON-NLS-2$
+ bottomRight.addView(IPageLayout.ID_PROP_SHEET);
+
+
+ }
+
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/editor/DocumentTypeSelectionDialog.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/editor/DocumentTypeSelectionDialog.java
new file mode 100644
index 0000000..5de725e
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/editor/DocumentTypeSelectionDialog.java
@@ -0,0 +1,139 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.ui.internal.editor;
+
+import java.text.MessageFormat;
+import java.util.Arrays;
+
+
+import org.eclipse.jface.dialogs.MessageDialog;
+import org.eclipse.jface.viewers.IStructuredSelection;
+import org.eclipse.jface.viewers.ListViewer;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.MouseEvent;
+import org.eclipse.swt.events.MouseListener;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.widgets.Button;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.swt.widgets.List;
+import org.eclipse.swt.widgets.Shell;
+import org.eclipse.wst.xml.vex.ui.internal.config.DocumentType;
+
+/**
+ * Dialog presented to the user to select a document type it cannot be
+ * determined from the document being opened.
+ */
+public class DocumentTypeSelectionDialog extends MessageDialog {
+
+ /**
+ * Class constructor.
+ * @param parentShell Parent Shell with respect to which this dialog is modal.
+ */
+ protected DocumentTypeSelectionDialog(Shell parentShell, String title, String message) {
+ super(parentShell, title, null, message, MessageDialog.QUESTION, new String[] { Messages.getString("DocumentTypeSelectionDialog.ok"), Messages.getString("DocumentTypeSelectionDialog.cancel") }, 0); //$NON-NLS-1$ //$NON-NLS-2$
+ this.setShellStyle(SWT.RESIZE);
+ }
+
+ /**
+ * Creates a new instance of the dialog. The caller must call open() on the
+ * returned dialog to prompt the user. The open() method blocks until the
+ * user has closed the window. Once open() returns, the caller should
+ * call getDoctype() to get the selected doctype (or null if the dialog
+ * was canceled.
+ *
+ * @param parentShell Parent Shell of the dialog.
+ * @param publicId Public ID of the document being opened, or null
+ * if the document does not have a PUBLIC DOCTYPE declaration.
+ */
+ public static DocumentTypeSelectionDialog create(Shell parentShell, String publicId) {
+ String message;
+
+ if (publicId == null) {
+ message = Messages.getString("DocumentTypeSelectionDialog.noDoctype"); //$NON-NLS-1$
+ } else {
+ message = Messages.getString("DocumentTypeSelectionDialog.unknownDoctype"); //$NON-NLS-1$
+ }
+
+ return new DocumentTypeSelectionDialog(parentShell, Messages.getString("DocumentTypeSelectionDialog.selectDoctype"), //$NON-NLS-1$
+ MessageFormat.format(message, new Object[] { publicId }));
+ }
+
+ protected Control createCustomArea(Composite parent) {
+
+ this.typeList = new ListViewer(parent, SWT.SINGLE | SWT.H_SCROLL | SWT.V_SCROLL );
+
+ List list = typeList.getList();
+
+ list.addMouseListener(this.mouseListener);
+
+ GridData gd = new GridData();
+ gd.grabExcessHorizontalSpace = true;
+ gd.grabExcessVerticalSpace = true;
+ gd.horizontalAlignment = GridData.FILL;
+ gd.verticalAlignment = GridData.FILL;
+ gd.heightHint = 120;
+ list.setLayoutData(gd);
+
+ this.alwaysUseButton = new Button(parent, SWT.CHECK);
+ this.alwaysUseButton.setText(Messages.getString("DocumentTypeSelectionDialog.alwaysUse")); //$NON-NLS-1$
+
+ DocumentType[] doctypes = DocumentType.getDocumentTypesWithStyles();
+ Arrays.sort(doctypes);
+ this.typeList.add(doctypes);
+
+ return list;
+ }
+
+ protected void buttonPressed(int buttonId) {
+ if (buttonId == 0) {
+ IStructuredSelection selection = (IStructuredSelection) this.typeList.getSelection();
+ this.doctype = (DocumentType) selection.getFirstElement();
+ this.alwaysUseThisDoctype = this.alwaysUseButton.getSelection();
+ }
+ super.buttonPressed(buttonId);
+ }
+
+ /**
+ * Returns the document type selected by the user, or null if none
+ * was selected.
+ */
+ public DocumentType getDoctype() {
+ return this.doctype;
+ }
+
+ /**
+ * Returns true if Vex should always use this document type for the
+ * selected file.
+ */
+ public boolean alwaysUseThisDoctype() {
+ return this.alwaysUseThisDoctype;
+ }
+
+ //======================================================= PRIVATE
+
+ private DocumentType doctype;
+ private boolean alwaysUseThisDoctype;
+
+ private ListViewer typeList;
+ private Button alwaysUseButton;
+
+ private MouseListener mouseListener = new MouseListener() {
+ public void mouseDoubleClick(MouseEvent e) {
+ buttonPressed(0);
+ }
+ public void mouseDown(MouseEvent e) {
+ }
+ public void mouseUp(MouseEvent e) {
+ }
+ };
+
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/editor/DocumentTypeSelectionPage.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/editor/DocumentTypeSelectionPage.java
new file mode 100644
index 0000000..e69e3e7
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/editor/DocumentTypeSelectionPage.java
@@ -0,0 +1,214 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.ui.internal.editor;
+
+import java.util.Arrays;
+import java.util.Set;
+
+
+import org.eclipse.jface.dialogs.IDialogSettings;
+import org.eclipse.jface.wizard.WizardPage;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.SelectionEvent;
+import org.eclipse.swt.events.SelectionListener;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.widgets.Combo;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Label;
+import org.eclipse.wst.xml.vex.core.internal.dom.Validator;
+import org.eclipse.wst.xml.vex.ui.internal.VexPlugin;
+import org.eclipse.wst.xml.vex.ui.internal.config.DocumentType;
+
+/**
+ * Wizard page for selecting the document type and root element for the
+ * new document.
+ */
+public class DocumentTypeSelectionPage extends WizardPage {
+
+ private static final String SETTINGS_PUBLIC_ID = "publicId"; //$NON-NLS-1$
+ private static final String SETTINGS_ROOT_ELEMENT_PREFIX = "root."; //$NON-NLS-1$
+
+ /**
+ * Class constructor.
+ */
+ public DocumentTypeSelectionPage() {
+ super(Messages.getString("DocumentTypeSelectionPage.pageName")); //$NON-NLS-1$
+ this.setPageComplete(false);
+
+ IDialogSettings rootSettings = VexPlugin.getInstance().getDialogSettings();
+ this.settings = rootSettings.getSection("newDocument"); //$NON-NLS-1$
+ if (this.settings == null) {
+ this.settings = rootSettings.addNewSection("newDocument"); //$NON-NLS-1$
+ }
+
+ this.doctypes = DocumentType.getDocumentTypesWithStyles();
+ Arrays.sort(this.doctypes);
+ }
+
+ public void createControl(Composite parent) {
+
+ Composite pane = new Composite(parent, SWT.NONE);
+ GridLayout layout = new GridLayout();
+ layout.numColumns = 2;
+ pane.setLayout(layout);
+ GridData gd;
+
+ Label label = new Label(pane, SWT.NONE);
+ label.setText(Messages.getString("DocumentTypeSelectionPage.doctype")); //$NON-NLS-1$
+
+ this.typeCombo = new Combo(pane, SWT.DROP_DOWN | SWT.READ_ONLY);
+ gd = new GridData();
+ gd.grabExcessHorizontalSpace = true;
+ gd.horizontalAlignment = GridData.FILL;
+ this.typeCombo.setLayoutData(gd);
+ this.typeCombo.addSelectionListener(typeComboSelectionListener);
+
+ label = new Label(pane, SWT.NONE);
+ label.setText(Messages.getString("DocumentTypeSelectionPage.rootElement")); //$NON-NLS-1$
+ this.setControl(pane);
+
+ this.elementCombo = new Combo(pane, SWT.DROP_DOWN | SWT.READ_ONLY);
+ gd = new GridData();
+ gd.grabExcessHorizontalSpace = true;
+ gd.horizontalAlignment = GridData.FILL;
+ this.elementCombo.setLayoutData(gd);
+ this.elementCombo.addSelectionListener(elementComboSelectionListener);
+
+ String publicId = this.settings.get(SETTINGS_PUBLIC_ID);
+ int initSelection = -1;
+ String[] typeNames = new String[this.doctypes.length];
+ for (int i = 0; i < this.doctypes.length; i++) {
+ typeNames[i] = this.doctypes[i].getName();
+ if (this.doctypes[i].getPublicId().equals(publicId)) {
+ initSelection = i;
+ }
+ }
+
+ this.typeCombo.setItems(typeNames);
+
+ if (initSelection != -1) {
+ this.typeCombo.select(initSelection);
+ // calling select() does not fire the selection listener,
+ // so we update it manually
+ this.updateElementCombo();
+ }
+
+ this.setTitle(Messages.getString("DocumentTypeSelectionPage.title")); //$NON-NLS-1$
+ this.setDescription(Messages.getString("DocumentTypeSelectionPage.desc")); //$NON-NLS-1$
+ }
+
+ /**
+ * Returns the selected document type.
+ */
+ public DocumentType getDocumentType() {
+ int i = this.typeCombo.getSelectionIndex();
+ if (i == -1) {
+ return null;
+ } else {
+ return this.doctypes[i];
+ }
+ }
+
+ /**
+ * Returns the selected name of the root element.
+ */
+ public String getRootElementName() {
+ return this.elementCombo.getText();
+ }
+
+ /**
+ * Called from the wizard's performFinal method to save the settings for
+ * this page.
+ */
+ public void saveSettings() {
+ DocumentType doctype = this.getDocumentType();
+ if (doctype != null) {
+ this.settings.put(SETTINGS_PUBLIC_ID, doctype.getPublicId());
+ String key = SETTINGS_ROOT_ELEMENT_PREFIX + doctype.getPublicId();
+ this.settings.put(key, this.getRootElementName());
+ }
+ }
+
+ //============================================================== PRIVATE
+
+ private IDialogSettings settings;
+ private DocumentType[] doctypes;
+ private Combo typeCombo;
+ private Combo elementCombo;
+
+ /**
+ * Update the elementCombo to reflect elements in the currently selected
+ * type.
+ */
+ private void updateElementCombo() {
+ int index = this.typeCombo.getSelectionIndex();
+ DocumentType dt = this.doctypes[index];
+ this.elementCombo.removeAll();
+
+ String[] roots = getDocumentType().getRootElements();
+ String selectedRoot = null;
+
+ if (roots.length == 0) {
+ Validator validator = dt.getValidator();
+ if (validator != null) {
+ Set set = validator.getValidRootElements();
+ roots = (String[]) set.toArray(new String[set.size()]);
+ }
+ } else {
+ selectedRoot = roots[0];
+ }
+
+
+ Arrays.sort(roots);
+ this.elementCombo.setItems(roots);
+
+ if (selectedRoot == null) {
+ // Restore the last used root element
+ String key = SETTINGS_ROOT_ELEMENT_PREFIX + dt.getPublicId();
+ selectedRoot = this.settings.get(key);
+ }
+
+ this.setPageComplete(false);
+ if (selectedRoot != null) {
+ for (int i = 0; i < roots.length; i++) {
+ if (roots[i].equals(selectedRoot)) {
+ this.elementCombo.select(i);
+ this.setPageComplete(true);
+ break;
+ }
+ }
+ }
+ }
+
+ /**
+ * Sets the root element combo box when the document type combo box is selected.
+ */
+ private SelectionListener typeComboSelectionListener = new SelectionListener() {
+ public void widgetSelected(SelectionEvent e) {
+ updateElementCombo();
+ }
+ public void widgetDefaultSelected(SelectionEvent e) {
+ }
+ };
+
+ /**
+ * When a root element is selected, mark the page as complete.
+ */
+ private SelectionListener elementComboSelectionListener = new SelectionListener() {
+ public void widgetSelected(SelectionEvent e) {
+ setPageComplete(true);
+ }
+ public void widgetDefaultSelected(SelectionEvent e) {
+ }
+ };
+
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/editor/ElementPropertySource.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/editor/ElementPropertySource.java
new file mode 100644
index 0000000..1df5c9d
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/editor/ElementPropertySource.java
@@ -0,0 +1,164 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.ui.internal.editor;
+
+
+import org.eclipse.ui.views.properties.ComboBoxPropertyDescriptor;
+import org.eclipse.ui.views.properties.IPropertyDescriptor;
+import org.eclipse.ui.views.properties.IPropertySource2;
+import org.eclipse.ui.views.properties.PropertyDescriptor;
+import org.eclipse.ui.views.properties.TextPropertyDescriptor;
+import org.eclipse.wst.xml.vex.core.internal.dom.AttributeDefinition;
+import org.eclipse.wst.xml.vex.core.internal.dom.DocumentValidationException;
+import org.eclipse.wst.xml.vex.core.internal.dom.Element;
+import org.eclipse.wst.xml.vex.core.internal.dom.Validator;
+
+
+/**
+ * Property source that treats element attributes as properties.
+ */
+public class ElementPropertySource implements IPropertySource2 {
+
+ /**
+ * Class constructor.
+ * @param element Selected element.
+ * @param validator Validator used to get attribute definitions for the element.
+ * @param multi True if multiple elements are selected. In this case
+ * the "id" attribute will not be editable.
+ */
+ public ElementPropertySource(Element element, Validator validator, boolean multi) {
+ this.element = element;
+ this.validator = validator;
+ this.multi = multi;
+ }
+
+ public Object getEditableValue() {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ public IPropertyDescriptor[] getPropertyDescriptors() {
+
+ // note that elements from DocumentFragments don't have access
+ // to their original document, so we get it from the VexWidget
+ AttributeDefinition[] attrDefs = this.validator.getAttributeDefinitions(this.element.getName());
+ IPropertyDescriptor[] pds = new IPropertyDescriptor[attrDefs.length];
+ for (int i = 0; i < attrDefs.length; i++) {
+ AttributeDefinition def = attrDefs[i];
+ if (this.multi && def.getName().equals(ATTR_ID)) {
+ pds[i] = new PropertyDescriptor(def.getName(), def.getName());
+ } else if (def.isFixed()) {
+ pds[i] = new PropertyDescriptor(def.getName(), def.getName());
+ } else if (def.getType() == AttributeDefinition.Type.ENUMERATION) {
+ pds[i] = new ComboBoxPropertyDescriptor(def.getName(), def.getName(), this.getEnumValues(def));
+ } else {
+ pds[i] = new TextPropertyDescriptor(def.getName(), def.getName());
+ }
+ }
+ return pds;
+ }
+
+ public Object getPropertyValue(Object id) {
+
+ if (this.multi && id.equals(ATTR_ID)) {
+ return Messages.getString("ElementPropertySource.multiple"); //$NON-NLS-1$
+ }
+
+ // note that elements from DocumentFragments don't have access
+ // to their original document, so we get it from the VexWidget
+ AttributeDefinition def = this.validator.getAttributeDefinition(this.element.getName(), (String) id);
+ String value = this.element.getAttribute((String) id);
+ if (value == null) {
+ value = def.getDefaultValue();
+ if (value == null) {
+ value = ""; //$NON-NLS-1$
+ }
+ }
+
+ if (def.getType() == AttributeDefinition.Type.ENUMERATION) {
+ String[] values = this.getEnumValues(def);
+ for (int i = 0; i < values.length; i++) {
+ if (values[i].equals(value)) {
+ return new Integer(i);
+ }
+ }
+ return new Integer(0); // TODO: if the actual value is not
+ // in the list, we should probably
+ // add it
+ } else {
+ return value;
+ }
+ }
+
+ public boolean isPropertySet(Object id) {
+ // TODO Auto-generated method stub
+ return false;
+ }
+
+ public void resetPropertyValue(Object id) {
+ // TODO Auto-generated method stub
+
+ }
+
+ public void setPropertyValue(Object id, Object value) {
+
+ try {
+ // note that elements from DocumentFragments don't have access
+ // to their original document, so we get it from the VexWidget
+ AttributeDefinition def = this.validator.getAttributeDefinition(
+ this.element.getName(), (String) id);
+
+ if (def.getType() == AttributeDefinition.Type.ENUMERATION) {
+ int i = ((Integer) value).intValue();
+ String s = this.getEnumValues(def)[i];
+ if (!def.isRequired() && s.equals("")) { //$NON-NLS-1$
+ this.element.removeAttribute(def.getName());
+ } else {
+ this.element.setAttribute(def.getName(), s);
+ }
+ } else {
+ String s = (String) value;
+ if (s.equals("")) { //$NON-NLS-1$
+ this.element.removeAttribute(def.getName());
+ } else {
+ this.element.setAttribute(def.getName(), s);
+ }
+ }
+ } catch (DocumentValidationException e) {
+ }
+ }
+
+ public boolean isPropertyResettable(Object id) {
+ // TODO Auto-generated method stub
+ return true;
+ }
+
+ //======================================== PRIVATE
+
+ private static final String ATTR_ID = "id"; //$NON-NLS-1$
+
+ private Element element;
+ private Validator validator;
+ private boolean multi;
+
+ private String[] getEnumValues(AttributeDefinition def) {
+ String[] values = def.getValues();
+ if (def.isRequired()) {
+ return values;
+ } else {
+ String[] values2 = new String[values.length+1];
+ values2[0] = ""; //$NON-NLS-1$
+ System.arraycopy(values, 0, values2, 1, values.length);
+ return values2;
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/editor/IOutlineProvider.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/editor/IOutlineProvider.java
new file mode 100644
index 0000000..096f24b
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/editor/IOutlineProvider.java
@@ -0,0 +1,50 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.ui.internal.editor;
+
+
+import org.eclipse.jface.viewers.IBaseLabelProvider;
+import org.eclipse.jface.viewers.ITreeContentProvider;
+import org.eclipse.wst.xml.vex.core.internal.dom.Element;
+
+/**
+ * Implemented by objects that can provide a document outline.
+ */
+public interface IOutlineProvider {
+
+ /**
+ * Initialize this outline provider. This method is guaranteed to be called
+ * befor any other in this class. The document has been fully created
+ * by the time this method is called, so it is acceptable to access
+ * the Vex Widget and its associated stylesheet and document.
+ *
+ * @param editor VexEditor with which this outline page is associated.
+ */
+ public void init(VexEditor editor);
+
+ /**
+ * Returns the content provider that supplies elements representing
+ * the document outline.
+ */
+ public ITreeContentProvider getContentProvider();
+
+ /**
+ * Returns the label provider for the outline.
+ */
+ public IBaseLabelProvider getLabelProvider();
+
+ /**
+ * Returns the outline element closest to the given child. If
+ * <code>child</code> is an outline element, it is returned directly.
+ * @param child Element for which to find the outline element.
+ */
+ public Element getOutlineElement(Element child);
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/editor/IVexEditorListener.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/editor/IVexEditorListener.java
new file mode 100644
index 0000000..3e3a9fb
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/editor/IVexEditorListener.java
@@ -0,0 +1,29 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.ui.internal.editor;
+
+/**
+ * Event interface through which VexEditor events are published.
+ */
+public interface IVexEditorListener {
+
+ /**
+ * Called after the editor has successfully loaded a document.
+ */
+ public void documentLoaded(VexEditorEvent event);
+
+ /**
+ * Called before the editor unloads a document. Note that the editor may
+ * be disposing of the corresponding VexWidget, so any registered
+ * listeners on the widget should unregister in this event.
+ */
+ public void documentUnloaded(VexEditorEvent event);
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/editor/InsertAssistant.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/editor/InsertAssistant.java
new file mode 100644
index 0000000..f557ac2
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/editor/InsertAssistant.java
@@ -0,0 +1,31 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.ui.internal.editor;
+
+
+import org.eclipse.jface.action.IAction;
+import org.eclipse.wst.xml.vex.core.internal.swt.VexWidget;
+
+
+/**
+ * Content assistant that shows valid elements to be inserted at the current
+ * point.
+ */
+public class InsertAssistant extends ContentAssistant {
+
+ public IAction[] getActions(VexWidget vexWidget) {
+ return vexWidget.getValidInsertActions();
+ }
+
+ public String getTitle(VexWidget vexWidget) {
+ return Messages.getString("InsertAssistant.title"); //$NON-NLS-1$
+ }
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/editor/Messages.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/editor/Messages.java
new file mode 100644
index 0000000..8b7ce39
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/editor/Messages.java
@@ -0,0 +1,49 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.ui.internal.editor;
+
+import java.text.MessageFormat;
+import java.util.MissingResourceException;
+import java.util.ResourceBundle;
+
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.wst.xml.vex.ui.internal.VexPlugin;
+
+/**
+ * Handler for language-specific strings in Vex.
+ */
+public class Messages {
+
+ private static ResourceBundle resources;
+
+ private Messages() {
+ }
+
+ /**
+ * Returns the language-specific string for the given key,
+ * or the key itself if not found.
+ */
+ public static String getString(String key) {
+ if (resources == null) {
+ resources = ResourceBundle.getBundle("org.eclipse.wst.xml.vex.ui.internal.editor.messages"); //$NON-NLS-1$
+ }
+
+ try {
+ return resources.getString(key);
+ } catch (MissingResourceException ex) {
+ String message = Messages.getString("Messages.cantFindResource"); //$NON-NLS-1$
+ VexPlugin.getInstance().log(IStatus.WARNING,
+ MessageFormat.format(message, new Object[] { key }));
+ return key;
+ }
+ }
+
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/editor/MorphAssistant.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/editor/MorphAssistant.java
new file mode 100644
index 0000000..5bd1e5b
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/editor/MorphAssistant.java
@@ -0,0 +1,36 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.ui.internal.editor;
+
+import java.text.MessageFormat;
+
+
+import org.eclipse.jface.action.IAction;
+import org.eclipse.wst.xml.vex.core.internal.swt.VexWidget;
+
+
+/**
+ * Content assistant that shows valid elements to be inserted at the current
+ * point.
+ */
+public class MorphAssistant extends ContentAssistant {
+
+ public IAction[] getActions(VexWidget vexWidget) {
+ return vexWidget.getValidMorphActions();
+ }
+
+ public String getTitle(VexWidget vexWidget) {
+ String message = Messages.getString("ChangeElementAction.dynamic.label"); //$NON-NLS-1$
+ String name = vexWidget.getCurrentElement().getName();
+ return MessageFormat.format(message, new Object[] { name });
+ }
+
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/editor/NewDocumentWizard.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/editor/NewDocumentWizard.java
new file mode 100644
index 0000000..df5c096
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/editor/NewDocumentWizard.java
@@ -0,0 +1,202 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.ui.internal.editor;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.text.MessageFormat;
+
+
+import org.eclipse.core.resources.IFile;
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.jface.dialogs.MessageDialog;
+import org.eclipse.jface.viewers.IStructuredSelection;
+import org.eclipse.ui.IEditorDescriptor;
+import org.eclipse.ui.IEditorRegistry;
+import org.eclipse.ui.IFileEditorMapping;
+import org.eclipse.ui.IWorkbench;
+import org.eclipse.ui.IWorkbenchPage;
+import org.eclipse.ui.IWorkbenchWindow;
+import org.eclipse.ui.PlatformUI;
+import org.eclipse.ui.ide.IDE;
+import org.eclipse.ui.internal.registry.EditorDescriptor;
+import org.eclipse.ui.internal.registry.EditorRegistry;
+import org.eclipse.ui.internal.registry.FileEditorMapping;
+import org.eclipse.ui.wizards.newresource.BasicNewResourceWizard;
+import org.eclipse.wst.xml.vex.core.internal.dom.Document;
+import org.eclipse.wst.xml.vex.core.internal.dom.DocumentWriter;
+import org.eclipse.wst.xml.vex.core.internal.dom.RootElement;
+import org.eclipse.wst.xml.vex.core.internal.widget.CssWhitespacePolicy;
+import org.eclipse.wst.xml.vex.ui.internal.VexPlugin;
+import org.eclipse.wst.xml.vex.ui.internal.config.Style;
+
+/**
+ * Wizard for creating a new Vex document.
+ */
+public class NewDocumentWizard extends BasicNewResourceWizard {
+
+ public void addPages() {
+ this.typePage = new DocumentTypeSelectionPage();
+ this.filePage = new DocumentFileCreationPage("filePage", this.getSelection()); //$NON-NLS-1$
+ addPage(typePage);
+ addPage(filePage);
+ }
+
+ public void init(
+ IWorkbench workbench,
+ IStructuredSelection currentSelection) {
+
+ super.init(workbench, currentSelection);
+ this.setWindowTitle(Messages.getString("NewDocumentWizard.title")); //$NON-NLS-1$
+ }
+
+ public boolean performFinish() {
+ try {
+ RootElement root = new RootElement(this.typePage.getRootElementName());
+ Document doc = new Document(root);
+ doc.setPublicID(this.typePage.getDocumentType().getPublicId());
+ doc.setSystemID(this.typePage.getDocumentType().getSystemId());
+
+
+ Style style = VexEditor.findStyleForDoctype(doc.getPublicID());
+ if (style == null) {
+ MessageDialog.openError(this.getShell(), Messages.getString("NewDocumentWizard.noStyles.title"), Messages.getString("NewDocumentWizard.noStyles.message")); //$NON-NLS-1$ //$NON-NLS-2$
+ return false;
+ // TODO: don't allow selection of types with no stylesheets
+ }
+
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ DocumentWriter writer = new DocumentWriter();
+ writer.setWhitespacePolicy(new CssWhitespacePolicy(style.getStyleSheet()));
+ writer.write(doc, baos);
+ baos.close();
+ ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
+
+ filePage.setInitialContents(bais);
+ IFile file = filePage.createNewFile();
+ IDE.setDefaultEditor(file, VexEditor.ID);
+ this.selectAndReveal(file);
+
+ registerEditorForFilename("*." + file.getFileExtension(), VexEditor.ID); //$NON-NLS-1$
+
+ // Open editor on new file.
+ IWorkbenchWindow dw = getWorkbench().getActiveWorkbenchWindow();
+ if (dw != null) {
+ IWorkbenchPage page = dw.getActivePage();
+ if (page != null) {
+ IDE.openEditor(page, file, true);
+ }
+ }
+
+ this.typePage.saveSettings();
+
+ return true;
+
+ } catch (Exception ex) {
+ String message = MessageFormat.format(
+ Messages.getString("NewDocumentWizard.errorLoading.message"),
+ new Object[] { filePage.getFileName(), ex.getMessage() });
+ VexPlugin.getInstance().log(IStatus.ERROR, message, ex);
+ MessageDialog.openError(this.getShell(), Messages.getString("NewDocumentWizard.errorLoading.title"), "Unable to create " + filePage.getFileName()); //$NON-NLS-1$ //$NON-NLS-2$
+ return false;
+ }
+ }
+
+ //=========================================================== PRIVATE
+
+ private DocumentTypeSelectionPage typePage;
+ private DocumentFileCreationPage filePage;
+
+
+ /**
+ * Register an editor to use for files with the given filename.
+ *
+ * NOTE: this method uses internal, undocumented Eclipse functionality.
+ * It may therefore break in a future version of Eclipse.
+ *
+ * @param fileName Filename to be registered. Use the form "*.ext" to register
+ * all files with a given extension.
+ * @param editorId ID of the editor to use for the given filename.
+ */
+ private static void registerEditorForFilename(String fileName, String editorId) {
+
+ EditorDescriptor ed = getEditorDescriptor(editorId);
+ if (ed == null) {
+ return;
+ }
+
+ IEditorRegistry reg = PlatformUI.getWorkbench().getEditorRegistry();
+ EditorRegistry ereg = (EditorRegistry) reg;
+ FileEditorMapping[] mappings = (FileEditorMapping[]) ereg.getFileEditorMappings();
+ FileEditorMapping mapping = null;
+ for (int i = 0; i < mappings.length; i++) {
+ IFileEditorMapping fem = mappings[i];
+ if (fem.getLabel().equals(fileName)) {
+ mapping = (FileEditorMapping) fem;
+ break;
+ }
+ }
+
+ if (mapping != null) {
+ // found mapping for fileName
+ // make sure it includes our editor
+ IEditorDescriptor[] editors = mapping.getEditors();
+ for (int i = 0; i < editors.length; i++) {
+ if (editors[i].getId().equals(editorId)) {
+ // already mapped
+ return;
+ }
+ }
+
+ // editor not in the list, so add it
+ mapping.addEditor(ed);
+ ereg.setFileEditorMappings(mappings);
+ ereg.saveAssociations();
+
+ } else {
+ // no mapping found for the filename
+ // let's add one
+ String name = null;
+ String ext = null;
+ int iDot = fileName.lastIndexOf('.');
+ if (iDot == -1) {
+ name = fileName;
+ } else {
+ name = fileName.substring(0, iDot);
+ ext = fileName.substring(iDot + 1);
+ }
+
+ mapping = new FileEditorMapping(name, ext);
+ FileEditorMapping[] newMappings = new FileEditorMapping[mappings.length + 1];
+ mapping.addEditor(ed);
+
+ System.arraycopy(mappings, 0, newMappings, 0, mappings.length);
+ newMappings[mappings.length] = mapping;
+ ereg.setFileEditorMappings(newMappings);
+ ereg.saveAssociations();
+ }
+
+ }
+
+ /**
+ * Return the IEditorDescriptor for the given editor ID.
+ */
+ private static EditorDescriptor getEditorDescriptor(String editorId) {
+ EditorRegistry reg = (EditorRegistry) PlatformUI.getWorkbench().getEditorRegistry();
+ IEditorDescriptor[] editors = reg.getSortedEditorsFromPlugins();
+ for (int i = 0; i < editors.length; i++) {
+ if (editors[i].getId().equals(editorId)) {
+ return (EditorDescriptor) editors[i];
+ }
+ }
+ return null;
+ }
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/editor/SelectionProvider.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/editor/SelectionProvider.java
new file mode 100644
index 0000000..f2ea175
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/editor/SelectionProvider.java
@@ -0,0 +1,62 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.ui.internal.editor;
+
+
+import org.eclipse.jface.viewers.ISelection;
+import org.eclipse.jface.viewers.ISelectionChangedListener;
+import org.eclipse.jface.viewers.ISelectionProvider;
+import org.eclipse.jface.viewers.SelectionChangedEvent;
+import org.eclipse.wst.xml.vex.core.internal.core.ListenerList;
+
+/**
+ * Implementation of ISelectionProvider. This class is also an
+ * ISelectionChangedListener; any events received by selectionChanged are
+ * relayed to registered listeners.
+ */
+public class SelectionProvider implements ISelectionProvider,
+ ISelectionChangedListener {
+
+ public void addSelectionChangedListener(ISelectionChangedListener listener) {
+ this.listeners.add(listener);
+ }
+
+ /**
+ * Fire a SelectionChangedEvent to all registered listeners.
+ * @param e Event to be passed to the listeners' selectionChanged method.
+ */
+ public void fireSelectionChanged(SelectionChangedEvent e) {
+ this.selection = e.getSelection();
+ this.listeners.fireEvent("selectionChanged", e); //$NON-NLS-1$
+ }
+
+ public ISelection getSelection() {
+ return this.selection;
+ }
+
+ public void removeSelectionChangedListener(
+ ISelectionChangedListener listener) {
+ this.listeners.remove(listener);
+ }
+
+ public void setSelection(ISelection selection) {
+ this.selection = selection;
+ }
+
+ public void selectionChanged(SelectionChangedEvent event) {
+ this.fireSelectionChanged(event);
+ }
+
+ //===================================================== PRIVATE
+
+ ISelection selection;
+ private ListenerList listeners = new ListenerList(ISelectionChangedListener.class, SelectionChangedEvent.class);
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/editor/VexActionBarContributor.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/editor/VexActionBarContributor.java
new file mode 100644
index 0000000..742a3d7
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/editor/VexActionBarContributor.java
@@ -0,0 +1,310 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.ui.internal.editor;
+
+
+import org.eclipse.jface.action.Action;
+import org.eclipse.jface.action.GroupMarker;
+import org.eclipse.jface.action.IAction;
+import org.eclipse.jface.action.IMenuListener;
+import org.eclipse.jface.action.IMenuManager;
+import org.eclipse.jface.action.MenuManager;
+import org.eclipse.jface.action.Separator;
+import org.eclipse.jface.viewers.ISelection;
+import org.eclipse.ui.IActionBars;
+import org.eclipse.ui.IEditorPart;
+import org.eclipse.ui.ISelectionListener;
+import org.eclipse.ui.IWorkbenchActionConstants;
+import org.eclipse.ui.IWorkbenchPage;
+import org.eclipse.ui.IWorkbenchPart;
+import org.eclipse.ui.actions.ActionFactory;
+import org.eclipse.ui.part.EditorActionBarContributor;
+import org.eclipse.wst.xml.vex.core.internal.action.ActionUtils;
+import org.eclipse.wst.xml.vex.core.internal.action.DeleteColumnAction;
+import org.eclipse.wst.xml.vex.core.internal.action.DeleteRowAction;
+import org.eclipse.wst.xml.vex.core.internal.action.InsertColumnAfterAction;
+import org.eclipse.wst.xml.vex.core.internal.action.InsertColumnBeforeAction;
+import org.eclipse.wst.xml.vex.core.internal.action.InsertRowAboveAction;
+import org.eclipse.wst.xml.vex.core.internal.action.InsertRowBelowAction;
+import org.eclipse.wst.xml.vex.core.internal.action.MoveColumnLeftAction;
+import org.eclipse.wst.xml.vex.core.internal.action.MoveColumnRightAction;
+import org.eclipse.wst.xml.vex.core.internal.action.MoveRowDownAction;
+import org.eclipse.wst.xml.vex.core.internal.action.MoveRowUpAction;
+import org.eclipse.wst.xml.vex.core.internal.dom.DocumentValidationException;
+import org.eclipse.wst.xml.vex.core.internal.swt.VexWidget;
+import org.eclipse.wst.xml.vex.core.internal.widget.IVexWidget;
+import org.eclipse.wst.xml.vex.ui.internal.action.InsertElementAction;
+import org.eclipse.wst.xml.vex.ui.internal.action.VexActionAdapter;
+import org.eclipse.wst.xml.vex.ui.internal.config.Style;
+
+/**
+ * Contribute actions on behalf of the VexEditor.
+ */
+public class VexActionBarContributor extends EditorActionBarContributor {
+
+ public void dispose() {
+ }
+
+ public MenuManager getContextMenuManager() {
+ return this.contextMenuManager;
+ }
+
+ public VexEditor getVexEditor() {
+ return (VexEditor) activeEditor;
+ }
+
+ public VexWidget getVexWidget() {
+ if (activeEditor != null) {
+ return ((VexEditor) activeEditor).getVexWidget();
+ } else {
+ return null;
+ }
+ }
+
+ public void init(IActionBars bars, IWorkbenchPage page) {
+ super.init(bars, page);
+
+ this.contextMenuManager = new MenuManager();
+ this.contextMenuManager.setRemoveAllWhenShown(true);
+ this.contextMenuManager.addMenuListener(this.contextMenuListener);
+
+ page.addSelectionListener(this.selectionListener);
+
+ this.globalCopyAction = ActionFactory.COPY.create(page.getWorkbenchWindow());
+ this.globalCutAction = ActionFactory.CUT.create(page.getWorkbenchWindow());
+ this.globalDeleteAction = ActionFactory.DELETE.create(page.getWorkbenchWindow());
+ this.globalPasteAction = ActionFactory.PASTE.create(page.getWorkbenchWindow());
+ this.globalRedoAction = ActionFactory.REDO.create(page.getWorkbenchWindow());
+ this.globalUndoAction = ActionFactory.UNDO.create(page.getWorkbenchWindow());
+ }
+
+
+ public void setActiveEditor(IEditorPart activeEditor) {
+
+ // This can occur if we have an error loading the editor,
+ // in which case Eclipse provides its own part
+ if (!(activeEditor instanceof VexEditor)) {
+ return;
+ }
+
+ this.activeEditor = activeEditor;
+
+ this.getActionBars().setGlobalActionHandler(
+ ActionFactory.COPY.getId(),
+ this.copyAction);
+
+ this.getActionBars().setGlobalActionHandler(
+ ActionFactory.CUT.getId(),
+ this.cutAction);
+
+ this.getActionBars().setGlobalActionHandler(
+ ActionFactory.DELETE.getId(),
+ this.deleteAction);
+
+ this.getActionBars().setGlobalActionHandler(
+ ActionFactory.PASTE.getId(),
+ this.pasteAction);
+
+ this.getActionBars().setGlobalActionHandler(
+ ActionFactory.REDO.getId(),
+ this.redoAction);
+
+ this.getActionBars().setGlobalActionHandler(
+ ActionFactory.SELECT_ALL.getId(),
+ this.selectAllAction);
+
+ this.getActionBars().setGlobalActionHandler(
+ ActionFactory.UNDO.getId(),
+ this.undoAction);
+
+ this.enableActions();
+ }
+
+ //===================================================== PRIVATE
+
+ private IEditorPart activeEditor;
+
+ private IAction globalCopyAction;
+ private IAction globalCutAction;
+ private IAction globalDeleteAction;
+ private IAction globalPasteAction;
+ private IAction globalRedoAction;
+ private IAction globalUndoAction;
+
+ private IAction copyAction = new CopyAction();
+ private IAction cutAction = new CutAction();
+ private IAction deleteAction = new DeleteAction();
+ private IAction pasteAction = new PasteAction();
+ private IAction redoAction = new RedoAction();
+ private IAction selectAllAction = new SelectAllAction();
+ private IAction undoAction = new UndoAction();
+
+ private MenuManager contextMenuManager;
+ private IMenuListener contextMenuListener = new IMenuListener() {
+
+ public void menuAboutToShow(IMenuManager manager) {
+
+ boolean showTableActions = false;
+ IVexWidget vexWidget = getVexWidget();
+ if (vexWidget != null) {
+ showTableActions = ActionUtils.getSelectedTableRows(vexWidget).getRows() != null;
+ }
+
+ manager.add(globalUndoAction);
+ manager.add(globalRedoAction);
+ manager.add(new Separator());
+ manager.add(new VexActionAdapter(getVexEditor(), new InsertElementAction()));
+
+ if (showTableActions) {
+ manager.add(new Separator());
+ manager.add(new RowMenuManager());
+ manager.add(new ColumnMenuManager());
+ }
+
+ manager.add(new Separator());
+ manager.add(globalCutAction);
+ manager.add(globalCopyAction);
+ manager.add(globalPasteAction);
+ manager.add(new Separator());
+ manager.add(globalDeleteAction);
+ manager.add(new Separator());
+ manager.add(new StyleMenuManager());
+ manager.add(new GroupMarker(IWorkbenchActionConstants.MB_ADDITIONS));
+ }
+
+ };
+
+ private void enableActions() {
+ VexWidget widget = this.getVexWidget();
+ this.copyAction.setEnabled(widget != null && widget.hasSelection());
+ this.cutAction.setEnabled(widget != null && widget.hasSelection());
+ this.deleteAction.setEnabled(widget != null && widget.hasSelection());
+ this.redoAction.setEnabled(widget != null && widget.canRedo());
+ this.undoAction.setEnabled(widget != null && widget.canUndo());
+ }
+
+ private ISelectionListener selectionListener = new ISelectionListener() {
+ public void selectionChanged(IWorkbenchPart part, ISelection selection) {
+ enableActions();
+ }
+ };
+
+ private class CopyAction extends Action {
+ public void run() {
+ getVexWidget().copySelection();
+ }
+ };
+
+
+ private class CutAction extends Action {
+ public void run() {
+ getVexWidget().cutSelection();
+ }
+ }
+
+ private class DeleteAction extends Action {
+ public void run() {
+ getVexWidget().deleteSelection();
+ }
+ };
+
+ private class PasteAction extends Action {
+ public void run() {
+ try {
+ getVexWidget().paste();
+ } catch (DocumentValidationException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+ }
+ };
+
+ private class SelectAllAction extends Action {
+ public void run() {
+ getVexWidget().selectAll();
+ }
+ };
+
+ private class SetStyleAction extends Action {
+ public SetStyleAction(Style style) {
+ super(style.getName(), IAction.AS_RADIO_BUTTON);
+ this.style = style;
+ }
+ public void run() {
+ getVexEditor().setStyle(style);
+ }
+ private Style style;
+ }
+
+ private class StyleMenuManager extends MenuManager {
+ public StyleMenuManager() {
+ super(Messages.getString("VexActionBarContributor.styleMenu.name")); //$NON-NLS-1$
+ final Action noItemsAction = new Action(Messages.getString("VexActionBarContributor.noValidItems")) { public void run() { } }; //$NON-NLS-1$
+ noItemsAction.setEnabled(false);
+ noItemsAction.setChecked(true);
+ this.add(noItemsAction);
+ this.addMenuListener(new IMenuListener() {
+ public void menuAboutToShow(IMenuManager manager) {
+ manager.removeAll();
+ String publicId = getVexWidget().getDocument().getPublicID();
+ Style[] styles = Style.getStylesForDoctype(publicId);
+ for (int i = 0; i < styles.length; i++) {
+ Action action = new SetStyleAction(styles[i]);
+ if (styles[i] == getVexEditor().getStyle()) {
+ action.setChecked(true);
+ }
+ manager.add(action);
+ }
+ }
+ });
+ }
+ }
+
+ private class RowMenuManager extends MenuManager {
+ public RowMenuManager() {
+ super(Messages.getString("VexActionBarContributor.rowMenu.name")); //$NON-NLS-1$
+ this.add(new VexActionAdapter(getVexEditor(), new InsertRowAboveAction()));
+ this.add(new VexActionAdapter(getVexEditor(), new InsertRowBelowAction()));
+ this.add(new VexActionAdapter(getVexEditor(), new DeleteRowAction()));
+ this.add(new VexActionAdapter(getVexEditor(), new MoveRowUpAction()));
+ this.add(new VexActionAdapter(getVexEditor(), new MoveRowDownAction()));
+ }
+ }
+
+ private class ColumnMenuManager extends MenuManager {
+ public ColumnMenuManager() {
+ super(Messages.getString("VexActionBarContributor.columnMenu.name")); //$NON-NLS-1$
+ this.add(new VexActionAdapter(getVexEditor(), new InsertColumnBeforeAction()));
+ this.add(new VexActionAdapter(getVexEditor(), new InsertColumnAfterAction()));
+ this.add(new VexActionAdapter(getVexEditor(), new DeleteColumnAction()));
+ this.add(new VexActionAdapter(getVexEditor(), new MoveColumnLeftAction()));
+ this.add(new VexActionAdapter(getVexEditor(), new MoveColumnRightAction()));
+ }
+ }
+
+ private class RedoAction extends Action {
+ public void run() {
+ if (getVexWidget().canRedo()) {
+ getVexWidget().redo();
+ }
+ }
+ };
+
+ private class UndoAction extends Action {
+ public void run() {
+ if (getVexWidget().canUndo()) {
+ getVexWidget().undo();
+ }
+ }
+ }
+
+
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/editor/VexActionDelegate.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/editor/VexActionDelegate.java
new file mode 100644
index 0000000..9f79838
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/editor/VexActionDelegate.java
@@ -0,0 +1,122 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.ui.internal.editor;
+
+import java.text.MessageFormat;
+import java.util.HashMap;
+import java.util.Map;
+
+
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.jface.action.IAction;
+import org.eclipse.jface.viewers.ISelection;
+import org.eclipse.ui.IEditorPart;
+import org.eclipse.ui.IWorkbenchWindow;
+import org.eclipse.ui.IWorkbenchWindowActionDelegate;
+import org.eclipse.wst.xml.vex.core.internal.action.IVexAction;
+import org.eclipse.wst.xml.vex.core.internal.action.RemoveElementAction;
+import org.eclipse.wst.xml.vex.core.internal.swt.VexWidget;
+import org.eclipse.wst.xml.vex.core.internal.widget.IVexWidget;
+import org.eclipse.wst.xml.vex.ui.internal.VexPlugin;
+import org.eclipse.wst.xml.vex.ui.internal.action.ChangeElementAction;
+
+/**
+ * An IWorkbenchWindowActionDelegate that defers to to an instance of IVexAction.
+ * The IDs of actions in plugin.xml using this delegate must be the classnames
+ * of actions implementing IVexAction. Such classes must have a no-args constructor.
+ */
+public class VexActionDelegate implements IWorkbenchWindowActionDelegate {
+
+ private static Map actions = new HashMap();
+
+ public VexActionDelegate() {
+ }
+
+ public void dispose() {
+ }
+
+ public void init(IWorkbenchWindow window) {
+ this.window = window;
+ }
+
+ public void run(IAction action) {
+ IVexAction vexAction = getAction(action.getId());
+ if (vexAction == null) {
+ return;
+ }
+
+ VexWidget vexWidget = this.getVexWidget();
+ if (vexWidget != null) {
+ vexWidget.setFocus();
+ vexAction.run(vexWidget);
+ }
+ }
+
+ public void selectionChanged(IAction action, ISelection selection) {
+
+ IVexAction vexAction = getAction(action.getId());
+ boolean enabled;
+
+ if (vexAction == null) {
+ enabled = false;
+ } else {
+ IVexWidget vexWidget = this.getVexWidget();
+ if (vexWidget == null) {
+ enabled = false;
+ } else {
+ enabled = vexAction.isEnabled(vexWidget);
+
+ if (action.getId().equals(ChangeElementAction.class.getName())) {
+ String elementName = vexWidget.getCurrentElement().getName();
+ String message = Messages.getString("ChangeElementAction.dynamic.label"); //$NON-NLS-1$
+ action.setText(MessageFormat.format(message, new Object[] { elementName }));
+ }
+
+ if (action.getId().equals(RemoveElementAction.class.getName())) {
+ String elementName = vexWidget.getCurrentElement().getName();
+ String message = Messages.getString("RemoveElementAction.dynamic.label"); //$NON-NLS-1$
+ action.setText(MessageFormat.format(message, new Object[] { elementName }));
+ }
+ }
+ }
+
+ action.setEnabled(enabled);
+ }
+
+
+ private IWorkbenchWindow window;
+
+ private VexWidget getVexWidget() {
+ IEditorPart editor = this.window.getActivePage().getActiveEditor();
+ if (editor != null && editor instanceof VexEditor) {
+ return ((VexEditor) editor).getVexWidget();
+ } else {
+ return null;
+ }
+ }
+
+ private static IVexAction getAction(String actionId) {
+ try {
+ IVexAction action = (IVexAction) actions.get(actionId);
+ if (action == null) {
+ action = (IVexAction) Class.forName(actionId).newInstance();
+ actions.put(actionId, action);
+ }
+ return action;
+ } catch (Exception ex) {
+ String message = MessageFormat.format(
+ Messages.getString("VexActionDelegate.errorLoadingAction"), //$NON-NLS-1$
+ new Object[] { actionId, ex.getMessage() });
+ VexPlugin.getInstance().log(IStatus.ERROR, message, ex);
+ return null;
+ }
+ }
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/editor/VexEditor.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/editor/VexEditor.java
new file mode 100644
index 0000000..b9ff330
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/editor/VexEditor.java
@@ -0,0 +1,987 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.ui.internal.editor;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.net.URL;
+import java.text.MessageFormat;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+
+
+import org.eclipse.core.resources.IFile;
+import org.eclipse.core.resources.IMarker;
+import org.eclipse.core.resources.IResourceChangeEvent;
+import org.eclipse.core.resources.IResourceChangeListener;
+import org.eclipse.core.resources.IResourceDelta;
+import org.eclipse.core.resources.ResourcesPlugin;
+import org.eclipse.core.runtime.IPath;
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.core.runtime.Platform;
+import org.eclipse.core.runtime.preferences.IPreferencesService;
+import org.eclipse.core.runtime.preferences.InstanceScope;
+import org.eclipse.jface.action.MenuManager;
+import org.eclipse.jface.commands.ActionHandler;
+import org.eclipse.jface.dialogs.MessageDialog;
+import org.eclipse.jface.viewers.ISelectionChangedListener;
+import org.eclipse.jface.viewers.IStructuredSelection;
+import org.eclipse.jface.viewers.SelectionChangedEvent;
+import org.eclipse.jface.window.Window;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Display;
+import org.eclipse.swt.widgets.Label;
+import org.eclipse.ui.IEditorInput;
+import org.eclipse.ui.IEditorSite;
+import org.eclipse.ui.IFileEditorInput;
+import org.eclipse.ui.PartInitException;
+import org.eclipse.ui.contexts.IContextService;
+import org.eclipse.ui.dialogs.SaveAsDialog;
+import org.eclipse.ui.editors.text.ILocationProvider;
+import org.eclipse.ui.handlers.IHandlerService;
+import org.eclipse.ui.part.EditorPart;
+import org.eclipse.ui.part.FileEditorInput;
+import org.eclipse.ui.views.contentoutline.IContentOutlinePage;
+import org.eclipse.ui.views.properties.IPropertySheetPage;
+import org.eclipse.ui.views.properties.IPropertySource;
+import org.eclipse.ui.views.properties.IPropertySourceProvider;
+import org.eclipse.ui.views.properties.PropertySheetPage;
+import org.eclipse.wst.xml.vex.core.internal.action.DeleteColumnAction;
+import org.eclipse.wst.xml.vex.core.internal.action.DeleteRowAction;
+import org.eclipse.wst.xml.vex.core.internal.action.DuplicateSelectionAction;
+import org.eclipse.wst.xml.vex.core.internal.action.InsertColumnAfterAction;
+import org.eclipse.wst.xml.vex.core.internal.action.InsertColumnBeforeAction;
+import org.eclipse.wst.xml.vex.core.internal.action.InsertRowAboveAction;
+import org.eclipse.wst.xml.vex.core.internal.action.InsertRowBelowAction;
+import org.eclipse.wst.xml.vex.core.internal.action.MoveColumnLeftAction;
+import org.eclipse.wst.xml.vex.core.internal.action.MoveColumnRightAction;
+import org.eclipse.wst.xml.vex.core.internal.action.MoveRowDownAction;
+import org.eclipse.wst.xml.vex.core.internal.action.MoveRowUpAction;
+import org.eclipse.wst.xml.vex.core.internal.action.NextTableCellAction;
+import org.eclipse.wst.xml.vex.core.internal.action.PasteTextAction;
+import org.eclipse.wst.xml.vex.core.internal.action.PreviousTableCellAction;
+import org.eclipse.wst.xml.vex.core.internal.action.RemoveElementAction;
+import org.eclipse.wst.xml.vex.core.internal.action.RestoreLastSelectionAction;
+import org.eclipse.wst.xml.vex.core.internal.action.SplitAction;
+import org.eclipse.wst.xml.vex.core.internal.action.SplitItemAction;
+import org.eclipse.wst.xml.vex.core.internal.core.ListenerList;
+import org.eclipse.wst.xml.vex.core.internal.dom.Document;
+import org.eclipse.wst.xml.vex.core.internal.dom.DocumentReader;
+import org.eclipse.wst.xml.vex.core.internal.dom.DocumentWriter;
+import org.eclipse.wst.xml.vex.core.internal.dom.Element;
+import org.eclipse.wst.xml.vex.core.internal.dom.IWhitespacePolicy;
+import org.eclipse.wst.xml.vex.core.internal.dom.IWhitespacePolicyFactory;
+import org.eclipse.wst.xml.vex.core.internal.dom.Validator;
+import org.eclipse.wst.xml.vex.core.internal.swt.VexWidget;
+import org.eclipse.wst.xml.vex.core.internal.widget.CssWhitespacePolicy;
+import org.eclipse.wst.xml.vex.ui.internal.VexPlugin;
+import org.eclipse.wst.xml.vex.ui.internal.action.ChangeElementAction;
+import org.eclipse.wst.xml.vex.ui.internal.action.InsertElementAction;
+import org.eclipse.wst.xml.vex.ui.internal.action.VexActionAdapter;
+import org.eclipse.wst.xml.vex.ui.internal.config.ConfigEvent;
+import org.eclipse.wst.xml.vex.ui.internal.config.ConfigRegistry;
+import org.eclipse.wst.xml.vex.ui.internal.config.DocumentType;
+import org.eclipse.wst.xml.vex.ui.internal.config.IConfigListener;
+import org.eclipse.wst.xml.vex.ui.internal.config.Style;
+import org.osgi.service.prefs.BackingStoreException;
+import org.osgi.service.prefs.Preferences;
+import org.xml.sax.EntityResolver;
+import org.xml.sax.InputSource;
+import org.xml.sax.SAXException;
+import org.xml.sax.SAXParseException;
+
+/**
+ * Editor for editing XML file using the VexWidget.
+ */
+public class VexEditor extends EditorPart {
+
+ /**
+ * ID of this editor extension.
+ */
+ public static final String ID = "org.eclipse.wst.xml.vex.ui.internal.editor.VexEditor"; //$NON-NLS-1$
+
+ /**
+ * Class constructor.
+ */
+ public VexEditor() {
+ this.debugging = VexPlugin.getInstance().isDebugging() &&
+ "true".equalsIgnoreCase(Platform.getDebugOption(VexPlugin.ID + "/debug/layout")); //$NON-NLS-1$ //$NON-NLS-2$
+ }
+
+ /**
+ * Add a VexEditorListener to the notification list.
+ * @param listener VexEditorListener to be added.
+ */
+ public void addVexEditorListener(IVexEditorListener listener) {
+ this.vexEditorListeners.add(listener);
+ }
+
+ public void dispose() {
+ super.dispose();
+
+ if (this.parentControl != null) {
+ // createPartControl was called, so we must de-register from config events
+ ConfigRegistry.getInstance().removeConfigListener(this.configListener);
+ }
+
+ if (getEditorInput() instanceof IFileEditorInput) {
+ ResourcesPlugin.getWorkspace().removeResourceChangeListener(this.resourceChangeListener);
+ }
+
+ }
+
+ public void doSave(IProgressMonitor monitor) {
+
+ IEditorInput input = this.getEditorInput();
+ OutputStream os = null;
+ try {
+ this.resourceChangeListener.setSaving(true);
+ DocumentWriter writer = new DocumentWriter();
+ writer.setWhitespacePolicy(new CssWhitespacePolicy(this.style.getStyleSheet()));
+
+ if (input instanceof IFileEditorInput) {
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ writer.write(this.doc, baos);
+ baos.close();
+ ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
+ ((IFileEditorInput) input).getFile().setContents(bais, false, false, monitor);
+ } else {
+ os = new FileOutputStream(((ILocationProvider) input).getPath(input).toFile());
+ writer.write(this.doc, os);
+ }
+
+ this.savedUndoDepth = this.vexWidget.getUndoDepth();
+ this.firePropertyChange(EditorPart.PROP_DIRTY);
+
+ } catch (Exception ex) {
+ monitor.setCanceled(true);
+ String title = Messages.getString("VexEditor.errorSaving.title"); //$NON-NLS-1$
+ String message = MessageFormat.format(
+ Messages.getString("VexEditor.errorSaving.message"), //$NON-NLS-1$
+ new Object[] { input.getName(), ex.getMessage() });
+ MessageDialog.openError(this.getEditorSite().getShell(), title, message);
+ VexPlugin.getInstance().log(IStatus.ERROR, message, ex);
+ } finally {
+ if (os != null) {
+ try {
+ os.close();
+ } catch (IOException e) {
+ }
+ }
+ this.resourceChangeListener.setSaving(false);
+ }
+ }
+
+ public void doSaveAs() {
+ SaveAsDialog dlg = new SaveAsDialog(this.getSite().getShell());
+ int result = dlg.open();
+ if (result == Window.OK) {
+ IPath path = dlg.getResult();
+ try {
+ this.resourceChangeListener.setSaving(true);
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ DocumentWriter writer = new DocumentWriter();
+ writer.setWhitespacePolicy(new CssWhitespacePolicy(this.style.getStyleSheet()));
+ writer.write(this.doc, baos);
+ baos.close();
+
+
+ ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
+ IFile file = ResourcesPlugin.getWorkspace().getRoot().getFile(path);
+ file.create(bais, false, null);
+
+ IFileEditorInput input = new FileEditorInput(file);
+ this.setInput(input);
+ this.savedUndoDepth = this.vexWidget.getUndoDepth();
+
+ this.firePropertyChange(EditorPart.PROP_DIRTY);
+ this.firePropertyChange(EditorPart.PROP_INPUT);
+ this.firePropertyChange(EditorPart.PROP_TITLE);
+
+ } catch (Exception ex) {
+ String title = Messages.getString("VexEditor.errorSaving.title"); //$NON-NLS-1$
+ String message = MessageFormat.format(
+ Messages.getString("VexEditor.errorSaving.message"), //$NON-NLS-1$
+ new Object[] { path, ex.getMessage() });
+ MessageDialog.openError(this.getEditorSite().getShell(), title, message);
+ VexPlugin.getInstance().log(IStatus.ERROR, message, ex);
+ } finally {
+ this.resourceChangeListener.setSaving(false);
+ }
+ }
+
+ }
+
+ /**
+ * Return a reasonable style for the given doctype.
+ *
+ * @param publicId Public ID for which to return the style.
+ */
+ public static Style findStyleForDoctype(String publicId) {
+
+ IPreferencesService preferences = Platform.getPreferencesService();
+ String key = getStylePreferenceKey(publicId);
+ String preferredStyleId = preferences.getString(VexPlugin.ID, key, null, null);
+
+ Preferences prefs = new InstanceScope().getNode(VexPlugin.ID);
+ preferredStyleId = prefs.get(key, null);
+
+ Style firstStyle = null;
+ ConfigRegistry registry = ConfigRegistry.getInstance();
+ List styles = registry.getAllConfigItems(Style.EXTENSION_POINT);
+ for (Iterator it = styles.iterator(); it.hasNext(); ) {
+ Style style = (Style) it.next();
+ if (style.appliesTo(publicId)) {
+ if (firstStyle == null) {
+ firstStyle = style;
+ }
+ if (style.getUniqueId().equals(preferredStyleId)) {
+ return style;
+ }
+ }
+ }
+ return firstStyle;
+ }
+
+
+ /**
+ * Returns the DocumentType associated with this editor.
+ */
+ public DocumentType getDocumentType() {
+ return this.doctype;
+ }
+
+ /**
+ * Returns the Style currently associated with the editor. May be null.
+ */
+ public Style getStyle() {
+ return style;
+ }
+
+ /**
+ * Returns the VexWidget that implements this editor.
+ */
+ public VexWidget getVexWidget() {
+ return this.vexWidget;
+ }
+
+ public void gotoMarker(IMarker marker) {
+ // TODO Auto-generated method stub
+
+ }
+
+ public void init(IEditorSite site, IEditorInput input) throws PartInitException {
+
+ this.setSite(site);
+ this.setInput(input);
+
+ this.getEditorSite().setSelectionProvider(this.selectionProvider);
+ this.getEditorSite().getSelectionProvider().addSelectionChangedListener(selectionChangedListener);
+
+ if (input instanceof IFileEditorInput) {
+ ResourcesPlugin.getWorkspace().addResourceChangeListener(this.resourceChangeListener, IResourceChangeEvent.POST_CHANGE);
+ }
+ }
+
+ private void loadInput() {
+
+ if (this.vexWidget != null) {
+ this.vexEditorListeners.fireEvent("documentUnloaded", new VexEditorEvent(this)); //$NON-NLS-1$
+ }
+
+ this.loaded = false;
+
+ IEditorInput input = this.getEditorInput();
+
+ try {
+ long start = System.currentTimeMillis();
+
+ IPath inputPath = null;
+
+ if (input instanceof IFileEditorInput) {
+ inputPath = ((IFileEditorInput) input).getFile().getRawLocation();
+ } else if (input instanceof ILocationProvider) {
+ // Yuck, this a crappy way for Eclipse to do this
+ // How about an exposed IJavaFileEditorInput, pleeze?
+ inputPath = ((ILocationProvider) input).getPath(input);
+ } else {
+ String msg = MessageFormat.format(
+ Messages.getString("VexEditor.unknownInputClass"), //$NON-NLS-1$
+ new Object[] { input.getClass() });
+ this.showLabel(msg);
+ return;
+ }
+
+ URL url = inputPath.toFile().toURL();
+
+ DocumentReader reader = new DocumentReader();
+ reader.setDebugging(this.debugging);
+ reader.setEntityResolver(entityResolver);
+ reader.setWhitespacePolicyFactory(wsFactory);
+ this.doctype = null;
+ this.doc = reader.read(url);
+
+ if (this.debugging) {
+ long end = System.currentTimeMillis();
+ System.out.println("Parsed document in " + (end-start) + "ms"); //$NON-NLS-1$ //$NON-NLS-2$
+ }
+
+ // this.doctype is set either by wsPolicyFactory or entityResolver
+ // this.style is set by wsPolicyFactory
+ // Otherwise, a PartInitException would have been thrown by now
+
+ Validator validator = this.doctype.getValidator();
+ if (validator != null) {
+ this.doc.setValidator(validator);
+ if (this.debugging) {
+ long end = System.currentTimeMillis();
+ System.out.println("Got validator in " + (end-start) + "ms"); //$NON-NLS-1$ //$NON-NLS-2$
+ }
+ }
+
+ this.showVexWidget();
+
+ this.vexWidget.setDebugging(debugging);
+ this.vexWidget.setDocument(this.doc, this.style.getStyleSheet());
+
+ if (this.updateDoctypeDecl) {
+ this.doc.setPublicID(this.doctype.getPublicId());
+ this.doc.setSystemID(this.doctype.getSystemId());
+ this.doSave(null);
+ }
+
+ this.loaded = true;
+ this.savedUndoDepth = this.vexWidget.getUndoDepth();
+ firePropertyChange(EditorPart.PROP_DIRTY);
+ this.wasDirty = isDirty();
+
+ this.vexEditorListeners.fireEvent("documentLoaded", new VexEditorEvent(this)); //$NON-NLS-1$
+
+ } catch (SAXParseException ex) {
+
+ if (ex.getException() instanceof NoRegisteredDoctypeException) {
+ // TODO doc did not have document type and the user
+ // declined to select another one. Should fail silently.
+ String msg;
+ NoRegisteredDoctypeException ex2 = (NoRegisteredDoctypeException) ex.getException();
+ if (ex2.getPublicId() == null) {
+ msg = Messages.getString("VexEditor.noDoctype"); //$NON-NLS-1$
+ } else {
+ msg = MessageFormat.format(
+ Messages.getString("VexEditor.unknownDoctype"), //$NON-NLS-1$
+ new Object[] { ex2.getPublicId() });
+ }
+ this.showLabel(msg);
+ } else if (ex.getException() instanceof NoStyleForDoctypeException) {
+ String msg = MessageFormat.format(
+ Messages.getString("VexEditor.noStyles"), //$NON-NLS-1$
+ new Object[] { this.doctype.getPublicId() });
+ this.showLabel(msg);
+ } else {
+ String file = ex.getSystemId();
+ if (file == null) {
+ file = input.getName();
+ }
+
+ String msg = MessageFormat.format(
+ Messages.getString("VexEditor.parseError"), //$NON-NLS-1$
+ new Object[] { new Integer(ex.getLineNumber()), file, ex.getLocalizedMessage() });
+
+ this.showLabel(msg);
+
+ VexPlugin.getInstance().log(IStatus.ERROR, msg, ex);
+ }
+
+ } catch (Exception ex) {
+
+ String msg = MessageFormat.format(
+ Messages.getString("VexEditor.unexpectedError"), //$NON-NLS-1$
+ new Object[] { input.getName() });
+
+ VexPlugin.getInstance().log(IStatus.ERROR, msg, ex);
+
+ this.showLabel(msg);
+ }
+ }
+
+ public boolean isDirty() {
+ if (this.vexWidget != null) {
+ return this.savedUndoDepth != this.vexWidget.getUndoDepth();
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Returns true if this editor has finished loading its document.
+ */
+ public boolean isLoaded() {
+ return this.loaded;
+ }
+
+ public boolean isSaveAsAllowed() {
+ return true;
+ }
+
+ public void createPartControl(Composite parent) {
+
+ this.parentControl = parent;
+
+ ConfigRegistry registry = ConfigRegistry.getInstance();
+
+ registry.addConfigListener(this.configListener);
+
+ if (registry.isConfigLoaded()) {
+ this.loadInput();
+ } else {
+ this.showLabel(Messages.getString("VexEditor.loading")); //$NON-NLS-1$
+ }
+ }
+
+ /**
+ * Remove a VexEditorListener from the notification list.
+ * @param listener VexEditorListener to be removed.
+ */
+ public void removeVexEditorListener(IVexEditorListener listener) {
+ this.vexEditorListeners.remove(listener);
+ }
+
+
+ public void setFocus() {
+ if (this.vexWidget != null) {
+ this.vexWidget.setFocus();
+ setStatus(getLocation());
+ }
+ }
+
+ protected void setInput(IEditorInput input) {
+ super.setInput(input);
+ this.setPartName(input.getName());
+ this.setContentDescription(input.getName());
+ this.setTitleToolTip(input.getToolTipText());
+ }
+
+ public void setStatus(String text) {
+ //this.statusLabel.setText(text);
+ this.getEditorSite().getActionBars().getStatusLineManager().setMessage(text);
+ }
+
+ /**
+ * Sets the style for this editor.
+ * @param style Style to use.
+ */
+ public void setStyle(Style style) {
+ this.style = style;
+ if (this.vexWidget != null) {
+ this.vexWidget.setStyleSheet(style.getStyleSheet());
+ Preferences prefs = new InstanceScope().getNode(VexPlugin.ID);
+ String key = getStylePreferenceKey(this.doc.getPublicID());
+ prefs.put(key, style.getUniqueId());
+ try {
+ prefs.flush();
+ } catch (BackingStoreException e) {
+ VexPlugin.getInstance().log(IStatus.ERROR, Messages.getString("VexEditor.errorSavingStylePreference"), e); //$NON-NLS-1$
+ }
+ }
+ }
+
+ //========================================================= PRIVATE
+
+ private boolean debugging;
+
+ private Composite parentControl;
+ private Label loadingLabel;
+
+ private boolean loaded;
+ private DocumentType doctype;
+ private Document doc;
+ private Style style;
+
+ private VexWidget vexWidget;
+
+ private int savedUndoDepth;
+ private boolean wasDirty;
+ //private Label statusLabel;
+
+ // This is true if the document's doctype decl is missing or unrecognized
+ // AND the user selected a new document type
+ // AND the user wants to always use the doctype for this document
+ private boolean updateDoctypeDecl;
+
+ private ListenerList vexEditorListeners = new ListenerList(IVexEditorListener.class, VexEditorEvent.class);
+
+ private SelectionProvider selectionProvider = new SelectionProvider();
+
+ /**
+ * Returns the preference key used to access the style ID for documents
+ * with the same public ID as the current document.
+ */
+ private static String getStylePreferenceKey(String publicId) {
+ return publicId + ".style"; //$NON-NLS-1$
+ }
+
+ private void showLabel(String message) {
+ if (this.loadingLabel == null) {
+ if (this.vexWidget != null) {
+ this.vexWidget.dispose();
+ this.vexWidget = null;
+ }
+ this.loadingLabel = new Label(this.parentControl, SWT.WRAP);
+ }
+ this.loadingLabel.setText(message);
+ this.parentControl.layout(true);
+ }
+
+ private void showVexWidget() {
+
+ if (this.vexWidget != null) {
+ return;
+ }
+
+ if (this.loadingLabel != null) {
+ this.loadingLabel.dispose();
+ this.loadingLabel = null;
+ }
+
+ GridLayout layout = new GridLayout();
+ layout.numColumns = 1;
+ layout.verticalSpacing = 0;
+ layout.marginHeight = 0;
+ layout.marginWidth = 0;
+ this.parentControl.setLayout(layout);
+ GridData gd;
+
+ //StatusPanel statusPanel = new StatusPanel(this.parentControl);
+
+// Composite statusPanel = new Composite(this.parentControl, SWT.NONE);
+// statusPanel.setLayout(new GridLayout());
+// gd = new GridData();
+// gd.grabExcessHorizontalSpace = true;
+// gd.horizontalAlignment = GridData.FILL;
+// statusPanel.setLayoutData(gd);
+
+// this.statusLabel = new Label(statusPanel, SWT.NONE);
+// gd = new GridData();
+// gd.grabExcessHorizontalSpace = true;
+// gd.horizontalAlignment = GridData.FILL;
+// this.statusLabel.setLayoutData(gd);
+
+ gd = new GridData();
+ gd.grabExcessHorizontalSpace = true;
+ gd.grabExcessVerticalSpace = true;
+ gd.horizontalAlignment = GridData.FILL;
+ gd.verticalAlignment = GridData.FILL;
+
+ this.vexWidget = new VexWidget(this.parentControl, SWT.V_SCROLL);
+ gd = new GridData();
+ gd.grabExcessHorizontalSpace = true;
+ gd.grabExcessVerticalSpace = true;
+ gd.horizontalAlignment = GridData.FILL;
+ gd.verticalAlignment = GridData.FILL;
+ this.vexWidget.setLayoutData(gd);
+
+ VexActionBarContributor contributor = (VexActionBarContributor)
+ this.getEditorSite().getActionBarContributor();
+
+ MenuManager menuMgr = contributor.getContextMenuManager();
+ this.getSite().registerContextMenu(menuMgr, this.vexWidget);
+ this.vexWidget.setMenu(menuMgr.createContextMenu(this.vexWidget));
+
+ this.savedUndoDepth = this.vexWidget.getUndoDepth();
+
+ // new for scopes
+ IContextService cs = (IContextService) this.getSite().getService(IContextService.class);
+ cs.activateContext("org.eclipse.wst.xml.vex.ui.VexEditorContext");
+
+ IHandlerService hs = (IHandlerService) this.getSite().getService(IHandlerService.class);
+
+ hs.activateHandler(
+ "org.eclipse.wst.xml.vex.ui.action.ChangeElementAction",
+ new ActionHandler(new VexActionAdapter(this, new ChangeElementAction())));
+
+ hs.activateHandler("org.eclipse.wst.xml.vex.ui.action.DeleteColumnAction",
+ new ActionHandler(new VexActionAdapter(this, new DeleteColumnAction())));
+
+ hs.activateHandler("org.eclipse.wst.xml.vex.ui.action.DeleteRowAction",
+ new ActionHandler(new VexActionAdapter(this, new DeleteRowAction())));
+
+ hs.activateHandler("org.eclipse.wst.xml.vex.ui.action.DuplicateSelectionAction",
+ new ActionHandler(new VexActionAdapter(this, new DuplicateSelectionAction())));
+
+ hs.activateHandler("org.eclipse.wst.xml.vex.ui.action.InsertColumnAfterAction",
+ new ActionHandler(new VexActionAdapter(this, new InsertColumnAfterAction())));
+
+ hs.activateHandler("org.eclipse.wst.xml.vex.ui.action.InsertColumnBeforeAction",
+ new ActionHandler(new VexActionAdapter(this, new InsertColumnBeforeAction())));
+
+ hs.activateHandler("net.sf.vex.editor.action.InsertElementAction",
+ new ActionHandler(new VexActionAdapter(this, new InsertElementAction())));
+
+ hs.activateHandler("org.eclipse.wst.xml.vex.ui.action.InsertRowAboveAction",
+ new ActionHandler(new VexActionAdapter(this, new InsertRowAboveAction())));
+
+ hs.activateHandler("org.eclipse.wst.xml.vex.ui.action.InsertRowBelowAction",
+ new ActionHandler(new VexActionAdapter(this, new InsertRowBelowAction())));
+
+ hs.activateHandler("org.eclipse.wst.xml.vex.ui.action.MoveColumnLeftAction",
+ new ActionHandler(new VexActionAdapter(this, new MoveColumnLeftAction())));
+
+ hs.activateHandler("org.eclipse.wst.xml.vex.ui.action.MoveColumnRightAction",
+ new ActionHandler(new VexActionAdapter(this, new MoveColumnRightAction())));
+
+ hs.activateHandler("org.eclipse.wst.xml.vex.ui.action.MoveRowDownAction",
+ new ActionHandler(new VexActionAdapter(this, new MoveRowDownAction())));
+
+ hs.activateHandler("org.eclipse.wst.xml.vex.ui.action.MoveRowUpAction",
+ new ActionHandler(new VexActionAdapter(this, new MoveRowUpAction())));
+
+ hs.activateHandler("org.eclipse.wst.xml.vex.ui.action.NextTableCellAction",
+ new ActionHandler(new VexActionAdapter(this, new NextTableCellAction())));
+
+ hs.activateHandler("org.eclipse.wst.xml.vex.ui.action.PasteTextAction",
+ new ActionHandler(new VexActionAdapter(this, new PasteTextAction())));
+
+ hs.activateHandler("org.eclipse.wst.xml.vex.ui.action.PreviousTableCellAction",
+ new ActionHandler(new VexActionAdapter(this, new PreviousTableCellAction())));
+
+ hs.activateHandler("org.eclipse.wst.xml.vex.ui.action.RemoveElementAction",
+ new ActionHandler(new VexActionAdapter(this, new RemoveElementAction())));
+
+ hs.activateHandler("org.eclipse.wst.xml.vex.ui.action.RestoreLastSelectionAction",
+ new ActionHandler(new VexActionAdapter(this, new RestoreLastSelectionAction())));
+
+ hs.activateHandler("org.eclipse.wst.xml.vex.ui.action.SplitAction",
+ new ActionHandler(new VexActionAdapter(this, new SplitAction())));
+
+ hs.activateHandler("org.eclipse.wst.xml.vex.ui.action.SplitItemAction",
+ new ActionHandler(new VexActionAdapter(this, new SplitItemAction())));
+
+
+ this.vexWidget.addSelectionChangedListener(this.selectionProvider);
+
+ this.parentControl.layout(true);
+
+ }
+
+ private void handleResourceChanged(IResourceDelta delta) {
+
+ if (delta.getKind() == IResourceDelta.CHANGED) {
+ if ((delta.getFlags() & IResourceDelta.CONTENT) != 0) {
+ this.handleResourceContentChanged();
+ }
+ } else if (delta.getKind() == IResourceDelta.REMOVED) {
+ if ((delta.getFlags() & IResourceDelta.MOVED_TO) != 0) {
+ IPath toPath = delta.getMovedToPath();
+ IFile file = ResourcesPlugin.getWorkspace().getRoot().getFile(toPath);
+ this.setInput(new FileEditorInput(file));
+ } else {
+ if (!this.isDirty()) {
+ this.getEditorSite().getPage().closeEditor(this, false);
+ } else {
+ this.handleResourceDeleted();
+ }
+ }
+ }
+
+ }
+
+
+ private void handleResourceContentChanged() {
+
+ if (!this.isDirty()) {
+ this.loadInput();
+ } else {
+
+ String message = MessageFormat.format(
+ Messages.getString("VexEditor.docChanged.message"), //$NON-NLS-1$
+ new Object[] { this.getEditorInput().getName() });
+
+ MessageDialog dlg = new MessageDialog(this.getSite().getShell(),
+ Messages.getString("VexEditor.docChanged.title"), //$NON-NLS-1$
+ null,
+ message,
+ MessageDialog.QUESTION,
+ new String[] {
+ Messages.getString("VexEditor.docChanged.discard"), //$NON-NLS-1$
+ Messages.getString("VexEditor.docChanged.overwrite") }, //$NON-NLS-1$
+ 1);
+
+ int result = dlg.open();
+
+ if (result == 0) { // Discard my changes
+ this.loadInput();
+ } else { // Overwrite other changes
+ this.doSave(null);
+ }
+ }
+ }
+
+
+ private void handleResourceDeleted() {
+
+ String message = MessageFormat.format(
+ Messages.getString("VexEditor.docDeleted.message"), //$NON-NLS-1$
+ new Object[] { this.getEditorInput().getName() });
+
+ MessageDialog dlg = new MessageDialog(this.getSite().getShell(),
+ Messages.getString("VexEditor.docDeleted.title"), //$NON-NLS-1$
+ null,
+ message,
+ MessageDialog.QUESTION,
+ new String[] {
+ Messages.getString("VexEditor.docDeleted.discard"), //$NON-NLS-1$
+ Messages.getString("VexEditor.docDeleted.save") }, //$NON-NLS-1$
+ 1);
+
+ int result = dlg.open();
+
+ if (result == 0) { // Discard
+
+ this.getEditorSite().getPage().closeEditor(this, false);
+
+ } else { // Save
+
+ this.doSaveAs();
+
+ // Check if they saved or not. If not, close the editor
+ if (!this.getEditorInput().exists()) {
+ this.getEditorSite().getPage().closeEditor(this, false);
+ }
+ }
+ }
+
+
+ // Listen for stylesheet changes and respond appropriately
+ private IConfigListener configListener = new IConfigListener() {
+
+ public void configChanged(ConfigEvent e) {
+ if (style != null) {
+ ConfigRegistry registry = ConfigRegistry.getInstance();
+ String currId = style.getUniqueId();
+ Style newStyle = (Style) registry.getConfigItem(Style.EXTENSION_POINT, currId);
+ if (newStyle == null) {
+ // Oops, style went bye-bye
+ // Let's just hold on to it in case it comes back later
+ } else {
+ vexWidget.setStyleSheet(newStyle.getStyleSheet());
+ style = newStyle;
+ }
+ }
+ }
+
+ public void configLoaded(ConfigEvent e) {
+ loadInput();
+ }
+ };
+
+ private ISelectionChangedListener selectionChangedListener = new ISelectionChangedListener() {
+ public void selectionChanged(SelectionChangedEvent event) {
+ if (isDirty() != wasDirty) {
+ firePropertyChange(EditorPart.PROP_DIRTY);
+ wasDirty = isDirty();
+ }
+ setStatus(getLocation());
+ }
+ };
+
+
+ private EntityResolver entityResolver = new EntityResolver() {
+ public InputSource resolveEntity(String publicId, String systemId) throws SAXException, IOException {
+
+ //System.out.println("### Resolving publicId " + publicId + ", systemId " + systemId);
+
+ if (doctype == null) {
+ //
+ // If doctype hasn't already been set, this must be the doctype
+ // decl.
+ //
+ if (publicId != null) {
+ doctype = DocumentType.getDocumentType(publicId);
+ }
+
+ if (doctype == null) {
+ DocumentTypeSelectionDialog dlg = DocumentTypeSelectionDialog.create(getSite().getShell(), publicId);
+ dlg.open();
+ doctype = dlg.getDoctype();
+ updateDoctypeDecl = dlg.alwaysUseThisDoctype();
+
+ if (doctype == null) {
+ throw new NoRegisteredDoctypeException(publicId);
+ }
+ }
+
+ URL url = doctype.getResourceUrl();
+
+ if (url == null) {
+ String message = MessageFormat.format(
+ Messages.getString("VexEditor.noUrlForDoctype"), //$NON-NLS-1$
+ new Object[] { publicId });
+ throw new RuntimeException(message);
+ }
+
+ return new InputSource(url.toString());
+ } else {
+ return null;
+ }
+ }
+ };
+
+ private IWhitespacePolicyFactory wsFactory = new IWhitespacePolicyFactory() {
+ public IWhitespacePolicy getPolicy(String publicId) {
+
+ if (doctype == null) {
+ DocumentTypeSelectionDialog dlg = DocumentTypeSelectionDialog.create(getSite().getShell(), publicId);
+ dlg.open();
+ doctype = dlg.getDoctype();
+ updateDoctypeDecl = dlg.alwaysUseThisDoctype();
+
+ if (doctype == null) {
+ throw new NoRegisteredDoctypeException(null);
+ }
+ }
+
+ style = VexEditor.findStyleForDoctype(doctype.getPublicId());
+ if (style == null) {
+ throw new NoStyleForDoctypeException(doctype);
+ }
+
+ return new CssWhitespacePolicy(style.getStyleSheet());
+ }
+
+ };
+
+ private class ResourceChangeListener implements IResourceChangeListener {
+
+ public void resourceChanged(IResourceChangeEvent event) {
+
+ if (this.saving) {
+ return;
+ }
+
+ IPath path = ((IFileEditorInput) getEditorInput()).getFile().getFullPath();
+ final IResourceDelta delta = event.getDelta().findMember(path);
+ if (delta != null) {
+ Display.getDefault().asyncExec(new Runnable() {
+ public void run() {
+ handleResourceChanged(delta);
+ }
+ });
+ }
+ }
+
+ public void setSaving(boolean saving) {
+ this.saving = saving;
+ }
+
+ // Set to true so we can ignore change events while we're saving.
+ private boolean saving;
+ };
+
+ private ResourceChangeListener resourceChangeListener = new ResourceChangeListener();
+
+ //
+ // wsFactory communicates failures back to init() through the XML parser
+ // by throwing one of these exceptions
+ //
+
+ /**
+ * Indicates that no document type is registered for the public ID
+ * in the document, or that the document does not have a PUBLIC
+ * DOCTYPE decl, in which case publicId is null.
+ */
+ private class NoRegisteredDoctypeException extends RuntimeException {
+ public NoRegisteredDoctypeException(String publicId) {
+ this.publicId = publicId;
+ }
+
+ public String getPublicId() {
+ return this.publicId;
+ }
+
+ private String publicId;
+ }
+
+ /**
+ * Indicates that the document was matched to a registered doctype,
+ * but that the given doctype does not have a matching style.
+ */
+ private class NoStyleForDoctypeException extends RuntimeException {
+
+ public NoStyleForDoctypeException(DocumentType doctype) {
+ this.doctype = doctype;
+ }
+
+ public DocumentType getDoctype() {
+ return this.doctype;
+ }
+
+ private DocumentType doctype;
+ }
+
+
+ private String getLocation() {
+ List path = new ArrayList();
+ Element element = this.vexWidget.getCurrentElement();
+ while (element != null) {
+ path.add(element.getName());
+ element = element.getParent();
+ }
+ Collections.reverse(path);
+ StringBuffer sb = new StringBuffer(path.size() * 15);
+ for (int i = 0; i < path.size(); i++) {
+ sb.append("/"); //$NON-NLS-1$
+ sb.append(path.get(i));
+ }
+ return sb.toString();
+ }
+
+
+ public Object getAdapter(Class adapter) {
+
+ if (adapter == IContentOutlinePage.class) {
+
+ return new DocumentOutlinePage();
+
+ } else if (adapter == IPropertySheetPage.class) {
+ PropertySheetPage page = new PropertySheetPage();
+ page.setPropertySourceProvider(new IPropertySourceProvider() {
+ public IPropertySource getPropertySource(Object object) {
+ if (object instanceof Element) {
+ IStructuredSelection sel = (IStructuredSelection) vexWidget.getSelection();
+ boolean multi = (sel != null && sel.size() > 1);
+ Validator validator = vexWidget.getDocument().getValidator();
+ return new ElementPropertySource((Element) object, validator, multi);
+ } else {
+ return null;
+ }
+ }
+ });
+ return page;
+ } else {
+ return super.getAdapter(adapter);
+ }
+ }
+
+
+
+}
+
+
+
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/editor/VexEditorEvent.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/editor/VexEditorEvent.java
new file mode 100644
index 0000000..f40cfd3
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/editor/VexEditorEvent.java
@@ -0,0 +1,34 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.ui.internal.editor;
+
+import java.util.EventObject;
+
+/**
+ * Event object published through the IVexEditorListener interface.
+ */
+public class VexEditorEvent extends EventObject {
+
+ /**
+ * Class constructor.
+ * @param source VexEditor that originated this event.
+ */
+ public VexEditorEvent(VexEditor source) {
+ super(source);
+ }
+
+ /**
+ * Returns the VexEditor that originated this event.
+ */
+ public VexEditor getVexEditor() {
+ return (VexEditor) this.getSource();
+ }
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/editor/messages.properties b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/editor/messages.properties
new file mode 100644
index 0000000..7e03ee1
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/editor/messages.properties
@@ -0,0 +1,103 @@
+#
+# Language-specific strings in the vex-editor plugin
+#
+
+
+#
+# Actions
+#
+# NOTE: THESE MUST BE KEPT IN SYNC WITH THE SAME BLOCK IN plugin.properties
+#
+# Note: when there are multiple labels for an action, the one named
+# Xxx.label is the command listed in the key bindings preference page.
+#
+ChangeElementAction.label=Change Element
+ChangeElementAction.dynamic.label=&Change <{0}> to...
+ChangeElementAction.menu.label=&Change Element...
+DeleteColumnAction.label=Delete Column
+DebugView.noActiveEditor=No Vex editor is currently active.
+DeleteRowAction.label=Delete Row
+DuplicateSelectionAction.label=&Duplicate Selection
+InsertColumnAfterAction.label=Insert Column After
+InsertColumnBeforeAction.label=Insert Column Before
+InsertElementAction.label=Insert Element
+InsertAssistant.title=Insert Element
+InsertElementAction.contextmenu.label=Insert Element...
+InsertElementAction.mainmenu.label=&Element...
+InsertRowAboveAction.label=Insert Row Above
+InsertRowBelowAction.label=Insert Row Below
+MoveColumnLeftAction.label=Move Column Left
+MoveColumnRightAction.label=Move Column Right
+MoveRowDownAction.label=Move Row Down
+MoveRowUpAction.label=Move Row Up
+NextTableCellAction.label=Next Table Cell
+PasteTextAction.label=Paste Text
+PreviousTableCellAction.label=Previous Table Cell
+RemoveElementAction.label=Remove Element
+RemoveElementAction.dynamic.label=Remove <{0}>
+RestoreLastSelectionAction.label=Restore Last Selection
+SplitAction.label=Split Block Element
+SplitItemAction.label=Split Item
+
+#
+# End of Actions
+#
+
+DebugView.noActiveEditor=No Vex editor is currently active.
+
+DocumentFileCreationPage.title=File
+DocumentFileCreationPage.desc=Enter a file name for the new document.
+
+DocumentOutlinePage.loadingError=Exception while loading {0} from bundle {1}: {2}
+DocumentOutlinePage.loading=Loading...
+DocumentOutlinePage.reloading=Reloading...
+
+Messages.cantFindResource=Can''t find resource string {0}
+
+DocumentTypeSelectionDialog.ok=OK
+DocumentTypeSelectionDialog.cancel=Cancel
+DocumentTypeSelectionDialog.unknownDoctype=Vex does not recognize the document type ''{0}''. To open the document with a different document type, select one of the registered document types below.
+DocumentTypeSelectionDialog.selectDoctype=Select Document Type
+DocumentTypeSelectionDialog.noDoctype=Vex cannot determine a document type from the selected document. Please select a document type from the following list.
+DocumentTypeSelectionDialog.alwaysUse=Always use this document type for the selected document.
+
+DocumentTypeSelectionPage.title=Document Type
+DocumentTypeSelectionPage.desc=Select a document type and root element for the new document.
+DocumentTypeSelectionPage.doctype=Document Type:
+DocumentTypeSelectionPage.pageName=Select Type
+DocumentTypeSelectionPage.rootElement=Root Element:
+
+ElementPropertySource.multiple=(multiple)
+
+NewDocumentWizard.title=New Vex Document
+NewDocumentWizard.noStyles.title=Error
+NewDocumentWizard.noStyles.message=No styles for the given document type.
+NewDocumentWizard.errorLoading.message=Error loading {0}: {1}
+NewDocumentWizard.errorLoading.title=Error
+
+VexActionBarContributor.styleMenu.name=Style
+VexActionBarContributor.noValidItems=No valid items
+VexActionBarContributor.rowMenu.name=Row
+VexActionBarContributor.columnMenu.name=Column
+
+VexActionDelegate.errorLoadingAction=Error loading action {0}: {1}
+
+VexEditor.errorSaving.title=Error
+VexEditor.errorSaving.message=Error saving {0}: {1}
+VexEditor.unknownInputClass=Unable to open inputs of type {0}. Please log a bug report at http://krasnay.ca/bugzilla.
+VexEditor.noDoctype=Cannot determine a document type from the selected document.
+VexEditor.unknownDoctype=No registered document types for public ID {0}.
+VexEditor.noStyles=No registered styles for documents with public ID {0}.
+VexEditor.parseError=Error at line {0} of file {1}: {2}
+VexEditor.unexpectedError=Unexpected error opening {0}. Please log a bug report at http://krasnay.ca/bugzilla.
+VexEditor.loading=Loading...
+VexEditor.errorSavingStylePreference=Error saving style preference
+VexEditor.docChanged.title=Document Changed
+VexEditor.docChanged.message=The file {0} has been changed from outside Vex. Would you like to discard your changes or overwrite the external changes?
+VexEditor.docChanged.discard=Discard My Changes
+VexEditor.docChanged.overwrite=Overwrite Other Changes
+VexEditor.docDeleted.title=Document Deleted
+VexEditor.docDeleted.message=The file {0} has been deleted from the workspace. Would you like to discard your changes or save to a new document?
+VexEditor.docDeleted.discard=Discard
+VexEditor.docDeleted.save=Save
+VexEditor.noUrlForDoctype=No URL defined for doctype {0}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/editor/messages_fr.properties b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/editor/messages_fr.properties
new file mode 100644
index 0000000..872a25e
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/editor/messages_fr.properties
@@ -0,0 +1,102 @@
+#
+# Language-specific strings in the vex-editor plugin
+#
+
+
+#
+# Actions
+#
+# NOTE: THESE MUST BE KEPT IN SYNC WITH THE SAME BLOCK IN plugin.properties
+#
+# Note: when there are multiple labels for an action, the one named
+# Xxx.label is the command listed in the key bindings preference page.
+#
+ChangeElementAction.label=Changer l'élément
+ChangeElementAction.dynamic.label=&Changer <{0}> en...
+ChangeElementAction.menu.label=&Changer l'élément...
+DeleteColumnAction.label=Detruire la colonne
+DebugView.noActiveEditor=Pas d'éditeur Vex actif
+DeleteRowAction.label=Détruire la ligne
+DuplicateSelectionAction.label=&Dupliquer la sélection
+InsertColumnAfterAction.label=Insérer une colonne après
+InsertColumnBeforeAction.label=Insérer une colonne avant
+InsertElementAction.label=Insérer un élément
+InsertAssistant.title=Insérer un élément
+InsertElementAction.contextmenu.label=Insérer l'élément...
+InsertElementAction.mainmenu.label=&Elément...
+InsertRowAboveAction.label=Insérer une ligne au dessus
+InsertRowBelowAction.label=Insérer une ligne en dessous
+MoveColumnLeftAction.label=Déplacer la colonne à gauche
+MoveColumnRightAction.label=Déplacer la colonne à droite
+MoveRowDownAction.label=Déplacer la ligne en dessous
+MoveRowUpAction.label=Déplacer la ligne au dessus
+NextTableCellAction.label=Prochaine cellule du tableau
+PasteTextAction.label=Copier le texte
+PreviousTableCellAction.label=Cellule précédente du tableau
+RemoveElementAction.label=Supprimer l'élément
+RemoveElementAction.dynamic.label=Supprimer <{0}>
+RestoreLastSelectionAction.label=Restaurer la sélection précédente
+SplitAction.label=Couper le bloc
+SplitItemAction.label=Couper l'item
+
+#
+# End of Actions
+#
+
+DebugView.noActiveEditor=Pas d'éditeur Vex actif.
+
+DocumentFileCreationPage.title=Fichier
+DocumentFileCreationPage.desc=Entrer un nom de fichier pour le nouveau document.
+
+DocumentOutlinePage.loadingError=Exception au chargement de {0} du bundle {1}: {2}
+DocumentOutlinePage.loading=Chargement...
+DocumentOutlinePage.reloading=Rechargement...
+
+Messages.cantFindResource=Ne peut pas trouver la chaine de la ressource {0}
+
+DocumentTypeSelectionDialog.ok=Accepter
+DocumentTypeSelectionDialog.cancel=Abandonner
+DocumentTypeSelectionDialog.unknownDoctype=Vex ne connait pas le type de document '{0}'. Pour ouvrir le document avec un autre type de document, sélectionner un des types enregistrés ci-dessous.
+DocumentTypeSelectionDialog.selectDoctype=Sélectionner un type de document
+DocumentTypeSelectionDialog.noDoctype=Vex ne peut pas déterminer le type de document pour le document sélectionné. Sélectionner un des types de document dans la liste suivante.
+DocumentTypeSelectionDialog.alwaysUse=Toujours utiliser ce type de document pour le document sélectionné.
+Always use this document type for the selected document.
+
+DocumentTypeSelectionPage.title=Type de document
+DocumentTypeSelectionPage.desc=Sélectionner le type de document et la racine pour le nouveau document.
+DocumentTypeSelectionPage.doctype=Type de document:
+DocumentTypeSelectionPage.pageName=Sélectionner le type
+DocumentTypeSelectionPage.rootElement=Elément racine:
+
+ElementPropertySource.multiple=(multiple)
+
+NewDocumentWizard.title=Nouveau Document Vex
+NewDocumentWizard.noStyles.title=Erreur
+NewDocumentWizard.noStyles.message=Pas de styles pour le type de document donné
+NewDocumentWizard.errorLoading.message=Erreur de chargement {0}: {1}
+NewDocumentWizard.errorLoading.title=Erreur
+
+VexActionBarContributor.styleMenu.name=Style
+VexActionBarContributor.noValidItems=Pas d'article valide
+
+VexActionDelegate.errorLoadingAction=Erreur sur l'action de chargement {0}: {1}
+
+VexEditor.errorSaving.title=Erreur
+VexEditor.errorSaving.message=Erreur de sauvegarde {0}: {1}
+VexEditor.unknownInputClass=Impssible d'ouvrir les entrées de type {0}. Envoyer un rapport de bogue à http://krasnay.ca/bugzilla SVP.
+VexEditor.noDoctype=Impossible de déterminer le type de document pour le document sélectionné.
+VexEditor.unknownDoctype=Pas de type de document enregistré pour la clé publique : {0}.
+VexEditor.noStyles=Pas de style enregistré pour la clé publique : {0}.
+VexEditor.parseError=Erreur à la ligne {0} du fichier {1}: {2}
+VexEditor.unexpectedError=Erreur inconnue à l'ouverture de {0}. Envoyer un rapport de bogue à http://krasnay.ca/bugzilla SVP.
+VexEditor.loading=En chargement...
+VexEditor.errorSavingStylePreference=Erreur à la sauvegarde des préférence de style
+VexEditor.docChanged.title=Document modifié
+VexEditor.docChanged.message=Le fichier {0} a été modifié en dehors de Vex. Voulez vous perdre vos modifications ou écraser les modifications extérieures?
+VexEditor.docChanged.discard=Perdre mes modifications
+VexEditor.docChanged.overwrite=Ecraser les autres
+VexEditor.docDeleted.title=Document supprimé
+VexEditor.docDeleted.message=Le fichier {0} a été supprimé de l'espace de travail. Voulez vous predre vos modifications ou enregistrer un nouveau document?
+VexEditor.docDeleted.discard=Perdre
+VexEditor.docDeleted.save=Enregistrer
+VexEditor.noUrlForDoctype=Pas d'URL défini pour le doctype {0}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/vex16.gif b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/vex16.gif
new file mode 100644
index 0000000..a32605a
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/vex16.gif
Binary files differ
diff --git a/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/.classpath b/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/.classpath
new file mode 100644
index 0000000..64c5e31
--- /dev/null
+++ b/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/.classpath
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+ <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/J2SE-1.5"/>
+ <classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>
+ <classpathentry kind="src" path="src"/>
+ <classpathentry kind="output" path="bin"/>
+</classpath>
diff --git a/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/.project b/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/.project
new file mode 100644
index 0000000..f8d75db
--- /dev/null
+++ b/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/.project
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+ <name>org.eclipse.wst.xml.vex.core.tests</name>
+ <comment></comment>
+ <projects>
+ </projects>
+ <buildSpec>
+ <buildCommand>
+ <name>org.eclipse.jdt.core.javabuilder</name>
+ <arguments>
+ </arguments>
+ </buildCommand>
+ <buildCommand>
+ <name>org.eclipse.pde.ManifestBuilder</name>
+ <arguments>
+ </arguments>
+ </buildCommand>
+ <buildCommand>
+ <name>org.eclipse.pde.SchemaBuilder</name>
+ <arguments>
+ </arguments>
+ </buildCommand>
+ </buildSpec>
+ <natures>
+ <nature>org.eclipse.pde.PluginNature</nature>
+ <nature>org.eclipse.jdt.core.javanature</nature>
+ </natures>
+</projectDescription>
diff --git a/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/.settings/org.eclipse.jdt.core.prefs b/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/.settings/org.eclipse.jdt.core.prefs
new file mode 100644
index 0000000..68dd593
--- /dev/null
+++ b/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/.settings/org.eclipse.jdt.core.prefs
@@ -0,0 +1,7 @@
+#Wed Oct 01 02:29:28 GMT 2008
+eclipse.preferences.version=1
+org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.5
+org.eclipse.jdt.core.compiler.compliance=1.5
+org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
+org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
+org.eclipse.jdt.core.compiler.source=1.5
diff --git a/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/META-INF/MANIFEST.MF b/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/META-INF/MANIFEST.MF
new file mode 100644
index 0000000..e13cc59
--- /dev/null
+++ b/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/META-INF/MANIFEST.MF
@@ -0,0 +1,13 @@
+Manifest-Version: 1.0
+Bundle-ManifestVersion: 2
+Bundle-Name: VEX Core Tests
+Bundle-SymbolicName: org.eclipse.wst.xml.vex.core.tests
+Bundle-Version: 0.5.0.qualifier
+Bundle-Activator: org.eclipse.wst.xml.vex.core.tests.VEXCoreTestPlugin
+Bundle-Vendor: Eclipse.org
+Require-Bundle: org.eclipse.ui,
+ org.eclipse.core.runtime,
+ org.eclipse.wst.xml.vex.core;bundle-version="0.5.0",
+ org.junit;bundle-version="3.8.1"
+Bundle-RequiredExecutionEnvironment: J2SE-1.5
+Bundle-ActivationPolicy: lazy
diff --git a/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/build.properties b/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/build.properties
new file mode 100644
index 0000000..34d2e4d
--- /dev/null
+++ b/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/build.properties
@@ -0,0 +1,4 @@
+source.. = src/
+output.. = bin/
+bin.includes = META-INF/,\
+ .
diff --git a/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/src/org/eclipse/wst/xml/vex/core/internal/css/CssTest.java b/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/src/org/eclipse/wst/xml/vex/core/internal/css/CssTest.java
new file mode 100644
index 0000000..2d6ce17
--- /dev/null
+++ b/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/src/org/eclipse/wst/xml/vex/core/internal/css/CssTest.java
@@ -0,0 +1,773 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.css;
+
+import java.net.URL;
+
+import org.eclipse.wst.xml.vex.core.internal.core.Color;
+import org.eclipse.wst.xml.vex.core.internal.core.DisplayDevice;
+import org.eclipse.wst.xml.vex.core.internal.css.CSS;
+import org.eclipse.wst.xml.vex.core.internal.css.StyleSheet;
+import org.eclipse.wst.xml.vex.core.internal.css.StyleSheetReader;
+import org.eclipse.wst.xml.vex.core.internal.css.Styles;
+import org.eclipse.wst.xml.vex.core.internal.dom.Document;
+import org.eclipse.wst.xml.vex.core.internal.dom.Element;
+import org.eclipse.wst.xml.vex.core.internal.dom.RootElement;
+
+import junit.framework.TestCase;
+
+/**
+ * Test the <code>net.sf.vex.css</code> package.
+ */
+public class CssTest extends TestCase {
+
+ protected void setUp() throws Exception {
+ super.setUp();
+ DisplayDevice.setCurrent(new TestDisplayDevice(90, 90));
+ }
+
+ /*
+ public void testAll() throws Exception {
+ Element aElement = new Element("A");
+ Element bElement = new Element("B");
+ Element cElement = new Element("C");
+ Document doc = new Document(aElement);
+ doc.insertElement(1, bElement);
+ doc.insertElement(2, cElement);
+
+ StyleSheet ss = parseStyleSheetResource("test1.css");
+ Styles styles = ss.get(aElement);
+
+ assertProperty(styles, "name", "A", LexicalUnit.SAC_IDENT);
+
+ }
+ */
+
+
+ public void testBorderColor() throws Exception {
+ StyleSheet ss = parseStyleSheetResource("test2.css");
+ Styles styles;
+ Color red = new Color(255, 0, 0);
+ Color green = new Color(0, 128, 0);
+ Color blue = new Color(0, 0, 255);
+ Color white = new Color(255, 255, 255);
+
+ styles = ss.getStyles(new Element("borderColor1"));
+ assertEquals(red, styles.getBorderTopColor());
+ assertEquals(red, styles.getBorderLeftColor());
+ assertEquals(red, styles.getBorderRightColor());
+ assertEquals(red, styles.getBorderBottomColor());
+
+ styles = ss.getStyles(new Element("borderColor2"));
+ assertEquals(red, styles.getBorderTopColor());
+ assertEquals(green, styles.getBorderLeftColor());
+ assertEquals(green, styles.getBorderRightColor());
+ assertEquals(red, styles.getBorderBottomColor());
+
+ styles = ss.getStyles(new Element("borderColor3"));
+ assertEquals(red, styles.getBorderTopColor());
+ assertEquals(green, styles.getBorderLeftColor());
+ assertEquals(green, styles.getBorderRightColor());
+ assertEquals(blue, styles.getBorderBottomColor());
+
+ styles = ss.getStyles(new Element("borderColor4"));
+ assertEquals(red, styles.getBorderTopColor());
+ assertEquals(green, styles.getBorderRightColor());
+ assertEquals(blue, styles.getBorderBottomColor());
+ assertEquals(white, styles.getBorderLeftColor());
+
+ }
+
+ public void testBorderStyle() throws Exception {
+ StyleSheet ss = parseStyleSheetResource("test2.css");
+ Styles styles;
+
+ styles = ss.getStyles(new Element("borderStyle1"));
+ assertEquals(CSS.SOLID, styles.getBorderTopStyle());
+ assertEquals(CSS.SOLID, styles.getBorderLeftStyle());
+ assertEquals(CSS.SOLID, styles.getBorderRightStyle());
+ assertEquals(CSS.SOLID, styles.getBorderBottomStyle());
+
+ styles = ss.getStyles(new Element("borderStyle2"));
+ assertEquals(CSS.SOLID, styles.getBorderTopStyle());
+ assertEquals(CSS.DOTTED, styles.getBorderLeftStyle());
+ assertEquals(CSS.DOTTED, styles.getBorderRightStyle());
+ assertEquals(CSS.SOLID, styles.getBorderBottomStyle());
+
+ styles = ss.getStyles(new Element("borderStyle3"));
+ assertEquals(CSS.SOLID, styles.getBorderTopStyle());
+ assertEquals(CSS.DOTTED, styles.getBorderLeftStyle());
+ assertEquals(CSS.DOTTED, styles.getBorderRightStyle());
+ assertEquals(CSS.DASHED, styles.getBorderBottomStyle());
+
+ styles = ss.getStyles(new Element("borderStyle4"));
+ assertEquals(CSS.SOLID, styles.getBorderTopStyle());
+ assertEquals(CSS.DOTTED, styles.getBorderRightStyle());
+ assertEquals(CSS.DASHED, styles.getBorderBottomStyle());
+ assertEquals(CSS.OUTSET, styles.getBorderLeftStyle());
+
+ }
+
+ public void testBorderWidth() throws Exception {
+ StyleSheet ss = parseStyleSheetResource("test2.css");
+ Styles styles;
+
+ styles = ss.getStyles(new Element("borderWidth1"));
+ assertEquals(1, styles.getBorderTopWidth());
+ assertEquals(1, styles.getBorderLeftWidth());
+ assertEquals(1, styles.getBorderRightWidth());
+ assertEquals(1, styles.getBorderBottomWidth());
+
+ styles = ss.getStyles(new Element("borderWidth2"));
+ assertEquals(1, styles.getBorderTopWidth());
+ assertEquals(2, styles.getBorderLeftWidth());
+ assertEquals(2, styles.getBorderRightWidth());
+ assertEquals(1, styles.getBorderBottomWidth());
+
+ styles = ss.getStyles(new Element("borderWidth3"));
+ assertEquals(1, styles.getBorderTopWidth());
+ assertEquals(2, styles.getBorderLeftWidth());
+ assertEquals(2, styles.getBorderRightWidth());
+ assertEquals(3, styles.getBorderBottomWidth());
+
+ styles = ss.getStyles(new Element("borderWidth4"));
+ assertEquals(1, styles.getBorderTopWidth());
+ assertEquals(2, styles.getBorderRightWidth());
+ assertEquals(3, styles.getBorderBottomWidth());
+ assertEquals(4, styles.getBorderLeftWidth());
+
+ }
+
+ public void testDefaults() throws Exception {
+ StyleSheet ss = parseStyleSheetResource("test2.css");
+ Styles styles = ss.getStyles(new Element("defaults"));
+
+ assertEquals(15.0f, styles.getFontSize(), 0.1);
+
+ assertNull(styles.getBackgroundColor());
+
+ assertEquals(new Color(0, 0, 0), styles.getBorderBottomColor());
+ assertEquals(CSS.NONE, styles.getBorderBottomStyle());
+ assertEquals(0, styles.getBorderBottomWidth());
+
+ assertEquals(new Color(0, 0, 0), styles.getBorderLeftColor());
+ assertEquals(CSS.NONE, styles.getBorderLeftStyle());
+ assertEquals(0, styles.getBorderLeftWidth());
+
+ assertEquals(new Color(0, 0, 0), styles.getBorderRightColor());
+ assertEquals(CSS.NONE, styles.getBorderRightStyle());
+ assertEquals(0, styles.getBorderRightWidth());
+
+ assertEquals(new Color(0, 0, 0), styles.getBorderTopColor());
+ assertEquals(CSS.NONE, styles.getBorderTopStyle());
+ assertEquals(0, styles.getBorderTopWidth());
+
+ assertEquals(new Color(0, 0, 0), styles.getColor());
+ assertEquals(CSS.INLINE, styles.getDisplay());
+
+ assertEquals(0, styles.getMarginBottom().get(10));
+ assertEquals(0, styles.getMarginLeft().get(10));
+ assertEquals(0, styles.getMarginRight().get(10));
+ assertEquals(0, styles.getMarginTop().get(10));
+
+ assertEquals(0, styles.getPaddingBottom().get(10));
+ assertEquals(0, styles.getPaddingLeft().get(10));
+ assertEquals(0, styles.getPaddingRight().get(10));
+ assertEquals(0, styles.getPaddingTop().get(10));
+ }
+
+ /**
+ * Check the correct properties are inherited by default.
+ */
+ public void testDefaultInheritance() throws Exception {
+ RootElement simple = new RootElement("simple");
+ Element defaults = new Element("defaults");
+ Document doc = new Document(simple);
+ doc.insertElement(1, defaults);
+
+ StyleSheet ss = parseStyleSheetResource("test2.css");
+ Styles styles = ss.getStyles(defaults);
+
+ assertEquals(12.5f, styles.getFontSize(), 0.1);
+
+ assertNull(styles.getBackgroundColor());
+
+ assertEquals(new Color(0, 128, 0), styles.getBorderBottomColor());
+ assertEquals(CSS.NONE, styles.getBorderBottomStyle());
+ assertEquals(0, styles.getBorderBottomWidth());
+
+ assertEquals(new Color(0, 128, 0), styles.getBorderLeftColor());
+ assertEquals(CSS.NONE, styles.getBorderLeftStyle());
+ assertEquals(0, styles.getBorderLeftWidth());
+
+ assertEquals(new Color(0, 128, 0), styles.getBorderRightColor());
+ assertEquals(CSS.NONE, styles.getBorderRightStyle());
+ assertEquals(0, styles.getBorderRightWidth());
+
+ assertEquals(new Color(0, 128, 0), styles.getBorderTopColor());
+ assertEquals(CSS.NONE, styles.getBorderTopStyle());
+ assertEquals(0, styles.getBorderTopWidth());
+
+ assertEquals(new Color(0, 128, 0), styles.getColor());
+ assertEquals(CSS.INLINE, styles.getDisplay());
+
+ assertEquals(0, styles.getMarginBottom().get(10));
+ assertEquals(0, styles.getMarginLeft().get(10));
+ assertEquals(0, styles.getMarginRight().get(10));
+ assertEquals(0, styles.getMarginTop().get(10));
+
+ assertEquals(0, styles.getPaddingBottom().get(10));
+ assertEquals(0, styles.getPaddingLeft().get(10));
+ assertEquals(0, styles.getPaddingRight().get(10));
+ assertEquals(0, styles.getPaddingTop().get(10));
+ }
+
+ public void testExpandBorder() throws Exception {
+ StyleSheet ss = parseStyleSheetResource("test2.css");
+ Styles styles;
+
+ styles = ss.getStyles(new Element("expandBorder"));
+ assertEquals(2, styles.getBorderBottomWidth());
+ assertEquals(CSS.SOLID, styles.getBorderBottomStyle());
+ assertEquals(new Color(255, 0, 0), styles.getBorderBottomColor());
+ assertEquals(2, styles.getBorderLeftWidth());
+ assertEquals(CSS.SOLID, styles.getBorderLeftStyle());
+ assertEquals(new Color(255, 0, 0), styles.getBorderLeftColor());
+ assertEquals(2, styles.getBorderRightWidth());
+ assertEquals(CSS.SOLID, styles.getBorderRightStyle());
+ assertEquals(new Color(255, 0, 0), styles.getBorderRightColor());
+ assertEquals(2, styles.getBorderTopWidth());
+ assertEquals(CSS.SOLID, styles.getBorderTopStyle());
+ assertEquals(new Color(255, 0, 0), styles.getBorderTopColor());
+
+ styles = ss.getStyles(new Element("expandBorderBottom"));
+ assertEquals(2, styles.getBorderBottomWidth());
+ assertEquals(CSS.SOLID, styles.getBorderBottomStyle());
+ assertEquals(new Color(255, 0, 0), styles.getBorderBottomColor());
+ assertEquals(0, styles.getBorderLeftWidth());
+ assertEquals(CSS.NONE, styles.getBorderLeftStyle());
+ assertEquals(new Color(0, 0, 0), styles.getBorderLeftColor());
+ assertEquals(0, styles.getBorderRightWidth());
+ assertEquals(CSS.NONE, styles.getBorderRightStyle());
+ assertEquals(new Color(0, 0, 0), styles.getBorderRightColor());
+ assertEquals(0, styles.getBorderTopWidth());
+ assertEquals(CSS.NONE, styles.getBorderTopStyle());
+ assertEquals(new Color(0, 0, 0), styles.getBorderTopColor());
+
+ styles = ss.getStyles(new Element("expandBorderLeft"));
+ assertEquals(0, styles.getBorderBottomWidth());
+ assertEquals(CSS.NONE, styles.getBorderBottomStyle());
+ assertEquals(new Color(0, 0, 0), styles.getBorderBottomColor());
+ assertEquals(2, styles.getBorderLeftWidth());
+ assertEquals(CSS.SOLID, styles.getBorderLeftStyle());
+ assertEquals(new Color(255, 0, 0), styles.getBorderLeftColor());
+ assertEquals(0, styles.getBorderRightWidth());
+ assertEquals(CSS.NONE, styles.getBorderRightStyle());
+ assertEquals(new Color(0, 0, 0), styles.getBorderRightColor());
+ assertEquals(0, styles.getBorderTopWidth());
+ assertEquals(CSS.NONE, styles.getBorderTopStyle());
+ assertEquals(new Color(0, 0, 0), styles.getBorderTopColor());
+
+ styles = ss.getStyles(new Element("expandBorderRight"));
+ assertEquals(0, styles.getBorderBottomWidth());
+ assertEquals(CSS.NONE, styles.getBorderBottomStyle());
+ assertEquals(new Color(0, 0, 0), styles.getBorderBottomColor());
+ assertEquals(0, styles.getBorderLeftWidth());
+ assertEquals(CSS.NONE, styles.getBorderLeftStyle());
+ assertEquals(new Color(0, 0, 0), styles.getBorderLeftColor());
+ assertEquals(2, styles.getBorderRightWidth());
+ assertEquals(CSS.SOLID, styles.getBorderRightStyle());
+ assertEquals(new Color(255, 0, 0), styles.getBorderRightColor());
+ assertEquals(0, styles.getBorderTopWidth());
+ assertEquals(CSS.NONE, styles.getBorderTopStyle());
+ assertEquals(new Color(0, 0, 0), styles.getBorderTopColor());
+
+ styles = ss.getStyles(new Element("expandBorderTop"));
+ assertEquals(0, styles.getBorderBottomWidth());
+ assertEquals(CSS.NONE, styles.getBorderBottomStyle());
+ assertEquals(new Color(0, 0, 0), styles.getBorderBottomColor());
+ assertEquals(0, styles.getBorderLeftWidth());
+ assertEquals(CSS.NONE, styles.getBorderLeftStyle());
+ assertEquals(new Color(0, 0, 0), styles.getBorderLeftColor());
+ assertEquals(0, styles.getBorderRightWidth());
+ assertEquals(CSS.NONE, styles.getBorderRightStyle());
+ assertEquals(new Color(0, 0, 0), styles.getBorderRightColor());
+ assertEquals(2, styles.getBorderTopWidth());
+ assertEquals(CSS.SOLID, styles.getBorderTopStyle());
+ assertEquals(new Color(255, 0, 0), styles.getBorderTopColor());
+
+ styles = ss.getStyles(new Element("expandBorder1"));
+ assertEquals(2, styles.getBorderBottomWidth());
+ assertEquals(CSS.SOLID, styles.getBorderBottomStyle());
+ assertEquals(new Color(0, 0, 0), styles.getBorderBottomColor());
+
+ styles = ss.getStyles(new Element("expandBorder2"));
+ assertEquals(0, styles.getBorderBottomWidth());
+ assertEquals(CSS.NONE, styles.getBorderBottomStyle());
+ assertEquals(new Color(255, 0, 0), styles.getBorderBottomColor());
+
+ styles = ss.getStyles(new Element("expandBorder3"));
+ assertEquals(0, styles.getBorderBottomWidth());
+ assertEquals(CSS.NONE, styles.getBorderBottomStyle());
+ assertEquals(new Color(0, 0, 0), styles.getBorderBottomColor());
+
+ styles = ss.getStyles(new Element("expandBorder4"));
+ assertEquals(3, styles.getBorderBottomWidth());
+ assertEquals(CSS.SOLID, styles.getBorderBottomStyle());
+ assertEquals(new Color(255, 0, 0), styles.getBorderBottomColor());
+
+ styles = ss.getStyles(new Element("expandBorder5"));
+ assertEquals(3, styles.getBorderBottomWidth());
+ assertEquals(CSS.SOLID, styles.getBorderBottomStyle());
+ assertEquals(new Color(0, 0, 0), styles.getBorderBottomColor());
+
+ styles = ss.getStyles(new Element("expandBorder6"));
+ assertEquals(0, styles.getBorderBottomWidth());
+ assertEquals(CSS.NONE, styles.getBorderBottomStyle());
+ assertEquals(new Color(255, 0, 0), styles.getBorderBottomColor());
+
+ }
+
+ public void testExpandMargins() throws Exception {
+ StyleSheet ss = parseStyleSheetResource("test2.css");
+
+ Styles styles = ss.getStyles(new Element("margin1"));
+ assertEquals(10, styles.getMarginTop().get(67));
+ assertEquals(10, styles.getMarginLeft().get(67));
+ assertEquals(10, styles.getMarginRight().get(67));
+ assertEquals(10, styles.getMarginBottom().get(67));
+
+ styles = ss.getStyles(new Element("margin2"));
+ assertEquals(10, styles.getMarginTop().get(67));
+ assertEquals(20, styles.getMarginLeft().get(67));
+ assertEquals(20, styles.getMarginRight().get(67));
+ assertEquals(10, styles.getMarginBottom().get(67));
+
+ styles = ss.getStyles(new Element("margin3"));
+ assertEquals(10, styles.getMarginTop().get(67));
+ assertEquals(20, styles.getMarginLeft().get(67));
+ assertEquals(20, styles.getMarginRight().get(67));
+ assertEquals(30, styles.getMarginBottom().get(67));
+
+ styles = ss.getStyles(new Element("margin4"));
+ assertEquals(10, styles.getMarginTop().get(67));
+ assertEquals(20, styles.getMarginRight().get(67));
+ assertEquals(30, styles.getMarginBottom().get(67));
+ assertEquals(40, styles.getMarginLeft().get(67));
+ }
+
+ public void testExtras() throws Exception {
+ StyleSheet ss = parseStyleSheetResource("test2.css");
+ Styles styles = ss.getStyles(new Element("extras"));
+
+ assertEquals(new Color(0, 255, 0), styles.getBackgroundColor());
+
+ assertEquals(new Color(128, 0, 0), styles.getBorderBottomColor());
+ assertEquals(CSS.SOLID, styles.getBorderBottomStyle());
+
+ assertEquals(new Color(0, 0, 128), styles.getBorderLeftColor());
+ assertEquals(CSS.DASHED, styles.getBorderLeftStyle());
+
+ assertEquals(new Color(128, 128, 0), styles.getBorderRightColor());
+ assertEquals(CSS.DOTTED, styles.getBorderRightStyle());
+
+ assertEquals(new Color(128, 0, 128), styles.getBorderTopColor());
+ assertEquals(CSS.DOUBLE, styles.getBorderTopStyle());
+
+ assertEquals(new Color(255, 0, 0), styles.getColor());
+ assertEquals(CSS.INLINE, styles.getDisplay());
+ }
+
+ public void testExtras2() throws Exception {
+ StyleSheet ss = parseStyleSheetResource("test2.css");
+ Styles styles = ss.getStyles(new Element("extras2"));
+
+ assertEquals(new Color(192, 192, 192), styles.getBackgroundColor());
+
+ assertEquals(new Color(0, 128, 128), styles.getBorderBottomColor());
+ assertEquals(CSS.NONE, styles.getBorderBottomStyle());
+ assertEquals(0, styles.getBorderBottomWidth());
+
+ assertEquals(new Color(255, 255, 255), styles.getBorderLeftColor());
+ assertEquals(CSS.GROOVE, styles.getBorderLeftStyle());
+
+ assertEquals(new Color(255, 255, 0), styles.getBorderRightColor());
+ assertEquals(CSS.RIDGE, styles.getBorderRightStyle());
+
+ assertEquals(CSS.INSET, styles.getBorderTopStyle());
+ }
+
+ /**
+ * Test the symbolic font sizes.
+ */
+ public void testFontSize() throws Exception {
+
+ StyleSheet ss = parseStyleSheetResource("test2.css");
+ Styles styles;
+
+ styles = ss.getStyles(new Element("medium"));
+ assertEquals(15.0f, styles.getFontSize(), 0.1);
+
+ styles = ss.getStyles(new Element("small"));
+ assertEquals(12.5f, styles.getFontSize(), 0.1);
+
+ styles = ss.getStyles(new Element("xsmall"));
+ assertEquals(10.4f, styles.getFontSize(), 0.1);
+
+ styles = ss.getStyles(new Element("xxsmall"));
+ assertEquals(8.7f, styles.getFontSize(), 0.1);
+
+ styles = ss.getStyles(new Element("large"));
+ assertEquals(18.0f, styles.getFontSize(), 0.1);
+
+ styles = ss.getStyles(new Element("xlarge"));
+ assertEquals(21.6f, styles.getFontSize(), 0.1);
+
+ styles = ss.getStyles(new Element("xxlarge"));
+ assertEquals(25.9, styles.getFontSize(), 0.1);
+
+ styles = ss.getStyles(new Element("smaller"));
+ assertEquals(12.5f, styles.getFontSize(), 0.1);
+
+ styles = ss.getStyles(new Element("font100pct"));
+ assertEquals(15.0f, styles.getFontSize(), 0.1);
+
+ styles = ss.getStyles(new Element("font80pct"));
+ assertEquals(12.0f, styles.getFontSize(), 0.1);
+
+ styles = ss.getStyles(new Element("font120pct"));
+ assertEquals(18.0f, styles.getFontSize(), 0.1);
+
+ }
+
+ public void testForcedInheritance() throws Exception {
+ RootElement simple = new RootElement("simple");
+ Element inherit = new Element("inherit");
+ Document doc = new Document(simple);
+ doc.insertElement(1, inherit);
+
+ StyleSheet ss = parseStyleSheetResource("test2.css");
+ Styles styles = ss.getStyles(inherit);
+
+ assertEquals(12.5f, styles.getFontSize(), 0.1);
+
+ assertEquals(new Color(0, 255, 255), styles.getBackgroundColor());
+
+ assertEquals(new Color(0, 0, 0), styles.getBorderBottomColor());
+ assertEquals(CSS.SOLID, styles.getBorderBottomStyle());
+ assertEquals(1, styles.getBorderBottomWidth());
+
+ assertEquals(new Color(0, 0, 255), styles.getBorderLeftColor());
+ assertEquals(CSS.DASHED, styles.getBorderLeftStyle());
+ assertEquals(3, styles.getBorderLeftWidth());
+
+ assertEquals(new Color(255, 0, 255), styles.getBorderRightColor());
+ assertEquals(CSS.DOTTED, styles.getBorderRightStyle());
+ assertEquals(5, styles.getBorderRightWidth());
+
+ assertEquals(new Color(128, 128, 128), styles.getBorderTopColor());
+ assertEquals(CSS.DOUBLE, styles.getBorderTopStyle());
+ assertEquals(1, styles.getBorderTopWidth());
+
+ assertEquals(new Color(0, 128, 0), styles.getColor());
+ assertEquals(CSS.BLOCK, styles.getDisplay());
+
+ assertEquals(3543, styles.getMarginBottom().get(10));
+ assertEquals(0, styles.getMarginLeft().get(10));
+ assertEquals(125, styles.getMarginRight().get(10));
+ assertEquals(75, styles.getMarginTop().get(10));
+
+ assertEquals(450, styles.getPaddingBottom().get(10));
+ assertEquals(4252, styles.getPaddingLeft().get(10));
+ assertEquals(120, styles.getPaddingRight().get(10));
+ assertEquals(19, styles.getPaddingTop().get(10));
+ }
+
+ public void testImportant() throws Exception {
+ StyleSheet ss = parseStyleSheetResource("testImportant.css");
+ Element a = new Element("a");
+ Styles styles = ss.getStyles(a);
+
+ Color black = new Color(0, 0, 0);
+ //Color white = new Color(255, 255, 255);
+ //Color red = new Color(255, 0, 0);
+ Color blue = new Color(0, 0, 255);
+
+ assertEquals(black, styles.getBackgroundColor());
+ assertEquals(black, styles.getColor());
+ assertEquals(blue, styles.getBorderTopColor());
+
+ }
+
+ public void testMarginInheritance() throws Exception {
+ StyleSheet ss = parseStyleSheetResource("test2.css");
+ Element root = new Element("margin1");
+ Element child = new Element("defaults");
+ child.setParent(root);
+ Styles styles = ss.getStyles(child);
+
+ assertEquals(0, styles.getMarginTop().get(67));
+ assertEquals(0, styles.getMarginLeft().get(67));
+ assertEquals(0, styles.getMarginRight().get(67));
+ assertEquals(0, styles.getMarginBottom().get(67));
+ }
+
+ public void testSimple() throws Exception {
+ StyleSheet ss = parseStyleSheetResource("test2.css");
+ Styles styles = ss.getStyles(new Element("simple"));
+
+ assertEquals(12.5f, styles.getFontSize(), 0.1);
+
+ assertEquals(new Color(0, 255, 255), styles.getBackgroundColor());
+
+ assertEquals(new Color(0, 0, 0), styles.getBorderBottomColor());
+ assertEquals(CSS.SOLID, styles.getBorderBottomStyle());
+ assertEquals(1, styles.getBorderBottomWidth());
+
+ assertEquals(new Color(0, 0, 255), styles.getBorderLeftColor());
+ assertEquals(CSS.DASHED, styles.getBorderLeftStyle());
+ assertEquals(3, styles.getBorderLeftWidth());
+
+ assertEquals(new Color(255, 0, 255), styles.getBorderRightColor());
+ assertEquals(CSS.DOTTED, styles.getBorderRightStyle());
+ assertEquals(5, styles.getBorderRightWidth());
+
+ assertEquals(new Color(128, 128, 128), styles.getBorderTopColor());
+ assertEquals(CSS.DOUBLE, styles.getBorderTopStyle());
+ assertEquals(1, styles.getBorderTopWidth());
+
+ assertEquals(new Color(0, 128, 0), styles.getColor());
+ assertEquals(CSS.BLOCK, styles.getDisplay());
+
+ assertEquals(3543, styles.getMarginBottom().get(10));
+ assertEquals(0, styles.getMarginLeft().get(10));
+ assertEquals(125, styles.getMarginRight().get(10));
+ assertEquals(75, styles.getMarginTop().get(10));
+
+ assertEquals(450, styles.getPaddingBottom().get(10));
+ assertEquals(4252, styles.getPaddingLeft().get(10));
+ assertEquals(120, styles.getPaddingRight().get(10));
+ assertEquals(19, styles.getPaddingTop().get(10));
+
+ }
+
+
+ /**
+ * Confirm our assumptions about the structure of lexical units.
+ */
+ /*
+ public void testLexicalUnits() throws Exception {
+ Element aElement = new Element("A");
+ StyleSheet ss = parseStyleSheetResource("testLexicalUnits.css");
+ Styles styles = ss.get(aElement);
+
+ LexicalUnit lu;
+ LexicalUnit lu2;
+
+ System.out.println("DEBUG: styles for element A");
+ dumpStyles(styles);
+
+ // TEST: how to access color specified in "rgb(R,G,B)" format
+ lu = styles.get(CSS.COLOR);
+ assertEquals(LexicalUnit.SAC_RGBCOLOR, lu.getLexicalUnitType());
+ assertEquals("color", lu.getFunctionName());
+ lu2 = lu.getParameters();
+ assertNotNull(lu2);
+ assertEquals(LexicalUnit.SAC_INTEGER, lu2.getLexicalUnitType());
+ assertEquals(255, lu2.getIntegerValue());
+ lu2 = lu2.getNextLexicalUnit();
+ assertEquals(LexicalUnit.SAC_OPERATOR_COMMA, lu2.getLexicalUnitType());
+ lu2 = lu2.getNextLexicalUnit();
+ assertEquals(LexicalUnit.SAC_INTEGER, lu2.getLexicalUnitType());
+ assertEquals(255, lu2.getIntegerValue());
+ lu2 = lu2.getNextLexicalUnit();
+ assertEquals(LexicalUnit.SAC_OPERATOR_COMMA, lu2.getLexicalUnitType());
+ lu2 = lu2.getNextLexicalUnit();
+ assertEquals(LexicalUnit.SAC_INTEGER, lu2.getLexicalUnitType());
+ assertEquals(255, lu2.getIntegerValue());
+ lu2 = lu2.getNextLexicalUnit();
+ assertNull(lu2);
+
+ // TEST: color specified in "#RGB" format is accessed the same way
+ lu = styles.get(CSS.BACKGROUND_COLOR);
+ assertEquals(LexicalUnit.SAC_RGBCOLOR, lu.getLexicalUnitType());
+ assertEquals("color", lu.getFunctionName());
+ lu2 = lu.getParameters();
+ assertNotNull(lu2);
+ assertEquals(LexicalUnit.SAC_INTEGER, lu2.getLexicalUnitType());
+ assertEquals(0, lu2.getIntegerValue());
+ lu2 = lu2.getNextLexicalUnit();
+ assertEquals(LexicalUnit.SAC_OPERATOR_COMMA, lu2.getLexicalUnitType());
+ lu2 = lu2.getNextLexicalUnit();
+ assertEquals(LexicalUnit.SAC_INTEGER, lu2.getLexicalUnitType());
+ assertEquals(0, lu2.getIntegerValue());
+ lu2 = lu2.getNextLexicalUnit();
+ assertEquals(LexicalUnit.SAC_OPERATOR_COMMA, lu2.getLexicalUnitType());
+ lu2 = lu2.getNextLexicalUnit();
+ assertEquals(LexicalUnit.SAC_INTEGER, lu2.getLexicalUnitType());
+ assertEquals(0, lu2.getIntegerValue());
+ lu2 = lu2.getNextLexicalUnit();
+ assertNull(lu2);
+
+ // TEST: color specified in "rgb(R%,G%,B%)" is accessed as SAC_PERCENTAGE
+ lu = styles.get(CSS.BORDER_BOTTOM);
+ assertEquals(LexicalUnit.SAC_RGBCOLOR, lu.getLexicalUnitType());
+ assertEquals("color", lu.getFunctionName());
+ lu2 = lu.getParameters();
+ assertNotNull(lu2);
+ assertEquals(LexicalUnit.SAC_PERCENTAGE, lu2.getLexicalUnitType());
+ assertEquals(10f, lu2.getFloatValue(), 0.001);
+ lu2 = lu2.getNextLexicalUnit();
+ assertEquals(LexicalUnit.SAC_OPERATOR_COMMA, lu2.getLexicalUnitType());
+ lu2 = lu2.getNextLexicalUnit();
+ assertEquals(LexicalUnit.SAC_PERCENTAGE, lu2.getLexicalUnitType());
+ assertEquals(20f, lu2.getFloatValue(), 0.001);
+ lu2 = lu2.getNextLexicalUnit();
+ assertEquals(LexicalUnit.SAC_OPERATOR_COMMA, lu2.getLexicalUnitType());
+ lu2 = lu2.getNextLexicalUnit();
+ assertEquals(LexicalUnit.SAC_PERCENTAGE, lu2.getLexicalUnitType());
+ assertEquals(30f, lu2.getFloatValue(), 0.001);
+ lu2 = lu2.getNextLexicalUnit();
+ assertNull(lu2);
+
+ // TEST: color incompletely specified, e.g. "rgb(10,20)" is rejected
+ // by the parser
+ lu = styles.get(CSS.BORDER_LEFT);
+ assertNull(lu);
+
+ // TEST: color incorrectly specified, e.g. "10", "foo", or
+ // "rgb(larry,curly,moe)" is passed by the parser
+ lu = styles.get(CSS.BORDER_RIGHT); // "10" is not a valid lexical unit
+ assertNull(lu);
+
+ lu = styles.get(CSS.BORDER_TOP); // "rgb(larry, curly, moe)"
+ assertNull(lu);
+
+ styles = ss.get(new Element("B"));
+ lu = styles.get(CSS.BACKGROUND_COLOR); // "foo"
+ assertEquals(LexicalUnit.SAC_IDENT, lu.getLexicalUnitType());
+ assertEquals("foo", lu.getStringValue());
+
+ lu = styles.get(CSS.COLOR); // "10px"
+ assertEquals(LexicalUnit.SAC_PIXEL, lu.getLexicalUnitType());
+ //assertEquals(10, lu.getIntegerValue()); // NOTE: not an int!
+ assertEquals(10f, lu.getFloatValue(), 0.001);
+ }
+
+
+ public void testExpandMargins() throws Exception {
+ Element aElement = new Element("A");
+ Element bElement = new Element("B");
+ Element cElement = new Element("C");
+ Element dElement = new Element("D");
+ Element eElement = new Element("E");
+ Element fElement = new Element("F");
+ Element gElement = new Element("G");
+ Document doc = new Document(new Element("root"));
+ doc.insertElement(1, aElement);
+ doc.insertElement(3, cElement);
+ doc.insertElement(5, cElement);
+ doc.insertElement(7, cElement);
+
+ StyleSheet ss = parseStyleSheetResource("expansion.css");
+ Styles styles;
+
+ // single margin call expands
+ styles = ss.get(aElement);
+ assertProperty(styles, CSS.MARGIN_BOTTOM, 1.0f, LexicalUnit.SAC_INCH);
+ assertProperty(styles, CSS.MARGIN_LEFT, 1.0f, LexicalUnit.SAC_INCH);
+ assertProperty(styles, CSS.MARGIN_RIGHT, 1.0f, LexicalUnit.SAC_INCH);
+ assertProperty(styles, CSS.MARGIN_TOP, 1.0f, LexicalUnit.SAC_INCH);
+
+ // more-specific overrides; shorthand comes first
+ styles = ss.get(bElement);
+ assertProperty(styles, CSS.MARGIN_BOTTOM, 0.5f, LexicalUnit.SAC_INCH);
+ assertProperty(styles, CSS.MARGIN_LEFT, 0.5f, LexicalUnit.SAC_INCH);
+ assertProperty(styles, CSS.MARGIN_RIGHT, 0.5f, LexicalUnit.SAC_INCH);
+ assertProperty(styles, CSS.MARGIN_TOP, 0.5f, LexicalUnit.SAC_INCH);
+
+ // more-specific overrides; shorthand comes last
+ styles = ss.get(cElement);
+ assertProperty(styles, CSS.MARGIN_BOTTOM, 0.5f, LexicalUnit.SAC_INCH);
+ assertProperty(styles, CSS.MARGIN_LEFT, 0.5f, LexicalUnit.SAC_INCH);
+ assertProperty(styles, CSS.MARGIN_RIGHT, 0.5f, LexicalUnit.SAC_INCH);
+ assertProperty(styles, CSS.MARGIN_TOP, 0.5f, LexicalUnit.SAC_INCH);
+
+ // second shorthand overrides first
+ styles = ss.get(dElement);
+ assertProperty(styles, CSS.MARGIN_BOTTOM, 0.25f, LexicalUnit.SAC_INCH);
+ assertProperty(styles, CSS.MARGIN_LEFT, 0.25f, LexicalUnit.SAC_INCH);
+ assertProperty(styles, CSS.MARGIN_RIGHT, 0.25f, LexicalUnit.SAC_INCH);
+ assertProperty(styles, CSS.MARGIN_TOP, 0.25f, LexicalUnit.SAC_INCH);
+
+ // expanding two values for margins
+ styles = ss.get(eElement);
+ assertProperty(styles, CSS.MARGIN_BOTTOM, 1.0f, LexicalUnit.SAC_INCH);
+ assertProperty(styles, CSS.MARGIN_LEFT, 2.0f, LexicalUnit.SAC_INCH);
+ assertProperty(styles, CSS.MARGIN_RIGHT, 2.0f, LexicalUnit.SAC_INCH);
+ assertProperty(styles, CSS.MARGIN_TOP, 1.0f, LexicalUnit.SAC_INCH);
+
+ // expanding three values for margins
+ styles = ss.get(fElement);
+ assertProperty(styles, CSS.MARGIN_BOTTOM, 3.0f, LexicalUnit.SAC_INCH);
+ assertProperty(styles, CSS.MARGIN_LEFT, 2.0f, LexicalUnit.SAC_INCH);
+ assertProperty(styles, CSS.MARGIN_RIGHT, 2.0f, LexicalUnit.SAC_INCH);
+ assertProperty(styles, CSS.MARGIN_TOP, 1.0f, LexicalUnit.SAC_INCH);
+
+ // expanding four values for margins
+ styles = ss.get(gElement);
+ assertProperty(styles, CSS.MARGIN_TOP, 1.0f, LexicalUnit.SAC_INCH);
+ assertProperty(styles, CSS.MARGIN_RIGHT, 2.0f, LexicalUnit.SAC_INCH);
+ assertProperty(styles, CSS.MARGIN_BOTTOM, 3.0f, LexicalUnit.SAC_INCH);
+ assertProperty(styles, CSS.MARGIN_LEFT, 4.0f, LexicalUnit.SAC_INCH);
+
+ }
+
+
+ public static void assertProperty(Styles styles,
+ String name,
+ float value,
+ int lexicalUnitType) {
+
+ LexicalUnit lu = styles.get(name);
+ assertEquals(lexicalUnitType, lu.getLexicalUnitType());
+ assertEquals(value, lu.getFloatValue(), 0.001f);
+ }
+
+ public static void assertProperty(Styles styles,
+ String name,
+ String value,
+ int lexicalUnitType) {
+
+ LexicalUnit lu = styles.get(name);
+ assertEquals(lexicalUnitType, lu.getLexicalUnitType());
+ assertEquals(value, lu.getStringValue());
+ }
+
+ public static void dumpStyles(Styles styles) {
+ java.util.Iterator iter = styles.getPropertyNames().iterator();
+ while (iter.hasNext()) {
+ String name = (String) iter.next();
+ System.out.println(name + ": " + styles.get(name));
+ }
+ }
+ */
+
+ private StyleSheet parseStyleSheetResource(String resource)
+ throws java.io.IOException {
+
+ URL url = this.getClass().getResource(resource);
+ StyleSheetReader reader = new StyleSheetReader();
+ return reader.read(url);
+
+ }
+}
+
diff --git a/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/src/org/eclipse/wst/xml/vex/core/internal/css/PropertyTest.java b/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/src/org/eclipse/wst/xml/vex/core/internal/css/PropertyTest.java
new file mode 100644
index 0000000..b53eb40
--- /dev/null
+++ b/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/src/org/eclipse/wst/xml/vex/core/internal/css/PropertyTest.java
@@ -0,0 +1,125 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.css;
+
+import org.eclipse.wst.xml.vex.core.internal.core.DisplayDevice;
+import org.eclipse.wst.xml.vex.core.internal.css.BorderStyleProperty;
+import org.eclipse.wst.xml.vex.core.internal.css.BorderWidthProperty;
+import org.eclipse.wst.xml.vex.core.internal.css.CSS;
+import org.eclipse.wst.xml.vex.core.internal.css.IProperty;
+import org.eclipse.wst.xml.vex.core.internal.css.Styles;
+import org.w3c.css.sac.LexicalUnit;
+
+import junit.framework.TestCase;
+
+public class PropertyTest extends TestCase {
+
+ /**
+ * From CSS2.1 section 8.5.3
+ */
+ public void testBorderStyleProperty() throws Exception {
+ Styles styles = new Styles();
+ Styles parentStyles = new Styles();
+ IProperty prop = new BorderStyleProperty(CSS.BORDER_TOP_STYLE);
+
+ // Inheritance
+ parentStyles.put(CSS.BORDER_TOP_STYLE, CSS.DASHED);
+ assertEquals(CSS.NONE, prop.calculate(null, parentStyles, styles));
+ assertEquals(CSS.DASHED, prop.calculate(TestLU.INHERIT, parentStyles, styles)); // not inherited
+
+ // Regular values
+ assertEquals(CSS.NONE, prop.calculate(TestLU.createIdent(CSS.NONE), parentStyles, styles));
+ assertEquals(CSS.HIDDEN, prop.calculate(TestLU.createIdent(CSS.HIDDEN), parentStyles, styles));
+ assertEquals(CSS.DOTTED, prop.calculate(TestLU.createIdent(CSS.DOTTED), parentStyles, styles));
+ assertEquals(CSS.DASHED, prop.calculate(TestLU.createIdent(CSS.DASHED), parentStyles, styles));
+ assertEquals(CSS.SOLID, prop.calculate(TestLU.createIdent(CSS.SOLID), parentStyles, styles));
+ assertEquals(CSS.DOUBLE, prop.calculate(TestLU.createIdent(CSS.DOUBLE), parentStyles, styles));
+ assertEquals(CSS.GROOVE, prop.calculate(TestLU.createIdent(CSS.GROOVE), parentStyles, styles));
+ assertEquals(CSS.RIDGE, prop.calculate(TestLU.createIdent(CSS.RIDGE), parentStyles, styles));
+ assertEquals(CSS.INSET, prop.calculate(TestLU.createIdent(CSS.INSET), parentStyles, styles));
+ assertEquals(CSS.OUTSET, prop.calculate(TestLU.createIdent(CSS.OUTSET), parentStyles, styles));
+
+ // Invalid token
+ assertEquals(CSS.NONE, prop.calculate(TestLU.createIdent(CSS.BOLD), parentStyles, styles));
+
+ // Wrong type
+ assertEquals(CSS.NONE, prop.calculate(TestLU.createString(CSS.HIDDEN), parentStyles, styles));
+ }
+
+
+ /**
+ * From CSS2.1 section 8.5.1
+ */
+ public void testBorderWidthProperty() throws Exception {
+
+ Styles styles = new Styles();
+ Styles parentStyles = new Styles();
+ DisplayDevice.setCurrent(new DummyDisplayDevice(50, 100));
+ IProperty prop = new BorderWidthProperty(CSS.BORDER_TOP_WIDTH, CSS.BORDER_TOP_STYLE, IProperty.AXIS_VERTICAL);
+
+ styles.put(CSS.FONT_SIZE, new Float(12));
+ styles.put(CSS.BORDER_TOP_STYLE, CSS.SOLID);
+
+ // Inheritance
+ parentStyles.put(CSS.BORDER_TOP_WIDTH, new Integer(27));
+ assertEquals(new Integer(3), prop.calculate(null, parentStyles, styles));
+ assertEquals(new Integer(27), prop.calculate(TestLU.INHERIT, parentStyles, styles)); // not inherited
+
+ // Regular values
+ assertEquals(new Integer(20), prop.calculate(TestLU.createFloat(LexicalUnit.SAC_INCH, 0.2f), parentStyles, styles));
+
+ // Invalid token
+ assertEquals(new Integer(3), prop.calculate(TestLU.createIdent(CSS.BOLD), parentStyles, styles));
+
+ // Wrong type
+ assertEquals(new Integer(3), prop.calculate(TestLU.createString(CSS.HIDDEN), parentStyles, styles));
+
+ // Corresponding style is "none" or "hidden"
+ styles.put(CSS.BORDER_TOP_STYLE, CSS.NONE);
+ assertEquals(new Integer(0), prop.calculate(TestLU.createFloat(LexicalUnit.SAC_INCH, 0.2f), parentStyles, styles));
+ styles.put(CSS.BORDER_TOP_STYLE, CSS.HIDDEN);
+ assertEquals(new Integer(0), prop.calculate(TestLU.createFloat(LexicalUnit.SAC_INCH, 0.2f), parentStyles, styles));
+
+ // check that we use the proper PPI
+ styles.put(CSS.BORDER_LEFT_STYLE, CSS.SOLID);
+ prop = new BorderWidthProperty(CSS.BORDER_LEFT_WIDTH, CSS.BORDER_LEFT_STYLE, IProperty.AXIS_HORIZONTAL);
+ assertEquals(new Integer(10), prop.calculate(TestLU.createFloat(LexicalUnit.SAC_INCH, 0.2f), parentStyles, styles));
+ }
+
+
+ /**
+ * From CSS2.1 section 8.5.2 (border-XXX-color),
+ * section 14.1 (color), and section 14.2.1 (background-color)
+ */
+ public void testColorProperty() throws Exception {
+ }
+
+
+ private class DummyDisplayDevice extends DisplayDevice {
+
+ public DummyDisplayDevice(int horizontalPPI, int verticalPPI) {
+ this.horizontalPPI = horizontalPPI;
+ this.verticalPPI = verticalPPI;
+ }
+
+ public int getHorizontalPPI() {
+ return this.horizontalPPI;
+ }
+
+ public int getVerticalPPI() {
+ return this.verticalPPI;
+ }
+
+
+ private int horizontalPPI;
+ private int verticalPPI;
+ }
+}
diff --git a/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/src/org/eclipse/wst/xml/vex/core/internal/css/RuleTest.java b/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/src/org/eclipse/wst/xml/vex/core/internal/css/RuleTest.java
new file mode 100644
index 0000000..09c0ad7
--- /dev/null
+++ b/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/src/org/eclipse/wst/xml/vex/core/internal/css/RuleTest.java
@@ -0,0 +1,272 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.css;
+
+import java.net.URL;
+
+import org.eclipse.wst.xml.vex.core.internal.css.Rule;
+import org.eclipse.wst.xml.vex.core.internal.css.StyleSheet;
+import org.eclipse.wst.xml.vex.core.internal.css.StyleSheetReader;
+import org.eclipse.wst.xml.vex.core.internal.dom.Document;
+import org.eclipse.wst.xml.vex.core.internal.dom.Element;
+import org.eclipse.wst.xml.vex.core.internal.dom.RootElement;
+
+import junit.framework.TestCase;
+
+/**
+ * Test rule matching.
+ */
+public class RuleTest extends TestCase {
+
+ public void testRuleMatching() throws Exception {
+ URL url = RuleTest.class.getResource("testRules.css");
+ StyleSheetReader reader = new StyleSheetReader();
+ reader.setSource(StyleSheet.SOURCE_USER);
+ StyleSheet ss = reader.read(url);
+ Rule[] rules = ss.getRules();
+
+ RootElement a = new RootElement("a");
+ Element b = new Element("b");
+ Element c = new Element("c");
+ Element d = new Element("d");
+ Element e = new Element("e");
+ Element f = new Element("f");
+
+ b.setAttribute("color", "blue");
+ c.setAttribute("color", "blue red");
+ d.setAttribute("color", "gree blue red");
+ e.setAttribute("color", "red blue");
+ f.setAttribute("color", "bluered");
+
+ Document doc = new Document(a);
+ doc.insertElement(1, b);
+ doc.insertElement(2, c);
+ doc.insertElement(3, d);
+ doc.insertElement(4, e);
+ doc.insertElement(5, f);
+
+// /* 0 */ c { }
+ Rule rule = rules[0];
+ assertFalse(rule.matches(a));
+ assertFalse(rule.matches(b));
+ assertTrue(rule.matches(c));
+ assertFalse(rule.matches(d));
+ assertFalse(rule.matches(e));
+
+// /* 1 */ b c { }
+ rule = rules[1];
+ assertFalse(rule.matches(a));
+ assertFalse(rule.matches(b));
+ assertTrue(rule.matches(c));
+ assertFalse(rule.matches(d));
+ assertFalse(rule.matches(e));
+
+// /* 2 */ b d { }
+ rule = rules[2];
+ assertFalse(rule.matches(a));
+ assertFalse(rule.matches(b));
+ assertFalse(rule.matches(c));
+ assertTrue(rule.matches(d));
+ assertFalse(rule.matches(e));
+
+// /* 3 */ other b c { }
+ rule = rules[3];
+ assertFalse(rule.matches(a));
+ assertFalse(rule.matches(b));
+ assertFalse(rule.matches(c));
+ assertFalse(rule.matches(d));
+ assertFalse(rule.matches(e));
+
+// /* 4 */ other b d { }
+ rule = rules[4];
+ assertFalse(rule.matches(a));
+ assertFalse(rule.matches(b));
+ assertFalse(rule.matches(c));
+ assertFalse(rule.matches(d));
+ assertFalse(rule.matches(e));
+
+// /* 5 */ a c e { }
+ rule = rules[5];
+ assertFalse(rule.matches(a));
+ assertFalse(rule.matches(b));
+ assertFalse(rule.matches(c));
+ assertFalse(rule.matches(d));
+ assertTrue(rule.matches(e));
+
+// /* 6 */ c a e { }
+ rule = rules[6];
+ assertFalse(rule.matches(a));
+ assertFalse(rule.matches(b));
+ assertFalse(rule.matches(c));
+ assertFalse(rule.matches(d));
+ assertFalse(rule.matches(e));
+
+// /* 7 */ * { }
+ rule = rules[7];
+ assertTrue(rule.matches(a));
+ assertTrue(rule.matches(b));
+ assertTrue(rule.matches(c));
+ assertTrue(rule.matches(d));
+ assertTrue(rule.matches(e));
+
+// /* 8 */ *[color]
+ rule = rules[8];
+ assertFalse(rule.matches(a));
+ assertTrue(rule.matches(b));
+ assertTrue(rule.matches(c));
+ assertTrue(rule.matches(d));
+ assertTrue(rule.matches(e));
+
+// /* 9 */ a[color]
+ rule = rules[9];
+ assertFalse(rule.matches(a));
+ assertFalse(rule.matches(b));
+ assertFalse(rule.matches(c));
+ assertFalse(rule.matches(d));
+ assertFalse(rule.matches(e));
+
+// /* 10 */ b[color]
+ rule = rules[10];
+ assertFalse(rule.matches(a));
+ assertTrue(rule.matches(b));
+ assertFalse(rule.matches(c));
+ assertFalse(rule.matches(d));
+ assertFalse(rule.matches(e));
+
+// /* 11 */ c[color]
+ rule = rules[11];
+ assertFalse(rule.matches(a));
+ assertFalse(rule.matches(b));
+ assertTrue(rule.matches(c));
+ assertFalse(rule.matches(d));
+ assertFalse(rule.matches(e));
+
+// /* 12 */ d[color]
+ rule = rules[12];
+ assertFalse(rule.matches(a));
+ assertFalse(rule.matches(b));
+ assertFalse(rule.matches(c));
+ assertTrue(rule.matches(d));
+ assertFalse(rule.matches(e));
+
+// /* 13 */ *[color=blue]
+ rule = rules[13];
+ assertFalse(rule.matches(a));
+ assertTrue(rule.matches(b));
+ assertFalse(rule.matches(c));
+ assertFalse(rule.matches(d));
+ assertFalse(rule.matches(e));
+
+// /* 14 */ a[color=blue]
+ rule = rules[14];
+ assertFalse(rule.matches(a));
+ assertFalse(rule.matches(b));
+ assertFalse(rule.matches(c));
+ assertFalse(rule.matches(d));
+ assertFalse(rule.matches(e));
+
+// /* 15 */ b[color=blue]
+ rule = rules[15];
+ assertFalse(rule.matches(a));
+ assertTrue(rule.matches(b));
+ assertFalse(rule.matches(c));
+ assertFalse(rule.matches(d));
+ assertFalse(rule.matches(e));
+
+// /* 16 */ b[color='blue']
+ rule = rules[16];
+ assertFalse(rule.matches(a));
+ assertTrue(rule.matches(b));
+ assertFalse(rule.matches(c));
+ assertFalse(rule.matches(d));
+ assertFalse(rule.matches(e));
+
+// /* 17 */ b[color="blue"]
+ rule = rules[17];
+ assertFalse(rule.matches(a));
+ assertTrue(rule.matches(b));
+ assertFalse(rule.matches(c));
+ assertFalse(rule.matches(d));
+ assertFalse(rule.matches(e));
+
+// /* 18 */ c[color=blue]
+ rule = rules[18];
+ assertFalse(rule.matches(a));
+ assertFalse(rule.matches(b));
+ assertFalse(rule.matches(c));
+ assertFalse(rule.matches(d));
+ assertFalse(rule.matches(e));
+
+// /* 19 */ a * { }
+ rule = rules[19];
+ assertFalse(rule.matches(a));
+ assertTrue(rule.matches(b));
+ assertTrue(rule.matches(c));
+ assertTrue(rule.matches(d));
+ assertTrue(rule.matches(e));
+
+// /* 20 */ a > * { }
+ rule = rules[20];
+ assertFalse(rule.matches(a));
+ assertTrue(rule.matches(b));
+ assertFalse(rule.matches(c));
+ assertFalse(rule.matches(d));
+ assertFalse(rule.matches(e));
+
+// /* 21 */ a *[color] { }
+ rule = rules[21];
+ assertFalse(rule.matches(a));
+ assertTrue(rule.matches(b));
+ assertTrue(rule.matches(c));
+ assertTrue(rule.matches(d));
+ assertTrue(rule.matches(e));
+
+// /* 22 */ a > *[color] { }
+ rule = rules[22];
+ assertFalse(rule.matches(a));
+ assertTrue(rule.matches(b));
+ assertFalse(rule.matches(c));
+ assertFalse(rule.matches(d));
+ assertFalse(rule.matches(e));
+
+// /* 23 */ *[color~=blue] { }
+ rule = rules[23];
+ assertFalse(rule.matches(a));
+ assertTrue(rule.matches(b));
+ assertTrue(rule.matches(c));
+ assertTrue(rule.matches(d));
+ assertTrue(rule.matches(e));
+ assertFalse(rule.matches(f));
+
+ //
+ // Rules that test class conditions
+ //
+
+ b.setAttribute("class", "foo");
+ c.setAttribute("class", "foo bar");
+ d.setAttribute("class", "bar");
+
+// /* 24 */ .foo { }
+ rule = rules[24];
+ assertFalse(rule.matches(a));
+ assertTrue(rule.matches(b));
+ assertTrue(rule.matches(c));
+ assertFalse(rule.matches(d));
+
+// /* 25 */ .foo.bar { }
+ rule = rules[25];
+ assertFalse(rule.matches(a));
+ assertFalse(rule.matches(b));
+ assertTrue(rule.matches(c));
+ assertFalse(rule.matches(d));
+
+ }
+}
diff --git a/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/src/org/eclipse/wst/xml/vex/core/internal/css/SerializationTest.java b/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/src/org/eclipse/wst/xml/vex/core/internal/css/SerializationTest.java
new file mode 100644
index 0000000..95d2165
--- /dev/null
+++ b/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/src/org/eclipse/wst/xml/vex/core/internal/css/SerializationTest.java
@@ -0,0 +1,46 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.css;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.net.URL;
+
+import org.eclipse.wst.xml.vex.core.internal.css.StyleSheet;
+import org.eclipse.wst.xml.vex.core.internal.css.StyleSheetReader;
+
+import junit.framework.TestCase;
+
+public class SerializationTest extends TestCase {
+
+ public void testSerialization() throws Exception {
+// serialize("test1.css");
+// serialize("test2.css");
+ serialize("testLexicalUnits.css");
+// serialize("testRules.css");
+ }
+
+ private void serialize(String resource) throws Exception {
+
+ URL url = this.getClass().getResource(resource);
+ StyleSheetReader reader = new StyleSheetReader();
+ StyleSheet ss = reader.read(url);
+
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ ObjectOutputStream oos = new ObjectOutputStream(baos);
+ oos.writeObject(ss);
+ ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
+ ObjectInputStream ois = new ObjectInputStream(bais);
+ ois.readObject();
+ }
+}
diff --git a/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/src/org/eclipse/wst/xml/vex/core/internal/css/TestDisplayDevice.java b/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/src/org/eclipse/wst/xml/vex/core/internal/css/TestDisplayDevice.java
new file mode 100644
index 0000000..3c31faf
--- /dev/null
+++ b/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/src/org/eclipse/wst/xml/vex/core/internal/css/TestDisplayDevice.java
@@ -0,0 +1,37 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.css;
+
+import org.eclipse.wst.xml.vex.core.internal.core.DisplayDevice;
+
+public class TestDisplayDevice extends DisplayDevice {
+
+ private int horizontalPPI;
+ private int verticalPPI;
+
+ public TestDisplayDevice(int horizontalPPI, int verticalPPI) {
+ this.horizontalPPI = horizontalPPI;
+ this.verticalPPI = verticalPPI;
+ }
+
+ public int getHorizontalPPI() {
+ return this.horizontalPPI;
+ }
+
+ /**
+ *
+ */
+
+ public int getVerticalPPI() {
+ return this.verticalPPI;
+ }
+
+}
diff --git a/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/src/org/eclipse/wst/xml/vex/core/internal/css/TestLU.java b/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/src/org/eclipse/wst/xml/vex/core/internal/css/TestLU.java
new file mode 100644
index 0000000..958ed2a
--- /dev/null
+++ b/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/src/org/eclipse/wst/xml/vex/core/internal/css/TestLU.java
@@ -0,0 +1,115 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.css;
+
+import org.w3c.css.sac.LexicalUnit;
+
+/**
+ * Dummy LexicalUnit implementation.
+ */
+public class TestLU implements LexicalUnit {
+
+ public TestLU(short type) {
+ this.lexicalUnitType = type;
+ }
+
+ public static LexicalUnit INHERIT = new TestLU(LexicalUnit.SAC_INHERIT);
+
+ public static LexicalUnit createFloat(short units, float value) {
+ TestLU lu = new TestLU(units);
+ lu.setFloatValue(value);
+ return lu;
+ }
+
+ public static LexicalUnit createIdent(String s) {
+ TestLU lu = new TestLU(LexicalUnit.SAC_IDENT);
+ lu.setStringValue(s);
+ return lu;
+ }
+
+ public static LexicalUnit createString(String s) {
+ TestLU lu = new TestLU(LexicalUnit.SAC_STRING_VALUE);
+ lu.setStringValue(s);
+ return lu;
+ }
+
+ public String getDimensionUnitText() {
+ return this.dimensionUnitText;
+ }
+ public float getFloatValue() {
+ return this.floatValue;
+ }
+ public String getFunctionName() {
+ return this.functionName;
+ }
+ public int getIntegerValue() {
+ return this.integerValue;
+ }
+ public short getLexicalUnitType() {
+ return this.lexicalUnitType;
+ }
+ public LexicalUnit getNextLexicalUnit() {
+ return this.nextLexicalUnit;
+ }
+ public LexicalUnit getParameters() {
+ return this.parameters;
+ }
+ public LexicalUnit getPreviousLexicalUnit() {
+ return this.previousLexicalUnit;
+ }
+ public String getStringValue() {
+ return this.stringValue;
+ }
+ public LexicalUnit getSubValues() {
+ return this.subValues;
+ }
+ public void setDimensionUnitText(String dimensionUnitText) {
+ this.dimensionUnitText = dimensionUnitText;
+ }
+ public void setFloatValue(float floatValue) {
+ this.floatValue = floatValue;
+ }
+ public void setFunctionName(String functionName) {
+ this.functionName = functionName;
+ }
+ public void setIntegerValue(int integerValue) {
+ this.integerValue = integerValue;
+ }
+ public void setLexicalUnitType(short lexicalUnitType) {
+ this.lexicalUnitType = lexicalUnitType;
+ }
+ public void setNextLexicalUnit(LexicalUnit nextLexicalUnit) {
+ this.nextLexicalUnit = nextLexicalUnit;
+ }
+ public void setParameters(LexicalUnit parameters) {
+ this.parameters = parameters;
+ }
+ public void setPreviousLexicalUnit(LexicalUnit previousLexicalUnit) {
+ this.previousLexicalUnit = previousLexicalUnit;
+ }
+ public void setStringValue(String stringValue) {
+ this.stringValue = stringValue;
+ }
+ public void setSubValues(LexicalUnit subValues) {
+ this.subValues = subValues;
+ }
+ private short lexicalUnitType;
+ private LexicalUnit nextLexicalUnit;
+ private LexicalUnit previousLexicalUnit;
+ private int integerValue;
+ private float floatValue;
+ private String dimensionUnitText;
+ private String functionName;
+ private LexicalUnit parameters;
+ private String stringValue;
+ private LexicalUnit subValues;
+
+}
diff --git a/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/src/org/eclipse/wst/xml/vex/core/internal/css/expansion.css b/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/src/org/eclipse/wst/xml/vex/core/internal/css/expansion.css
new file mode 100644
index 0000000..d731168
--- /dev/null
+++ b/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/src/org/eclipse/wst/xml/vex/core/internal/css/expansion.css
@@ -0,0 +1,46 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * John Krasnay - initial API and implementation
+ *******************************************************************************/
+A {
+ margin: 1in;
+}
+
+B {
+ margin: 1in;
+ margin-bottom: 0.5in;
+ margin-left: 0.5in;
+ margin-right: 0.5in;
+ margin-top: 0.5in;
+}
+
+C {
+ margin-bottom: 0.5in;
+ margin-left: 0.5in;
+ margin-right: 0.5in;
+ margin-top: 0.5in;
+ margin: 1in;
+}
+
+D {
+ margin: 0.5in;
+ margin: 0.25in;
+}
+
+E {
+ margin: 1in 2in;
+}
+
+F {
+ margin: 1in 2in 3in;
+}
+
+G {
+ margin: 1in 2in 3in 4in;
+}
diff --git a/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/src/org/eclipse/wst/xml/vex/core/internal/css/test1.css b/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/src/org/eclipse/wst/xml/vex/core/internal/css/test1.css
new file mode 100644
index 0000000..0903f4c
--- /dev/null
+++ b/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/src/org/eclipse/wst/xml/vex/core/internal/css/test1.css
@@ -0,0 +1,28 @@
+/* Stylesheet for unit testing */
+
+A {
+ name: A;
+}
+
+B {
+ name: B;
+}
+
+/* should take precedence over above, since more elements */
+B, D {
+ name: BorD;
+}
+
+C {
+ name: C;
+}
+
+/* should take precedence over C rule above, since more elements */
+A C {
+ name: AdescendC;
+}
+
+/* Confirm *not* selected. */
+A > C {
+ name: AchildC;
+}
diff --git a/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/src/org/eclipse/wst/xml/vex/core/internal/css/test1.xml b/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/src/org/eclipse/wst/xml/vex/core/internal/css/test1.xml
new file mode 100644
index 0000000..0dbff01
--- /dev/null
+++ b/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/src/org/eclipse/wst/xml/vex/core/internal/css/test1.xml
@@ -0,0 +1,5 @@
+<A>
+ <B>
+ <C/>
+ </B>
+</A>
\ No newline at end of file
diff --git a/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/src/org/eclipse/wst/xml/vex/core/internal/css/test2.css b/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/src/org/eclipse/wst/xml/vex/core/internal/css/test2.css
new file mode 100644
index 0000000..3fb0334
--- /dev/null
+++ b/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/src/org/eclipse/wst/xml/vex/core/internal/css/test2.css
@@ -0,0 +1,277 @@
+/* defaults */
+defaults {
+}
+
+/* extra tokens not covered by "simple" */
+extras {
+ background-color: lime; /* 0, 255, 0 */
+
+ border-bottom-color: maroon; /* 128, 0, 0 */
+ border-bottom-style: solid;
+
+ border-left-color: navy; /* 0, 0, 128 */
+ border-left-style: dashed;
+
+ border-right-color: olive; /* 128, 128, 0 */
+ border-right-style: dotted;
+
+ border-top-color: purple; /* 128, 0, 128 */
+ border-top-style: double;
+
+ color: red; /* 255, 0, 0 */
+ display: inline;
+}
+
+extras2 {
+ background-color: silver; /* 192, 192, 192 */
+
+ border-bottom-color: teal; /* 0, 128, 128 */
+ border-bottom-style: none;
+ border-bottom-width: thin; /* 0, due to style: none */
+
+ border-left-color: white; /* 255, 255, 255 */
+ border-left-style: groove;
+
+ border-right-color: yellow; /* 255, 255, 0 */
+ border-right-style: ridge;
+
+ border-top-style: inset;
+}
+
+xxsmall {
+ font-size: xx-small;
+}
+
+xsmall {
+ font-size: x-small;
+}
+
+small {
+ font-size: small;
+}
+
+medium {
+ font-size: medium;
+}
+
+large {
+ font-size: large;
+}
+
+xlarge {
+ font-size: x-large;
+}
+xxlarge {
+ font-size: xx-large;
+}
+
+smaller {
+ font-size: smaller;
+}
+
+larger {
+ font-size: larger;
+}
+
+font100pct {
+ font-size: 100%;
+}
+
+font80pct {
+ font-size: 80%;
+}
+
+font120pct {
+ font-size: 120%;
+}
+
+inherit {
+ background-color: inherit;
+
+ border-bottom-color: inherit;
+ border-bottom-style: inherit;
+ border-bottom-width: inherit;
+
+ border-left-color: inherit;
+ border-left-style: inherit;
+ border-left-width: inherit;
+
+ border-right-color: inherit;
+ border-right-style: inherit;
+ border-right-width: inherit;
+
+ border-top-color: inherit;
+ border-top-style: inherit;
+ border-top-width: inherit;
+
+ color: inherit;
+ display: inherit;
+
+ margin-bottom: inherit;
+ margin-left: inherit;
+ margin-right: inherit;
+ margin-top: inherit;
+
+ padding-bottom: inherit;
+ padding-left: inherit;
+ padding-right: inherit;
+ padding-top: inherit;
+
+ font-family: inherit;
+ font-size: inherit;
+ font-style: inherit;
+ font-weight: inherit;
+}
+
+
+/* normal, non-expanded values */
+simple {
+ background-color: aqua; /* 0, 255, 255 */
+
+ border-bottom-color: black; /* 0, 0, 0 */
+ border-bottom-style: solid;
+ border-bottom-width: thin; /* 1px */
+
+ border-left-color: blue;
+ border-left-style: dashed;
+ border-left-width: medium; /* 3px */
+
+ border-right-color: fuchsia; /* 255, 0, 255 */
+ border-right-style: dotted;
+ border-right-width: thick; /* 5px */
+
+ border-top-color: gray;
+ border-top-style: double;
+ border-top-width: 1px;
+
+ color: green; /* 0, 128, 0 */
+ display: block;
+
+ margin-bottom: 100cm; /* 3543px */
+ margin-left: 0; /* 0 */
+ margin-right: 10em; /* 120px */
+ margin-top: 10ex; /* 72px */
+
+ padding-bottom: 5in; /* 450px */
+ padding-left: 1200mm; /* 4251px */
+ padding-right: 8pc; /* 120px */
+ padding-top: 15pt; /* 18px */
+
+ font-family: helvetica;
+ font-size: 10pt; /* 12.5px */
+ font-style: italic;
+ font-weight: bold;
+}
+
+expandBorder {
+ border: 2px solid red;
+}
+
+expandBorderTop {
+ border-top: 2px solid red;
+}
+
+expandBorderLeft {
+ border-left: 2px solid red;
+}
+
+expandBorderBottom {
+ border-bottom: 2px solid red;
+}
+
+expandBorderRight {
+ border-right: 2px solid red;
+}
+
+expandBorder1 {
+ border: 2px solid;
+}
+
+expandBorder2 {
+ border: 2px red;
+}
+
+expandBorder3 {
+ border: 2px;
+}
+
+expandBorder4 {
+ border: solid red;
+}
+
+expandBorder5 {
+ border: solid;
+}
+
+expandBorder6 {
+ border: red;
+}
+
+borderColor1 {
+ border-color: red;
+}
+
+borderColor2 {
+ border-color: red green;
+}
+
+borderColor3 {
+ border-color: red green blue;
+}
+
+borderColor4 {
+ border-color: red green blue white;
+}
+
+borderStyle1 {
+ border-style: solid;
+}
+
+borderStyle2 {
+ border-style: solid dotted;
+}
+
+borderStyle3 {
+ border-style: solid dotted dashed;
+}
+
+borderStyle4 {
+ border-style: solid dotted dashed outset;
+}
+
+borderWidth1 {
+ border-style: solid;
+ border-width: 1px;
+}
+
+borderWidth2 {
+ border-style: solid;
+ border-width: 1px 2px;
+}
+
+borderWidth3 {
+ border-style: solid;
+ border-width: 1px 2px 3px;
+}
+
+borderWidth4 {
+ border-style: solid;
+ border-width: 1px 2px 3px 4px;
+}
+
+margin1 {
+ margin: 10px;
+}
+
+margin2 {
+ margin: 10px 20px;
+}
+
+margin3 {
+ margin: 10px 20px 30px;
+}
+
+margin4 {
+ margin: 10px 20px 30px 40px;
+}
+
diff --git a/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/src/org/eclipse/wst/xml/vex/core/internal/css/testImportant.css b/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/src/org/eclipse/wst/xml/vex/core/internal/css/testImportant.css
new file mode 100644
index 0000000..1618d16
--- /dev/null
+++ b/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/src/org/eclipse/wst/xml/vex/core/internal/css/testImportant.css
@@ -0,0 +1,13 @@
+
+a {
+ background-color: black;
+ color: white;
+ border-top-color: blue !important;
+}
+
+* {
+ background-color: white;
+ color: black !important;
+ border-top-color: red !important;
+}
+
diff --git a/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/src/org/eclipse/wst/xml/vex/core/internal/css/testLexicalUnits.css b/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/src/org/eclipse/wst/xml/vex/core/internal/css/testLexicalUnits.css
new file mode 100644
index 0000000..bf927e1
--- /dev/null
+++ b/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/src/org/eclipse/wst/xml/vex/core/internal/css/testLexicalUnits.css
@@ -0,0 +1,20 @@
+/* Stylesheet for the testLexicalUnits() test case */
+
+A {
+ background-color: #000;
+ border-bottom: rgb(10%, 20%, 30%);
+ border-left: rgb(15, 25)
+ border-right: 10;
+ border-top: rgb(larry, curly, moe);
+ color: rgb(255, 255, 255);
+ font-name: "Times New Roman", serif;
+ font-size: 10px;
+ font-weight: 400;
+ font-style: italic;
+}
+
+B {
+ background-color: foo;
+ color: 10px;
+}
+
diff --git a/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/src/org/eclipse/wst/xml/vex/core/internal/css/testRules.css b/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/src/org/eclipse/wst/xml/vex/core/internal/css/testRules.css
new file mode 100644
index 0000000..a1c4ea4
--- /dev/null
+++ b/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/src/org/eclipse/wst/xml/vex/core/internal/css/testRules.css
@@ -0,0 +1,26 @@
+/* 0 */ c { }
+/* 1 */ b c { }
+/* 2 */ b d { }
+/* 3 */ other b c { }
+/* 4 */ other b d { }
+/* 5 */ a c e { }
+/* 6 */ c a e { }
+/* 7 */ * { }
+/* 8 */ *[color] { }
+/* 9 */ a[color] { }
+/* 10 */ b[color] { }
+/* 11 */ c[color] { }
+/* 12 */ d[color] { }
+/* 13 */ *[color=blue] { }
+/* 14 */ a[color=blue] { }
+/* 15 */ b[color=blue] { }
+/* 16 */ b[color='blue'] { }
+/* 17 */ b[color="blue"] { }
+/* 18 */ c[color=blue] { }
+/* 19 */ a * { }
+/* 20 */ a > * { }
+/* 21 */ a *[color] { }
+/* 22 */ a > *[color] { }
+/* 23 */ *[color~=blue] { }
+/* 24 */ .foo { }
+/* 25 */ .foo.bar { }
diff --git a/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/src/org/eclipse/wst/xml/vex/core/internal/dom/BlockElementBoxTest.java b/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/src/org/eclipse/wst/xml/vex/core/internal/dom/BlockElementBoxTest.java
new file mode 100644
index 0000000..8ae6e37
--- /dev/null
+++ b/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/src/org/eclipse/wst/xml/vex/core/internal/dom/BlockElementBoxTest.java
@@ -0,0 +1,73 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.dom;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+
+import org.eclipse.wst.xml.vex.core.internal.core.Graphics;
+import org.eclipse.wst.xml.vex.core.internal.css.StyleSheet;
+import org.eclipse.wst.xml.vex.core.internal.css.StyleSheetReader;
+import org.eclipse.wst.xml.vex.core.internal.dom.Document;
+import org.eclipse.wst.xml.vex.core.internal.dom.DocumentReader;
+import org.eclipse.wst.xml.vex.core.internal.layout.BlockElementBox;
+import org.eclipse.wst.xml.vex.core.internal.layout.Box;
+import org.eclipse.wst.xml.vex.core.internal.layout.CssBoxFactory;
+import org.eclipse.wst.xml.vex.core.internal.layout.FakeGraphics;
+import org.eclipse.wst.xml.vex.core.internal.layout.LayoutContext;
+
+import junit.framework.TestCase;
+
+public class BlockElementBoxTest extends TestCase {
+
+ private Graphics g;
+ private LayoutContext context;
+
+ public BlockElementBoxTest() throws Exception {
+
+ StyleSheetReader ssReader = new StyleSheetReader();
+ StyleSheet ss = ssReader.read(this.getClass().getResource("test.css"));
+
+ this.g = new FakeGraphics();
+
+ this.context = new LayoutContext();
+ this.context.setBoxFactory(new CssBoxFactory());
+ this.context.setGraphics(this.g);
+ this.context.setStyleSheet(ss);
+
+ }
+
+ public void testPositioning() throws Exception {
+
+ String docString = "<root><small/><medium/><large/></root>";
+ DocumentReader docReader = new DocumentReader();
+ docReader.setDebugging(true);
+ Document doc = docReader.read(docString);
+ BlockElementBox box = new BlockElementBox(context, null, doc.getRootElement());
+
+ Method createChildren = BlockElementBox.class.getDeclaredMethod("createChildren", new Class[] { LayoutContext.class });
+ createChildren.setAccessible(true);
+ createChildren.invoke(box, new Object[] { this.context });
+
+ Box[] children = box.getChildren();
+ assertEquals(3, children.length);
+ assertEquals(1 + 10, this.getGap(box, 0));
+ assertEquals(30 + 3 + 300 + 2 + 20, this.getGap(box, 1));
+ assertEquals(60 + 6 + 600 + 3 + 30, this.getGap(box, 2));
+ assertEquals(90 + 9, this.getGap(box, 3));
+ }
+
+ public int getGap(BlockElementBox box, int n) throws SecurityException, NoSuchMethodException, IllegalArgumentException, IllegalAccessException, InvocationTargetException {
+ Method getGap = BlockElementBox.class.getDeclaredMethod("getGap", new Class[] { Integer.TYPE });
+ getGap.setAccessible(true);
+ return ((Integer) getGap.invoke(box, new Object[] { new Integer(n) })).intValue();
+ }
+}
diff --git a/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/src/org/eclipse/wst/xml/vex/core/internal/dom/DFABuilderTest.java b/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/src/org/eclipse/wst/xml/vex/core/internal/dom/DFABuilderTest.java
new file mode 100644
index 0000000..5a758df
--- /dev/null
+++ b/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/src/org/eclipse/wst/xml/vex/core/internal/dom/DFABuilderTest.java
@@ -0,0 +1,276 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.dom;
+
+import org.eclipse.wst.xml.vex.core.internal.dom.*;
+
+import junit.framework.*;
+
+/**
+ * Test the <code>net.sf.vex.dom</code> package.
+ */
+public class DFABuilderTest extends TestCase {
+
+ public void testAll() {
+ DFAState dfa;
+
+ dfa = parseRegex("a");
+ assertTrue(matches(dfa, "a"));
+ assertFalse(matches(dfa, ""));
+ assertFalse(matches(dfa, "aa"));
+
+ dfa = parseRegex("a?");
+ assertTrue(matches(dfa, ""));
+ assertTrue(matches(dfa, "a"));
+ assertFalse(matches(dfa, "aa"));
+
+ dfa = parseRegex("a*");
+ assertTrue(matches(dfa, ""));
+ assertTrue(matches(dfa, "a"));
+ assertTrue(matches(dfa, "aa"));
+ assertTrue(matches(dfa, "aaa"));
+
+ dfa = parseRegex("a+");
+ assertFalse(matches(dfa, ""));
+ assertTrue(matches(dfa, "a"));
+ assertTrue(matches(dfa, "aa"));
+ assertTrue(matches(dfa, "aaa"));
+
+ dfa = parseRegex("ab");
+ assertFalse(matches(dfa, ""));
+ assertFalse(matches(dfa, "a"));
+ assertFalse(matches(dfa, "b"));
+ assertTrue(matches(dfa, "ab"));
+ assertFalse(matches(dfa, "aab"));
+ assertFalse(matches(dfa, "abb"));
+
+ dfa = parseRegex("a|b");
+ assertFalse(matches(dfa, ""));
+ assertTrue(matches(dfa, "a"));
+ assertTrue(matches(dfa, "b"));
+ assertFalse(matches(dfa, "ab"));
+ assertFalse(matches(dfa, "ba"));
+
+ dfa = parseRegex("a?b");
+ assertFalse(matches(dfa, ""));
+ assertFalse(matches(dfa, "a"));
+ assertTrue(matches(dfa, "b"));
+ assertTrue(matches(dfa, "ab"));
+ assertFalse(matches(dfa, "aa"));
+ assertFalse(matches(dfa, "bb"));
+ assertFalse(matches(dfa, "aab"));
+
+ dfa = parseRegex("a*b");
+ assertFalse(matches(dfa, ""));
+ assertFalse(matches(dfa, "a"));
+ assertTrue(matches(dfa, "b"));
+ assertTrue(matches(dfa, "ab"));
+ assertFalse(matches(dfa, "aa"));
+ assertFalse(matches(dfa, "bb"));
+ assertTrue(matches(dfa, "aab"));
+ assertTrue(matches(dfa, "aaab"));
+ assertFalse(matches(dfa, "aabb"));
+
+ dfa = parseRegex("a+b");
+ assertFalse(matches(dfa, ""));
+ assertFalse(matches(dfa, "a"));
+ assertFalse(matches(dfa, "b"));
+ assertTrue(matches(dfa, "ab"));
+ assertFalse(matches(dfa, "aa"));
+ assertFalse(matches(dfa, "bb"));
+ assertTrue(matches(dfa, "aab"));
+ assertTrue(matches(dfa, "aaab"));
+ assertFalse(matches(dfa, "aabb"));
+
+ dfa = parseRegex("(ts?)?p*");
+ assertTrue(matches(dfa, ""));
+ assertTrue(matches(dfa, "t"));
+ assertTrue(matches(dfa, "ts"));
+ assertTrue(matches(dfa, "tsppp"));
+ assertTrue(matches(dfa, "ppp"));
+ assertFalse(matches(dfa, "s"));
+ assertFalse(matches(dfa, "sp"));
+ }
+
+ /**
+ * Returns true if the given DFA matches the given string.
+ */
+ private boolean matches(DFAState state, String s) {
+ for (int i = 0; i < s.length(); i++) {
+ String symbol = String.valueOf(s.charAt(i));
+ state = state.getNextState(symbol);
+ if (state == null) {
+ return false;
+ }
+ }
+ return state.isAccepting();
+ }
+
+ /**
+ * Parses simple regex's into nodes.
+ */
+ private DFAState parseRegex(String s) {
+ ParseState ps = new ParseState();
+ ps.s = s;
+ ps.pos = 0;
+ return DFABuilder.createDFA(parseRegex(ps));
+ }
+
+ private static class ParseState {
+ public String s;
+ public int pos;
+ public char getChar() {
+ if (pos == s.length()) {
+ return 0;
+ } else {
+ return s.charAt(pos++);
+ }
+ }
+ public char peekChar() {
+ if (pos == s.length()) {
+ return 0;
+ } else {
+ return s.charAt(pos);
+ }
+ }
+ }
+
+ //
+ // regex ::= null
+ // | part regex*
+ // | ')'
+ //
+ // part ::= repeating [ '|' part ]
+ //
+ // repeating ::= simplepart [ '?' | '*' | '+' ]
+ //
+ // simplepart ::= char
+ // | '(' regex
+ //
+ private DFABuilder.Node parseRegex(ParseState ps) {
+
+ if (ps.peekChar() == ')') {
+ ps.getChar();
+ return null;
+ }
+
+ DFABuilder.Node node = parsePart(ps);
+ if (node == null) {
+ return node;
+ }
+
+ DFABuilder.Node node2 = parseRegex(ps);
+ if (node2 != null) {
+ node = DFABuilder.createSequenceNode(node, node2);
+ }
+
+ return node;
+ }
+
+ private DFABuilder.Node parsePart(ParseState ps) {
+ DFABuilder.Node node = parseRepeating(ps);
+ if (node == null) {
+ return node;
+ }
+
+ if (ps.peekChar() == '|') {
+ ps.getChar();
+ DFABuilder.Node node2 = parsePart(ps);
+ if (node2 != null) {
+ node = DFABuilder.createChoiceNode(node, node2);
+ }
+ }
+
+ return node;
+ }
+
+
+ private DFABuilder.Node parseRepeating(ParseState ps) {
+
+ DFABuilder.Node node = parseSimplePart(ps);
+ if (node == null) {
+ return node;
+ }
+
+ if (ps.peekChar() == '?') {
+ ps.getChar();
+ node = DFABuilder.createOptionalNode(node);
+ } else if (ps.peekChar() == '*') {
+ ps.getChar();
+ node = DFABuilder.createRepeatingNode(node, 0);
+ } else if (ps.peekChar() == '+') {
+ ps.getChar();
+ node = DFABuilder.createRepeatingNode(node, 1);
+ }
+
+ return node;
+ }
+
+ private DFABuilder.Node parseSimplePart(ParseState ps) {
+
+ DFABuilder.Node node;
+
+ char c = ps.getChar();
+
+ if (c == 0) {
+ return null;
+ } else if (c == '(') {
+ node = parseRegex(ps);
+ } else {
+ node = DFABuilder.createSymbolNode(String.valueOf(c));
+ }
+
+ return node;
+ }
+
+ /*
+ private DFABuilder.Node parseRegexToNode(ParseState ps) {
+ DFABuilder.Node node = null;
+ while (ps.pos < ps.s.length()) {
+ DFABuilder.Node nextNode;
+ char c = ps.s.charAt(ps.pos);
+ ps.pos++;
+ if (c == ')') {
+ return node;
+ } else if (c == '(') {
+ nextNode = parseRegexToNode(ps);
+ } else if (c == '?' || c == '*' || c == '+') {
+ throw new RuntimeException("Misplaced '?': " + ps.s);
+ } else {
+ nextNode = DFABuilder.createSymbolNode(String.valueOf(c));
+ }
+
+ if (ps.pos < ps.s.length()) {
+ char c2 = ps.s.charAt(ps.pos);
+ if (c2 == '?') {
+ nextNode = DFABuilder.createOptionalNode(nextNode);
+ ps.pos++;
+ } else if (c2 == '*') {
+ nextNode = DFABuilder.createRepeatingNode(nextNode, 0);
+ ps.pos++;
+ } else if (c2 == '+') {
+ nextNode = DFABuilder.createRepeatingNode(nextNode, 1);
+ ps.pos++;
+ }
+ }
+
+ if (node == null) {
+ node = nextNode;
+ } else {
+ node = DFABuilder.createSequenceNode(node, nextNode);
+ }
+ }
+
+ return node;
+ }
+ */
+}
+
diff --git a/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/src/org/eclipse/wst/xml/vex/core/internal/dom/DTDValidatorTest.java b/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/src/org/eclipse/wst/xml/vex/core/internal/dom/DTDValidatorTest.java
new file mode 100644
index 0000000..3b7fcc9
--- /dev/null
+++ b/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/src/org/eclipse/wst/xml/vex/core/internal/dom/DTDValidatorTest.java
@@ -0,0 +1,111 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.dom;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.net.URL;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+
+import org.eclipse.wst.xml.vex.core.internal.dom.AttributeDefinition;
+import org.eclipse.wst.xml.vex.core.internal.dom.DTDValidator;
+import org.eclipse.wst.xml.vex.core.internal.dom.Document;
+import org.eclipse.wst.xml.vex.core.internal.dom.Element;
+import org.eclipse.wst.xml.vex.core.internal.dom.RootElement;
+import org.eclipse.wst.xml.vex.core.internal.dom.Validator;
+
+import junit.framework.TestCase;
+
+public class DTDValidatorTest extends TestCase {
+
+ public void testAll() throws Exception {
+ URL url = DTDValidatorTest.class.getResource("test1.dtd");
+ Validator validator = DTDValidator.create(url);
+
+ AttributeDefinition.Type adType = validator.getAttributeDefinitions("section")[0].getType();
+
+ // Test serialization while we're at it
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ ObjectOutputStream oos = new ObjectOutputStream(baos);
+ oos.writeObject(validator);
+
+ ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
+ ObjectInputStream ois = new ObjectInputStream(bais);
+ validator = (Validator) ois.readObject();
+
+ AttributeDefinition.Type adType2 = validator.getAttributeDefinitions("section")[0].getType();
+
+ assertSame(adType, adType2);
+
+ Document doc;
+ Set expected;
+
+ doc = new Document(new RootElement("empty"));
+ doc.setValidator(validator);
+ assertEquals(Collections.EMPTY_SET, getValidItemsAt(doc, 1));
+
+ doc = new Document(new RootElement("any"));
+ doc.setValidator(validator);
+ Set anySet = new HashSet();
+ anySet.add(Validator.PCDATA);
+ anySet.add("any");
+ anySet.add("empty");
+ anySet.add("section");
+ anySet.add("title");
+ anySet.add("para");
+ anySet.add("emphasis");
+ assertEquals(anySet, getValidItemsAt(doc, 1));
+
+ // <section> <title> a b </title> <para> </para> </section>
+ // 1 2 3 4 5 6 7
+ doc = new Document(new RootElement("section"));
+ doc.setValidator(validator);
+ doc.insertElement(1, new Element("title"));
+ doc.insertText(2, "ab");
+ doc.insertElement(5, new Element("para"));
+
+ assertEquals(Collections.EMPTY_SET, getValidItemsAt(doc, 1));
+ expected = Collections.singleton(Validator.PCDATA);
+ assertEquals(expected, getValidItemsAt(doc, 2));
+ assertEquals(expected, getValidItemsAt(doc, 3));
+ assertEquals(expected, getValidItemsAt(doc, 4));
+ expected = Collections.singleton("para");
+ assertEquals(expected, getValidItemsAt(doc, 5));
+ assertEquals(expected, getValidItemsAt(doc, 7));
+ expected = new HashSet();
+ expected.add(Validator.PCDATA);
+ expected.add("emphasis");
+ assertEquals(expected, getValidItemsAt(doc, 6));
+
+ }
+
+ private Set getValidItemsAt(Document doc, int offset) {
+ Element element = doc.getElementAt(offset);
+ String[] prefix = doc.getNodeNames(element.getStartOffset() + 1, offset);
+ String[] suffix = doc.getNodeNames(offset, element.getEndOffset());
+ return doc.getValidator().getValidItems(element.getName(), prefix, suffix);
+ }
+ /*
+ private void dump(Validator validator, Document doc, int offset) {
+ Set set = getValidItemsAt(doc, offset);
+
+ Iterator iter = set.iterator();
+ while (iter.hasNext()) {
+ System.out.println(" " + iter.next());
+ }
+ }
+ */
+}
+
diff --git a/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/src/org/eclipse/wst/xml/vex/core/internal/dom/DocumentWriterTest.java b/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/src/org/eclipse/wst/xml/vex/core/internal/dom/DocumentWriterTest.java
new file mode 100644
index 0000000..aa2b180
--- /dev/null
+++ b/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/src/org/eclipse/wst/xml/vex/core/internal/dom/DocumentWriterTest.java
@@ -0,0 +1,189 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.dom;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+import java.util.Arrays;
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.parsers.SAXParserFactory;
+import junit.framework.TestCase;
+
+import org.eclipse.wst.xml.vex.core.internal.css.StyleSheet;
+import org.eclipse.wst.xml.vex.core.internal.css.StyleSheetReader;
+import org.eclipse.wst.xml.vex.core.internal.dom.Document;
+import org.eclipse.wst.xml.vex.core.internal.dom.DocumentWriter;
+import org.eclipse.wst.xml.vex.core.internal.dom.Element;
+import org.eclipse.wst.xml.vex.core.internal.dom.IWhitespacePolicy;
+import org.eclipse.wst.xml.vex.core.internal.dom.IWhitespacePolicyFactory;
+import org.eclipse.wst.xml.vex.core.internal.dom.Node;
+import org.eclipse.wst.xml.vex.core.internal.widget.CssWhitespacePolicy;
+import org.xml.sax.InputSource;
+import org.xml.sax.SAXException;
+import org.xml.sax.XMLReader;
+import org.xml.sax.helpers.DefaultHandler;
+
+/**
+ * Test the DocumentWriterImpl class.
+ */
+public class DocumentWriterTest extends TestCase {
+
+ public void testWriteDocument() throws Exception {
+
+ StyleSheetReader reader = new StyleSheetReader();
+ StyleSheet ss = reader.read(this.getClass().getResource("test.css"));
+
+ URL docUrl = this.getClass().getResource("DocumentWriterTest1.xml");
+
+ Document docOrig = readDocument(new InputSource(docUrl.toString()), ss);
+
+ DocumentWriter dw = new DocumentWriter();
+ dw.setWhitespacePolicy(new CssWhitespacePolicy(ss));
+ ByteArrayOutputStream os = new ByteArrayOutputStream();
+ dw.write(docOrig, os);
+
+ InputStream is = new ByteArrayInputStream(os.toByteArray());
+
+ // Dump document to console
+// BufferedReader br = new BufferedReader(new InputStreamReader(is));
+// while (true) {
+// String s = br.readLine();
+// if (s == null)
+// break;
+// System.out.println(s);
+// }
+// is.reset();
+
+ Document docNew = readDocument(new InputSource(is), ss);
+
+ assertEquals(docOrig, docNew);
+ }
+
+ private void assertEquals(Document expected, Document actual)
+ throws Exception {
+
+ assertEquals(expected.getRootElement(), actual.getRootElement());
+ }
+
+ private void assertEquals(Element expected, Element actual)
+ throws Exception {
+
+ System.out.println("Checking " + actual.getName());
+ assertEquals(expected.getName(), actual.getName());
+
+ String[] expectedAttrs = expected.getAttributeNames();
+ Arrays.sort(expectedAttrs);
+
+ String[] actualAttrs = actual.getAttributeNames();
+ Arrays.sort(actualAttrs);
+
+ assertEquals(expectedAttrs.length, actualAttrs.length);
+ for (int i = 0; i < expectedAttrs.length; i++) {
+ assertEquals(expectedAttrs[i], actualAttrs[i]);
+ }
+
+ Node[] expectedContent = expected.getChildNodes();
+ Node[] actualContent = actual.getChildNodes();
+ assertEquals(expectedContent.length, actualContent.length);
+ for (int i = 0; i < expectedContent.length; i++) {
+ assertEquals(
+ expectedContent[i].getClass(),
+ actualContent[i].getClass());
+ if (expectedContent[i] instanceof Element) {
+ assertEquals(
+ (Element) expectedContent[i],
+ (Element) actualContent[i]);
+ } else {
+ assertEquals(
+ expectedContent[i].getText(),
+ actualContent[i].getText());
+ }
+ }
+ }
+
+ private static Document readDocument(InputSource is, StyleSheet ss)
+ throws ParserConfigurationException, SAXException, IOException {
+
+ SAXParserFactory factory = SAXParserFactory.newInstance();
+ XMLReader xmlReader = factory.newSAXParser().getXMLReader();
+ DefaultHandler defaultHandler = new DefaultHandler();
+
+ final IWhitespacePolicy policy = new CssWhitespacePolicy(ss);
+
+ IWhitespacePolicyFactory wsFactory = new IWhitespacePolicyFactory() {
+ public IWhitespacePolicy getPolicy(String publicId) {
+ return policy;
+ }
+ };
+
+ org.eclipse.wst.xml.vex.core.internal.dom.DocumentBuilder builder =
+ new org.eclipse.wst.xml.vex.core.internal.dom.DocumentBuilder(wsFactory);
+
+ xmlReader.setContentHandler(builder);
+ xmlReader.setDTDHandler(defaultHandler);
+ xmlReader.setEntityResolver(defaultHandler);
+ xmlReader.setErrorHandler(defaultHandler);
+ xmlReader.parse(is);
+ return builder.getDocument();
+ }
+
+ /**
+ * Which elements are block elements for the purposes of our test cases.
+ */
+ /*
+ private static boolean isBlockElement(Element element) {
+ return element.getName().equals("html")
+ || element.getName().equals("body")
+ || element.getName().equals("p");
+ }
+*/
+ /**
+ * Parse the given document and pass them through to stdout to confirm
+ * their goodness.
+ */
+ public static void main(String[] args) {
+ if (args.length < 2) {
+ System.out.println("Usage: java DocumentWriterTest filename width");
+ System.exit(1);
+ }
+
+ FileInputStream fis = null;
+ try {
+ fis = new FileInputStream(args[0]);
+ int width = Integer.parseInt(args[1]);
+ StyleSheetReader reader = new StyleSheetReader();
+ StyleSheet ss =
+ reader.read(DocumentWriterTest.class.getResource("test.css"));
+ Document doc = readDocument(new InputSource(fis), ss);
+
+ DocumentWriter writer = new DocumentWriter();
+ writer.setWhitespacePolicy(new CssWhitespacePolicy(ss));
+ writer.setWrapColumn(width);
+
+ writer.write(doc, System.out);
+ } catch (Exception ex) {
+ ex.printStackTrace();
+ System.exit(1);
+ } finally {
+ if (fis != null) {
+ try {
+ fis.close();
+ } catch (IOException ex) {
+ }
+ fis = null;
+ }
+ }
+ }
+}
diff --git a/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/src/org/eclipse/wst/xml/vex/core/internal/dom/DocumentWriterTest1.xml b/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/src/org/eclipse/wst/xml/vex/core/internal/dom/DocumentWriterTest1.xml
new file mode 100644
index 0000000..4b1428b
--- /dev/null
+++ b/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/src/org/eclipse/wst/xml/vex/core/internal/dom/DocumentWriterTest1.xml
@@ -0,0 +1,11 @@
+<html>
+ <body style="font: 10pt 'Times New Roman', serif;" foo="bar none" xml:lang="en">
+
+ <p>This should be a complex document with some <a href="here is a
+ URL">elements with attributes</a>. The more complex, the more
+ thorough the check will be.</p>
+
+ <img src="http://myhost.com/really/long/path/to/a/jpeg.jpg" alt="<pickin & grinnin>"/>
+
+ </body>
+</html>
\ No newline at end of file
diff --git a/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/src/org/eclipse/wst/xml/vex/core/internal/dom/DomTest.java b/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/src/org/eclipse/wst/xml/vex/core/internal/dom/DomTest.java
new file mode 100644
index 0000000..b11dfee
--- /dev/null
+++ b/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/src/org/eclipse/wst/xml/vex/core/internal/dom/DomTest.java
@@ -0,0 +1,349 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.dom;
+
+import org.eclipse.wst.xml.vex.core.internal.dom.Document;
+import org.eclipse.wst.xml.vex.core.internal.dom.DocumentFragment;
+import org.eclipse.wst.xml.vex.core.internal.dom.Element;
+import org.eclipse.wst.xml.vex.core.internal.dom.Node;
+import org.eclipse.wst.xml.vex.core.internal.dom.RootElement;
+import org.eclipse.wst.xml.vex.core.internal.dom.Text;
+
+import junit.framework.TestCase;
+
+/**
+ * Test the <code>net.sf.vex.dom</code> package.
+ */
+public class DomTest extends TestCase {
+
+ public void testDom() throws Exception {
+
+ //
+ // Document initialisation
+ //
+ RootElement root = new RootElement("article");
+ Document doc = new Document(root);
+ Node[] content;
+ Element[] children;
+
+ // root
+ // | |
+ // * *
+
+ assertEquals(2, doc.getLength());
+ assertEquals(root, doc.getRootElement());
+ assertEquals(0, root.getStartOffset());
+ assertEquals(1, root.getEndOffset());
+
+ content = root.getChildNodes();
+ assertEquals(0, content.length);
+ children = root.getChildElements();
+ assertEquals(0, children.length);
+
+ // root
+ // | |
+ // * a c *
+
+ try {
+ doc.insertText(0, "ac");
+ fail("Expected IllegalArgumentException");
+ } catch (IllegalArgumentException ex) {
+ }
+
+
+ try {
+ doc.insertText(2, "ac");
+ fail("Expected IllegalArgumentException");
+ } catch (IllegalArgumentException ex) {
+ }
+
+ doc.insertText(1, "ac");
+ assertEquals(4, doc.getLength());
+ content = root.getChildNodes();
+ assertEquals(1, content.length);
+ assertIsText(content[0], "ac", 1, 3);
+ assertEquals(1, content[0].getStartPosition().getOffset());
+ assertEquals(3, content[0].getEndPosition().getOffset());
+ assertEquals(0, root.getStartOffset());
+ assertEquals(3, root.getEndOffset());
+
+ //
+ // Try inserting at illegal offset
+ //
+ Element element = new Element("b");
+
+ try {
+ doc.insertElement(0, element);
+ fail("Expected IllegalArgumentException");
+ } catch (IllegalArgumentException ex) {
+ }
+
+
+ try {
+ doc.insertElement(4, element);
+ fail("Expected IllegalArgumentException");
+ } catch (IllegalArgumentException ex) {
+ }
+
+ // root
+ // | |
+ // | z |
+ // | | | |
+ // * a * * c *
+ // 0 1 2 3 4 5 6
+ //
+ doc.insertElement(2, element);
+ assertEquals(root, element.getParent());
+ assertEquals(6, doc.getLength());
+
+ Element element2 = new Element("x");
+ doc.insertElement(2, element2);
+
+ content = root.getChildNodes();
+ assertEquals(4, content.length);
+ assertIsText(content[0], "a", 1, 2);
+ assertIsElement(content[1], "x", root, 2, 3);
+ assertIsElement(content[2], "b", root, 4, 5);
+ assertIsText(content[3], "c", 6, 7);
+
+ }
+
+ public void testFragments() throws Exception {
+
+ Document doc;
+ DocumentFragment frag;
+ Element[] elements;
+ Node[] nodes;
+ Element root;
+ Element x;
+ Element y;
+ Element z;
+
+ // Case 1: just text
+ //
+ // root
+ // * a b c *
+ // 0 1 2 3 4 5
+ doc = new Document(new RootElement("root"));
+ doc.insertText(1, "abc");
+
+ try {
+ frag = doc.getFragment(2, 2);
+ fail();
+ } catch (IllegalArgumentException ex) {
+ }
+
+ try {
+ frag = doc.getFragment(-1, 0);
+ fail();
+ } catch (IllegalArgumentException ex) {
+ }
+
+ try {
+ frag = doc.getFragment(4, 5);
+ fail();
+ } catch (IllegalArgumentException ex) {
+ }
+
+ frag = doc.getFragment(2, 3);
+ assertEquals(1, frag.getContent().getLength());
+ assertEquals(0, frag.getElements().length);
+ nodes = frag.getNodes();
+ assertEquals(1, nodes.length);
+ this.assertIsText(nodes[0], "b", 0, 1);
+
+ // Case 2: single element, no children
+ //
+ // root
+ // | |
+ // | z |
+ // | | | |
+ // * a * b * c *
+ // 0 1 2 3 4 5 6 7
+
+ // z
+ // | |
+ // a * b * c
+ // 0 1 2 3 4 5
+
+ doc = new Document(new RootElement("root"));
+ doc.insertText(1, "ac");
+ doc.insertElement(2, new Element("z"));
+ doc.insertText(3, "b");
+
+ frag = doc.getFragment(1, 6);
+ elements = frag.getElements();
+ assertEquals(1, elements.length);
+ this.assertIsElement(elements[0], "z", null, 1, 3);
+ nodes = frag.getNodes();
+ assertEquals(3, nodes.length);
+ assertIsText(nodes[0], "a", 0, 1);
+ assertIsElement(nodes[1], "z", null, 1, 3);
+ assertIsText(nodes[2], "c", 4, 5);
+ nodes = elements[0].getChildNodes();
+ assertEquals(1, nodes.length);
+ assertIsText(nodes[0], "b", 2, 3);
+
+ // Case 3: complex with child elements
+ //
+ // root
+ // | |
+ // | z |
+ // | | | |
+ // | | | x | | y | | |
+ // * a * b c * d * e * f * g h * i *
+ // 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7
+ // 3a: |<------frag----->|
+ // 3b: |<--------frag----------->|
+
+
+
+ doc = new Document(new RootElement("root"));
+ doc.insertText(1, "ai");
+ doc.insertElement(2, new Element("z"));
+ doc.insertText(3, "bcgh");
+ doc.insertElement(5, new Element("x"));
+ doc.insertText(6, "d");
+ doc.insertText(8, "e");
+ doc.insertElement(9, new Element("y"));
+ doc.insertText(10, "f");
+
+ // 3a:
+ // | x | | y |
+ // c * d * e * f * g
+ // 0 1 2 3 4 5 6 7 8 9
+ frag = doc.getFragment(4, 13);
+ assertEquals(9, frag.getContent().getLength());
+
+ elements = frag.getElements();
+ assertEquals(2, elements.length);
+ assertIsElement(elements[0], "x", null, 1, 3);
+ assertIsElement(elements[1], "y", null, 5, 7);
+
+ nodes = frag.getNodes();
+ assertEquals(5, nodes.length);
+ assertIsText(nodes[0], "c", 0, 1);
+ assertIsElement(nodes[1], "x", null, 1, 3);
+ assertIsText(nodes[2], "e", 4, 5);
+ assertIsElement(nodes[3], "y", null, 5, 7);
+ assertIsText(nodes[4], "g", 8, 9);
+
+ // 3b:
+ // z
+ // | |
+ // | | x | | y | |
+ // * b c * d * e * f * g h *
+ // 0 1 2 3 4 5 6 7 8 9 0 1 2 3
+ frag = doc.getFragment(2, 15);
+ assertEquals(13, frag.getContent().getLength());
+
+ elements = frag.getElements();
+ assertEquals(1, elements.length);
+ assertIsElement(elements[0], "z", null, 0, 12);
+
+ nodes = frag.getNodes();
+ assertEquals(1, nodes.length);
+ assertIsElement(nodes[0], "z", null, 0, 12);
+
+ z = elements[0];
+ nodes = z.getChildNodes();
+ assertEquals(5, nodes.length);
+ assertIsText(nodes[0], "bc", 1, 3);
+ assertIsElement(nodes[1], "x", z, 3, 5);
+ assertIsText(nodes[2], "e", 6, 7);
+ assertIsElement(nodes[3], "y", z, 7, 9);
+ assertIsText(nodes[4], "gh", 10, 12);
+
+ // 3c: remove and re-insert the same frag as in 3a
+ frag = doc.getFragment(4, 13);
+ doc.delete(4, 13);
+ doc.insertFragment(4, frag);
+
+ root = doc.getRootElement();
+ assertIsElement(root, "root", null, 0, 16);
+ nodes = root.getChildNodes();
+ assertEquals(3, nodes.length);
+ assertIsText(nodes[0], "a", 1, 2);
+ assertIsElement(nodes[1], "z", doc.getRootElement(), 2, 14);
+ assertIsText(nodes[2], "i", 15, 16);
+ z = (Element) nodes[1];
+ nodes = z.getChildNodes();
+ assertEquals(5, nodes.length);
+ assertIsText(nodes[0], "bc", 3, 5);
+ assertIsElement(nodes[1], "x", z, 5, 7);
+ assertIsText(nodes[2], "e", 8, 9);
+ assertIsElement(nodes[3], "y", z, 9, 11);
+ assertIsText(nodes[4], "gh", 12, 14);
+ x = (Element) nodes[1];
+ y = (Element) nodes[3];
+ nodes = x.getChildNodes();
+ assertEquals(1, nodes.length);
+ assertIsText(nodes[0], "d", 6, 7);
+ nodes = y.getChildNodes();
+ assertEquals(1, nodes.length);
+ assertIsText(nodes[0], "f", 10, 11);
+
+ // 3d: remove and re-insert the same frag as in 3b
+ frag = doc.getFragment(2, 15);
+ doc.delete(2, 15);
+ doc.insertFragment(2, frag);
+
+ root = doc.getRootElement();
+ assertIsElement(root, "root", null, 0, 16);
+ nodes = root.getChildNodes();
+ assertEquals(3, nodes.length);
+ assertIsText(nodes[0], "a", 1, 2);
+ assertIsElement(nodes[1], "z", doc.getRootElement(), 2, 14);
+ assertIsText(nodes[2], "i", 15, 16);
+ z = (Element) nodes[1];
+ nodes = z.getChildNodes();
+ assertEquals(5, nodes.length);
+ assertIsText(nodes[0], "bc", 3, 5);
+ assertIsElement(nodes[1], "x", z, 5, 7);
+ assertIsText(nodes[2], "e", 8, 9);
+ assertIsElement(nodes[3], "y", z, 9, 11);
+ assertIsText(nodes[4], "gh", 12, 14);
+ x = (Element) nodes[1];
+ y = (Element) nodes[3];
+ nodes = x.getChildNodes();
+ assertEquals(1, nodes.length);
+ assertIsText(nodes[0], "d", 6, 7);
+ nodes = y.getChildNodes();
+ assertEquals(1, nodes.length);
+ assertIsText(nodes[0], "f", 10, 11);
+
+ }
+
+ public void assertIsElement(Node node,
+ String name,
+ Element parent,
+ int startOffset,
+ int endOffset) {
+
+ assertTrue(node instanceof Element);
+ assertEquals(name, ((Element)node).getName());
+ assertEquals(parent, ((Element)node).getParent());
+ assertEquals(startOffset, node.getStartOffset());
+ assertEquals(endOffset, node.getEndOffset());
+ }
+
+ public void assertIsText(Node node,
+ String text,
+ int startOffset,
+ int endOffset) {
+
+ assertTrue(node instanceof Text);
+ assertEquals(text, node.getText());
+ assertEquals(startOffset, node.getStartOffset());
+ assertEquals(endOffset, node.getEndOffset());
+ }
+}
+
diff --git a/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/src/org/eclipse/wst/xml/vex/core/internal/dom/GapContentTest.java b/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/src/org/eclipse/wst/xml/vex/core/internal/dom/GapContentTest.java
new file mode 100644
index 0000000..6363436
--- /dev/null
+++ b/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/src/org/eclipse/wst/xml/vex/core/internal/dom/GapContentTest.java
@@ -0,0 +1,144 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.dom;
+
+import org.eclipse.wst.xml.vex.core.internal.dom.GapContent;
+import org.eclipse.wst.xml.vex.core.internal.dom.Position;
+
+import junit.framework.TestCase;
+
+/**
+ * Test the GapContent class
+ */
+public class GapContentTest extends TestCase {
+
+ public void testGapContent() throws Exception {
+ //
+ // a b (gap) c d
+ // | | | | |
+ // 0 1 2 3 4
+ //
+
+ GapContent content = new GapContent(2);
+ assertEquals(0, content.getLength());
+ content.insertString(0, "a");
+ assertEquals(1, content.getLength());
+ content.insertString(1, "d");
+ assertEquals(2, content.getLength());
+ content.insertString(1, "c");
+ assertEquals(3, content.getLength());
+ content.insertString(1, "b");
+ assertEquals(4, content.getLength());
+
+ Position pa = content.createPosition(0);
+ Position pb = content.createPosition(1);
+ Position pc = content.createPosition(2);
+ Position pd = content.createPosition(3);
+ Position pe = content.createPosition(4);
+
+ try {
+ content.getString(-1, 1);
+ fail("expected exception");
+ } catch (IllegalArgumentException ex) {
+ }
+
+ try {
+ content.getString(4, 1);
+ fail("expected exception");
+ } catch (IllegalArgumentException ex) {
+ }
+
+ try {
+ content.getString(0, -1);
+ fail("expected exception");
+ } catch (IllegalArgumentException ex) {
+ }
+
+ try {
+ content.getString(0, 5);
+ fail("expected exception");
+ } catch (IllegalArgumentException ex) {
+ }
+
+ try {
+ content.createPosition(-1);
+ fail("expected exception");
+ } catch (IllegalArgumentException ex) {
+ }
+
+ try {
+ content.createPosition(5);
+ fail("expected exception");
+ } catch (IllegalArgumentException ex) {
+ }
+
+ assertEquals("a", content.getString(0, 1));
+ assertEquals("b", content.getString(1, 1));
+ assertEquals("c", content.getString(2, 1));
+ assertEquals("d", content.getString(3, 1));
+
+ assertEquals("ab", content.getString(0, 2));
+ assertEquals("bc", content.getString(1, 2));
+ assertEquals("cd", content.getString(2, 2));
+
+ assertEquals("abc", content.getString(0, 3));
+ assertEquals("bcd", content.getString(1, 3));
+
+ assertEquals("abcd", content.getString(0, 4));
+
+ //
+ // a b x (gap) y c d
+ // | | | | | | |
+ // 0 1 2 3 4 5 6
+ //
+ content.insertString(2, "y");
+ assertEquals(5, content.getLength());
+ content.insertString(2, "x");
+ assertEquals(6, content.getLength());
+
+ assertEquals(0, pa.getOffset());
+ assertEquals(1, pb.getOffset());
+ assertEquals(4, pc.getOffset());
+ assertEquals(5, pd.getOffset());
+ assertEquals(6, pe.getOffset());
+
+ Position px = content.createPosition(2);
+ Position py = content.createPosition(3);
+
+ content.remove(2, 2);
+
+ assertEquals(4, content.getLength());
+
+ assertEquals(0, pa.getOffset());
+ assertEquals(1, pb.getOffset());
+ assertEquals(2, px.getOffset());
+ assertEquals(2, py.getOffset());
+ assertEquals(2, pc.getOffset());
+ assertEquals(3, pd.getOffset());
+ assertEquals(4, pe.getOffset());
+
+ assertEquals("a", content.getString(0, 1));
+ assertEquals("b", content.getString(1, 1));
+ assertEquals("c", content.getString(2, 1));
+ assertEquals("d", content.getString(3, 1));
+
+ assertEquals("ab", content.getString(0, 2));
+ assertEquals("bc", content.getString(1, 2));
+ assertEquals("cd", content.getString(2, 2));
+
+ assertEquals("abc", content.getString(0, 3));
+ assertEquals("bcd", content.getString(1, 3));
+
+ assertEquals("abcd", content.getString(0, 4));
+
+ }
+}
+
diff --git a/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/src/org/eclipse/wst/xml/vex/core/internal/dom/SpaceNormalizerTest.java b/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/src/org/eclipse/wst/xml/vex/core/internal/dom/SpaceNormalizerTest.java
new file mode 100644
index 0000000..5511862
--- /dev/null
+++ b/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/src/org/eclipse/wst/xml/vex/core/internal/dom/SpaceNormalizerTest.java
@@ -0,0 +1,188 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.dom;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.parsers.SAXParserFactory;
+import junit.framework.TestCase;
+
+import org.eclipse.wst.xml.vex.core.internal.css.StyleSheet;
+import org.eclipse.wst.xml.vex.core.internal.css.StyleSheetReader;
+import org.eclipse.wst.xml.vex.core.internal.dom.Document;
+import org.eclipse.wst.xml.vex.core.internal.dom.DocumentBuilder;
+import org.eclipse.wst.xml.vex.core.internal.dom.Element;
+import org.eclipse.wst.xml.vex.core.internal.dom.IWhitespacePolicy;
+import org.eclipse.wst.xml.vex.core.internal.dom.IWhitespacePolicyFactory;
+import org.eclipse.wst.xml.vex.core.internal.dom.Node;
+import org.eclipse.wst.xml.vex.core.internal.dom.Text;
+import org.eclipse.wst.xml.vex.core.internal.widget.CssWhitespacePolicy;
+import org.xml.sax.InputSource;
+import org.xml.sax.SAXException;
+import org.xml.sax.XMLReader;
+
+/**
+ * Test the SpaceNormalizer class.
+ */
+public class SpaceNormalizerTest extends TestCase {
+
+ /**
+ * Test the normalize method. Test cases are as follows.
+ *
+ * <ul>
+ * <li>leading w/s trimmed</li>
+ * <li>trailing w/s trimmed</li>
+ * <li>internal w/s collapsed to a single space</li>
+ * <li>internal w/s before and after an inline child element collapsed
+ * to a single space.</li>
+ * <li>internal w/s before and after a block child element removed.</li>
+ * <li>spaces between blocks eliminated.</li>
+ * <li>no extraneous spaces before or after elements added</li>
+ * </ul>
+ */
+ public void testNormalize() throws Exception {
+
+ String input = "<doc>\n\t " +
+ "<block>\n\t foo\n\t <inline>foo\n\t bar</inline>\n\t baz\n\t </block>\n\t " +
+ "<block>\n\t foo\n\t <block>bar</block>\n\t baz</block>" +
+ "<block>\n\t foo<inline> foo bar </inline>baz \n\t </block>" +
+ "<block>\n\t foo<block>bar</block>baz \n\t</block>" +
+ "\n\t </doc>";
+
+ StyleSheetReader reader = new StyleSheetReader();
+ StyleSheet ss = reader.read(this.getClass().getResource("test.css"));
+
+ Document doc = createDocument(input, ss);
+
+ //SpaceNormalizer norm = new SpaceNormalizer(ss);
+ //norm.normalize(doc);
+
+ Element element;
+
+ element = doc.getRootElement();
+ assertContent(element, new String[] { "<block>",
+ "<block>",
+ "<block>",
+ "<block>" });
+
+ Element[] children = element.getChildElements();
+
+ //--- Block 0 ---
+
+ assertContent(children[0], new String[] { "foo ",
+ "<inline>",
+ " baz" });
+ Element[] c2 = children[0].getChildElements();
+ assertContent(c2[0], new String[] { "foo bar" });
+
+ //--- Block 1 ---
+
+ assertContent(children[1], new String[] { "foo",
+ "<block>",
+ "baz" });
+ c2 = children[1].getChildElements();
+ assertContent(c2[0], new String[] { "bar" });
+
+ //--- Block 2 ---
+
+ assertContent(children[2], new String[] { "foo",
+ "<inline>",
+ "baz" });
+ c2 = children[2].getChildElements();
+ assertContent(c2[0], new String[] { "foo bar" });
+
+ //--- Block 3 ---
+
+ assertContent(children[3], new String[] { "foo",
+ "<block>",
+ "baz" });
+ c2 = children[3].getChildElements();
+ assertContent(c2[0], new String[] { "bar" });
+
+
+ //========= Now test with a PRE element =========
+
+ input = "<doc>\n\t " +
+ "<pre>\n\t foo\n\t <inline>\n\t foo\n\t bar\n \t</inline>\n\t baz\n\t </pre>\n\t " +
+ "\n\t </doc>";
+
+ doc = createDocument(input, ss);
+
+ element = doc.getRootElement();
+ assertContent(element, new String[] { "<pre>" });
+
+ Element pre = element.getChildElements()[0];
+ assertContent(pre, new String[] {
+ "\n\t foo\n\t ", "<inline>", "\n\t baz\n\t "
+ });
+
+ Element inline = pre.getChildElements()[0];
+ assertContent(inline, new String[] { "\n\t foo\n\t bar\n \t" });
+
+ }
+
+
+ //========================================================= PRIVATE
+
+// private static final String DTD = "<!ELEMENT doc ANY>";
+
+ /**
+ * Asserts the content of the given element matches the given
+ * list. If a string in content is enclosed in angle brackets,
+ * it's assume to refer to the name of an element; otherwise, it
+ * represents text content.
+ */
+ private void assertContent(Element element, String[] strings) {
+ Node[] content = element.getChildNodes();
+ assertEquals(strings.length, content.length);
+ for (int i = 0; i < strings.length; i++) {
+ if (strings[i].startsWith("<")) {
+ String name = strings[i].substring(1, strings[i].length() - 1);
+ assertTrue(content[i] instanceof Element);
+ assertEquals(name, ((Element)content[i]).getName());
+ } else {
+ assertTrue(content[i] instanceof Text);
+ assertEquals(strings[i], content[i].getText());
+ }
+ }
+ }
+
+ private Document createDocument(String s, StyleSheet ss)
+ throws ParserConfigurationException, SAXException, IOException {
+
+ SAXParserFactory factory = SAXParserFactory.newInstance();
+ XMLReader xmlReader = factory.newSAXParser().getXMLReader();
+ final StyleSheet mySS = ss;
+ DocumentBuilder builder = new DocumentBuilder(new IWhitespacePolicyFactory() {
+
+ public IWhitespacePolicy getPolicy(String publicId) {
+ return new CssWhitespacePolicy(mySS);
+ }
+
+ });
+
+ InputSource is = new InputSource(new ByteArrayInputStream(s.getBytes()));
+ xmlReader.setContentHandler(builder);
+ //xmlReader.setDTDHandler(defaultHandler);
+// xmlReader.setEntityResolver(new EntityResolver() {
+// public InputSource resolveEntity(String publicId, String systemId) throws SAXException, IOException {
+// System.out.println("resolveEntity called");
+// return new InputSource(new ByteArrayInputStream(DTD.getBytes()));
+// }
+// });
+ //xmlReader.setErrorHandler(defaultHandler);
+ xmlReader.parse(is);
+ return builder.getDocument();
+ }
+
+}
+
diff --git a/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/src/org/eclipse/wst/xml/vex/core/internal/dom/TextWrapperTest.java b/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/src/org/eclipse/wst/xml/vex/core/internal/dom/TextWrapperTest.java
new file mode 100644
index 0000000..0d9ae11
--- /dev/null
+++ b/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/src/org/eclipse/wst/xml/vex/core/internal/dom/TextWrapperTest.java
@@ -0,0 +1,109 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.dom;
+
+import org.eclipse.wst.xml.vex.core.internal.dom.TextWrapper;
+
+import junit.framework.TestCase;
+
+/**
+ * Test the TextWrapper class.
+ */
+public class TextWrapperTest extends TestCase {
+
+ public void testWrap() {
+ String[] results;
+ String[] inputs;
+ TextWrapper wrapper = new TextWrapper();
+
+ results = wrapper.wrap(40);
+ assertEquals(0, results.length);
+
+ inputs = new String[] {
+ "Here ",
+ "are ",
+ "some ",
+ "short ",
+ "words ",
+ "and here are some long ones. We make sure we have some short stuff and some long stuff, just to make sure it all wraps." };
+
+ for (int i = 0; i < inputs.length; i++) {
+ wrapper.add(inputs[i]);
+ }
+ results = wrapper.wrap(40);
+ assertWidth(results, 40);
+ assertPreserved(inputs, results);
+
+ wrapper.clear();
+ results = wrapper.wrap(40);
+ assertEquals(0, results.length);
+
+ String s1 = "yabba ";
+ String s3 = "yabba yabba yabba ";
+ wrapper.add(s1);
+ wrapper.addNoSplit(s3);
+ wrapper.addNoSplit(s3);
+ wrapper.add(s1);
+ results = wrapper.wrap(18);
+ assertEquals(4, results.length);
+ assertEquals(s1, results[0]);
+ assertEquals(s3, results[1]);
+ assertEquals(s3, results[2]);
+ assertEquals(s1, results[3]);
+ }
+
+ /**
+ * Ensure the two string arrays represent the same run of text
+ * after all elements are concatenated.
+ */
+ private void assertPreserved(String[] inputs, String[] results) {
+ StringBuffer inputSB = new StringBuffer();
+ StringBuffer resultSB = new StringBuffer();
+ for (int i = 0; i < inputs.length; i++) {
+ inputSB.append(inputs[i]);
+ }
+ for (int i = 0; i < results.length; i++) {
+ resultSB.append(results[i]);
+ }
+ assertEquals(inputSB.toString(), resultSB.toString());
+ }
+
+ /**
+ * Ensure all lines fit within the given width, and that adding an
+ * extra token from the next line would blow it.
+ */
+ private void assertWidth(String[] results, int width) {
+ for (int i = 0; i < results.length; i++) {
+ assertTrue(results[i].length() > 0);
+ assertTrue(results[i].length() <= width);
+ if (i < results.length-1) {
+ assertTrue(results[i].length()
+ + getToken(results[i+1]).length() > width);
+ }
+ }
+ }
+
+ /**
+ * Get a token from a string.
+ */
+ private String getToken(String s) {
+ int i = 0;
+ while (i < s.length() && !Character.isWhitespace(s.charAt(i))) {
+ i++;
+ }
+ while (i < s.length() && Character.isWhitespace(s.charAt(i))) {
+ i++;
+ }
+ return s.substring(0, i);
+ }
+
+}
+
diff --git a/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/src/org/eclipse/wst/xml/vex/core/internal/dom/test.css b/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/src/org/eclipse/wst/xml/vex/core/internal/dom/test.css
new file mode 100644
index 0000000..9a83031
--- /dev/null
+++ b/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/src/org/eclipse/wst/xml/vex/core/internal/dom/test.css
@@ -0,0 +1,53 @@
+
+html {
+ display: block;
+}
+
+body {
+ display: block;
+}
+
+p {
+ display: block;
+}
+
+block {
+ display: block;
+}
+
+pre {
+ display: block;
+ white-space: pre;
+}
+
+
+/* Styles for positioning tests */
+root {
+ border: 3px 7px 11px 13px;
+ padding: 17px 19px 23px 29px;
+}
+
+small {
+ border-width: 1px 2px 3px 4px;
+ border-style: solid;
+ margin: 100px 200px 300px 400px;
+ padding: 10px 20px 30px 40px;
+ display: block;
+}
+
+medium {
+ border-width: 2px 4px 6px 8px;
+ border-style: solid;
+ margin: 200px 400px 600px 800px;
+ padding: 20px 40px 60px 80px;
+ display: block;
+}
+
+large {
+ border-width: 3px 6px 9px 12px;
+ border-style: solid;
+ margin: 300px 600px 900px 1200px;
+ padding: 30px 60px 90px 120px;
+ display: block;
+}
+
diff --git a/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/src/org/eclipse/wst/xml/vex/core/internal/dom/test1.dtd b/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/src/org/eclipse/wst/xml/vex/core/internal/dom/test1.dtd
new file mode 100644
index 0000000..f246a88
--- /dev/null
+++ b/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/src/org/eclipse/wst/xml/vex/core/internal/dom/test1.dtd
@@ -0,0 +1,10 @@
+<!ELEMENT any ANY>
+<!ELEMENT empty EMPTY>
+<!ELEMENT section (title?, para+)>
+<!ELEMENT para (#PCDATA | emphasis)*>
+<!ELEMENT title (#PCDATA)>
+<!ELEMENT emphasis (#PCDATA)>
+
+<!-- a dummy attribute, just to make sure attribute def serialization is OK -->
+<!ATTLIST section
+ name CDATA #IMPLIED>
\ No newline at end of file
diff --git a/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/src/org/eclipse/wst/xml/vex/core/internal/layout/FakeGraphics.java b/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/src/org/eclipse/wst/xml/vex/core/internal/layout/FakeGraphics.java
new file mode 100644
index 0000000..a235c7a
--- /dev/null
+++ b/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/src/org/eclipse/wst/xml/vex/core/internal/layout/FakeGraphics.java
@@ -0,0 +1,166 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.layout;
+
+import org.eclipse.wst.xml.vex.core.internal.core.Color;
+import org.eclipse.wst.xml.vex.core.internal.core.ColorResource;
+import org.eclipse.wst.xml.vex.core.internal.core.DisplayDevice;
+import org.eclipse.wst.xml.vex.core.internal.core.FontMetrics;
+import org.eclipse.wst.xml.vex.core.internal.core.FontResource;
+import org.eclipse.wst.xml.vex.core.internal.core.FontSpec;
+import org.eclipse.wst.xml.vex.core.internal.core.Graphics;
+import org.eclipse.wst.xml.vex.core.internal.core.Rectangle;
+
+/**
+ * A pseudo-Graphics class that returns a known set of font metrics.
+ */
+public class FakeGraphics implements Graphics {
+
+ private int charWidth = 6;
+
+ public FakeGraphics() {
+ DisplayDevice.setCurrent(new DisplayDevice() {
+ public int getHorizontalPPI() {
+ return 72;
+ }
+ public int getVerticalPPI() {
+ return 72;
+ }
+ });
+ }
+
+ private FontMetrics fontMetrics = new FontMetrics() {
+ public int getAscent() {
+ return 10;
+ }
+ public int getDescent() {
+ return 3;
+ }
+ public int getHeight() {
+ return 13;
+ }
+ public int getLeading() {
+ return 2;
+ }
+ };
+
+ public int charsWidth(char[] data, int offset, int length) {
+ return length * charWidth;
+ }
+
+ public ColorResource createColor(Color rgb) {
+ return new ColorResource() {
+ public void dispose() {
+ }
+ };
+ }
+
+ public FontResource createFont(FontSpec fontSpec) {
+ return new FontResource() {
+ public void dispose() {
+ }
+ };
+ }
+
+ public void dispose() {
+ }
+
+ public void drawChars(char[] chars, int offset, int length, int x, int y) {
+ }
+
+ public void drawLine(int x1, int y1, int x2, int y2) {
+ }
+
+ public void drawString(String s, int x, int y) {
+ }
+
+ public void drawOval(int x, int y, int width, int height) {
+ }
+
+ public void drawRect(int x, int y, int width, int height) {
+ }
+
+ public void fillOval(int x, int y, int width, int height) {
+ }
+
+ public void fillRect(int x, int y, int width, int height) {
+ }
+
+ public Rectangle getClipBounds() {
+ return null;
+ }
+
+ public ColorResource getBackgroundColor() {
+ return null;
+ }
+
+ public ColorResource getColor() {
+ return null;
+ }
+
+ public FontResource getFont() {
+ return null;
+ }
+
+ public int getLineStyle() {
+ return 0;
+ }
+
+ public int getLineWidth() {
+ return 0;
+ }
+
+ public ColorResource getSystemColor(int id) {
+ return null;
+ }
+
+ public FontMetrics getFontMetrics() {
+ return this.fontMetrics;
+ }
+
+ public boolean isAntiAliased() {
+ return false;
+ }
+
+ public void setAntiAliased(boolean antiAliased) {
+ }
+
+ public ColorResource setBackgroundColor(ColorResource color) {
+ return null;
+ }
+
+ public ColorResource setColor(ColorResource color) {
+ return null;
+ }
+
+ public FontResource setFont(FontResource font) {
+ return null;
+ }
+
+ public void setLineStyle(int style) {
+ }
+
+ public void setLineWidth(int width) {
+ }
+
+ public int stringWidth(String s) {
+ return charWidth * s.length();
+ }
+
+ public int getCharWidth() {
+ return this.charWidth;
+ }
+
+ public void setXORMode(boolean xorMode) {
+ // TODO Auto-generated method stub
+
+ }
+}
diff --git a/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/src/org/eclipse/wst/xml/vex/core/internal/layout/LayoutTest.java b/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/src/org/eclipse/wst/xml/vex/core/internal/layout/LayoutTest.java
new file mode 100644
index 0000000..4daa71c
--- /dev/null
+++ b/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/src/org/eclipse/wst/xml/vex/core/internal/layout/LayoutTest.java
@@ -0,0 +1,259 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.layout;
+
+import java.io.IOException;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Stack;
+
+import javax.xml.parsers.FactoryConfigurationError;
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.parsers.SAXParserFactory;
+
+import junit.framework.Test;
+import junit.framework.TestCase;
+import junit.framework.TestSuite;
+
+import org.eclipse.wst.xml.vex.core.internal.css.StyleSheet;
+import org.eclipse.wst.xml.vex.core.internal.css.StyleSheetReader;
+import org.eclipse.wst.xml.vex.core.internal.dom.Document;
+import org.eclipse.wst.xml.vex.core.internal.dom.DocumentReader;
+import org.eclipse.wst.xml.vex.core.internal.dom.IWhitespacePolicy;
+import org.eclipse.wst.xml.vex.core.internal.dom.IWhitespacePolicyFactory;
+import org.eclipse.wst.xml.vex.core.internal.layout.Box;
+import org.eclipse.wst.xml.vex.core.internal.layout.LayoutContext;
+import org.eclipse.wst.xml.vex.core.internal.layout.RootBox;
+import org.eclipse.wst.xml.vex.core.internal.layout.TextBox;
+import org.eclipse.wst.xml.vex.core.internal.widget.CssWhitespacePolicy;
+import org.xml.sax.Attributes;
+import org.xml.sax.InputSource;
+import org.xml.sax.SAXException;
+import org.xml.sax.XMLReader;
+import org.xml.sax.helpers.DefaultHandler;
+
+/**
+ * Runs several suites of layout tests. Each suite is defined in an XML file.
+ * The XML files to run are registered in the suite() method.
+ */
+public class LayoutTest extends TestCase {
+
+ public String id;
+ public String doc;
+ public int layoutWidth = 100;
+ public BoxSpec result;
+ public String css;
+
+ public static Test suite() throws ParserConfigurationException, FactoryConfigurationError, IOException, SAXException {
+ TestSuite suite = new TestSuite(LayoutTest.class.getName());
+ suite.addTest(loadSuite("block-inline.xml"));
+ suite.addTest(loadSuite("before-after.xml"));
+ suite.addTest(loadSuite("linebreaks.xml"));
+ suite.addTest(loadSuite("tables.xml"));
+ return suite;
+ }
+
+ public static Test loadSuite(String filename) throws ParserConfigurationException, FactoryConfigurationError, IOException, SAXException {
+ XMLReader xmlReader = SAXParserFactory.newInstance().newSAXParser().getXMLReader();
+ TestCaseBuilder builder = new TestCaseBuilder();
+ xmlReader.setContentHandler(builder);
+ //xmlReader.setEntityResolver(builder);
+ URL url = LayoutTest.class.getResource(filename);
+ xmlReader.parse(new InputSource(url.toString()));
+
+ TestSuite suite = new TestSuite(filename);
+ for (Iterator it = builder.testCases.iterator(); it.hasNext();) {
+ LayoutTest test = (LayoutTest) it.next();
+ suite.addTest(test);
+ }
+ return suite;
+ }
+
+
+ public LayoutTest() {
+ super("testLayout");
+ }
+
+
+ public String getName() {
+ return this.id;
+ }
+
+
+ public void testLayout() throws Exception {
+ System.out.println();
+ System.out.println("-----------------------------------------------------------------------");
+ System.out.println("Test: " + this.id);
+ System.out.println("-----------------------------------------------------------------------");
+
+ URL url = LayoutTest.class.getResource(this.css);
+ StyleSheetReader reader = new StyleSheetReader();
+ final StyleSheet ss = reader.read(url);
+
+ FakeGraphics g = new FakeGraphics();
+
+ LayoutContext context = new LayoutContext();
+ context.setBoxFactory(new TestBoxFactory());
+ context.setGraphics(g);
+ context.setStyleSheet(ss);
+
+ DocumentReader docReader = new DocumentReader();
+ docReader.setWhitespacePolicyFactory(new IWhitespacePolicyFactory() {
+ public IWhitespacePolicy getPolicy(String publicId) {
+ return new CssWhitespacePolicy(ss);
+ }
+ });
+ Document doc = docReader.read(this.doc);
+ context.setDocument(doc);
+
+ RootBox rootBox = new RootBox(context, doc.getRootElement(), this.layoutWidth);
+ rootBox.layout(context, 0, Integer.MAX_VALUE);
+
+ assertBox(this.result, rootBox, "");
+ }
+
+
+ private static void assertBox(BoxSpec boxSpec, Box box, String indent) {
+
+ System.out.println(indent + boxSpec.className);
+
+ if (boxSpec.className != null) {
+ String actualClassName = box.getClass().getName();
+ if (boxSpec.className.lastIndexOf('.') == -1) {
+ // no dot in box spec classname, so strip the prefix from the actual classname
+ int lastDot = actualClassName.lastIndexOf('.');
+ actualClassName = actualClassName.substring(lastDot + 1);
+ }
+ assertEquals(boxSpec.className, actualClassName);
+ }
+
+ if (boxSpec.element != null) {
+ assertNotNull(box.getElement());
+ assertEquals(boxSpec.element, box.getElement().getName());
+ }
+
+ if (boxSpec.text != null && box instanceof TextBox) {
+ assertEquals(boxSpec.text, ((TextBox) box).getText());
+ }
+
+ if (boxSpec.children.size() > 0 && box.getChildren() == null) {
+ fail("Expected " + boxSpec.children.size() + " children, but " + boxSpec.className + "'s children is null");
+ }
+
+ if (boxSpec.children.size() != box.getChildren().length) {
+ System.out.println("Wrong number of child boxes");
+ System.out.println(" Expected:");
+ for (Iterator it = boxSpec.children.iterator(); it.hasNext();) {
+ BoxSpec childSpec = (BoxSpec) it.next();
+ System.out.print(" " + childSpec.className);
+ if (childSpec.text != null) {
+ System.out.print(" '" + childSpec.text + "'");
+ }
+ System.out.println();
+ }
+ System.out.println(" Actual:");
+ for (int i = 0; i < box.getChildren().length; i++) {
+ Box childBox = box.getChildren()[i];
+ System.out.println(" " + childBox.getClass() + ": " + childBox);
+ }
+ fail("Wrong number of child boxes.");
+ }
+
+ for (int i = 0; i < boxSpec.children.size(); i++) {
+ assertBox((BoxSpec) boxSpec.children.get(i), box.getChildren() [i], indent + " ");
+ }
+
+ }
+
+ private static class TestCaseBuilder extends DefaultHandler {
+
+ private List testCases;
+ private String css;
+ private LayoutTest testCase;
+ private BoxSpec boxSpec;
+ private Stack boxSpecs;
+ private boolean inDoc;
+
+ public void characters(char[] ch, int start, int length)
+ throws SAXException {
+
+ String s = new String(ch, start, length).trim();
+ if (s.length() > 0) {
+ if (inDoc) {
+ this.testCase.doc = new String(ch, start, length);
+ } else {
+ throw new IllegalStateException();
+ }
+ }
+ }
+ public void endElement(String uri, String localName, String qName)
+ throws SAXException {
+ if (qName.equals("box")) {
+ if (this.boxSpecs.isEmpty()) {
+ this.boxSpec = null;
+ } else {
+ this.boxSpec = (BoxSpec) this.boxSpecs.pop();
+ }
+ } else if (qName.equals("doc")) {
+ this.inDoc = false;
+ }
+ }
+ public void startElement(String uri, String localName, String qName,
+ Attributes attributes) throws SAXException {
+
+ if (qName.equals("testcases")) {
+ this.testCases = new ArrayList();
+ this.css = attributes.getValue("css");
+ if (this.css == null) {
+ this.css = "test.css";
+ }
+ this.testCase = null;
+ this.boxSpecs = new Stack();
+ } else if (qName.equals("test")) {
+ this.testCase = new LayoutTest();
+ this.testCase.id = attributes.getValue("id");
+ this.testCase.css = this.css;
+ String layoutWidth = attributes.getValue("layoutWidth");
+ if (layoutWidth != null) {
+ this.testCase.layoutWidth = Integer.parseInt(layoutWidth);
+ }
+ testCases.add(this.testCase);
+ } else if (qName.equals("doc")) {
+ this.inDoc = true;
+ } else if (qName.equals("result")) {
+ } else if (qName.equals("box")) {
+ BoxSpec parent = this.boxSpec;
+ this.boxSpec = new BoxSpec();
+ this.boxSpec.className = attributes.getValue("class");
+ this.boxSpec.element = attributes.getValue("element");
+ this.boxSpec.text = attributes.getValue("text");
+ if (parent == null) {
+ this.testCase.result = this.boxSpec;
+ } else {
+ this.boxSpecs.push(parent);
+ parent.children.add(this.boxSpec);
+ }
+ } else {
+ throw new SAXException("Unrecognized element: " + qName);
+ }
+ }
+ }
+
+ private static class BoxSpec {
+ public String className;
+ public String element;
+ public List children = new ArrayList();
+ public String text;
+ }
+
+}
diff --git a/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/src/org/eclipse/wst/xml/vex/core/internal/layout/TestBlockElementBox.java b/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/src/org/eclipse/wst/xml/vex/core/internal/layout/TestBlockElementBox.java
new file mode 100644
index 0000000..4e23c87
--- /dev/null
+++ b/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/src/org/eclipse/wst/xml/vex/core/internal/layout/TestBlockElementBox.java
@@ -0,0 +1,73 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.layout;
+
+import java.net.URL;
+
+import org.eclipse.wst.xml.vex.core.internal.css.StyleSheet;
+import org.eclipse.wst.xml.vex.core.internal.css.StyleSheetReader;
+import org.eclipse.wst.xml.vex.core.internal.dom.Document;
+import org.eclipse.wst.xml.vex.core.internal.dom.Element;
+import org.eclipse.wst.xml.vex.core.internal.dom.RootElement;
+import org.eclipse.wst.xml.vex.core.internal.layout.BlockElementBox;
+import org.eclipse.wst.xml.vex.core.internal.layout.BlockPseudoElementBox;
+import org.eclipse.wst.xml.vex.core.internal.layout.Box;
+import org.eclipse.wst.xml.vex.core.internal.layout.LayoutContext;
+import org.eclipse.wst.xml.vex.core.internal.layout.RootBox;
+
+import junit.framework.TestCase;
+
+
+public class TestBlockElementBox extends TestCase {
+
+ FakeGraphics g;
+ LayoutContext context;
+
+ public TestBlockElementBox() throws Exception {
+ URL url = this.getClass().getResource("test.css");
+ StyleSheetReader reader = new StyleSheetReader();
+ StyleSheet ss = reader.read(url);
+
+ this.g = new FakeGraphics();
+
+ this.context = new LayoutContext();
+ this.context.setBoxFactory(new TestBoxFactory());
+ this.context.setGraphics(this.g);
+ this.context.setStyleSheet(ss);
+ }
+
+ public void testBeforeAfter() throws Exception {
+ RootElement root = new RootElement("root");
+ Document doc = new Document(root);
+ doc.insertElement(1, new Element("beforeBlock"));
+
+ RootBox rootBox = new RootBox(this.context, root, 500);
+ rootBox.layout(this.context, 0, Integer.MAX_VALUE);
+
+ Box[] children;
+ BlockElementBox beb;
+
+ children = rootBox.getChildren();
+ assertEquals(1, children.length);
+ assertEquals(BlockElementBox.class, children[0].getClass());
+ beb = (BlockElementBox) children[0];
+ assertEquals(root, beb.getElement());
+
+ children = beb.getChildren();
+ assertEquals(2, children.length);
+ assertEquals(BlockPseudoElementBox.class, children[0].getClass());
+ assertEquals(BlockElementBox.class, children[1].getClass());
+ beb = (BlockElementBox) children[1];
+ assertEquals("beforeBlock", beb.getElement().getName());
+
+ }
+
+}
diff --git a/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/src/org/eclipse/wst/xml/vex/core/internal/layout/TestBlocksInInlines.java b/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/src/org/eclipse/wst/xml/vex/core/internal/layout/TestBlocksInInlines.java
new file mode 100644
index 0000000..3b004ff
--- /dev/null
+++ b/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/src/org/eclipse/wst/xml/vex/core/internal/layout/TestBlocksInInlines.java
@@ -0,0 +1,62 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.layout;
+
+import java.net.URL;
+
+import org.eclipse.wst.xml.vex.core.internal.css.StyleSheet;
+import org.eclipse.wst.xml.vex.core.internal.css.StyleSheetReader;
+import org.eclipse.wst.xml.vex.core.internal.dom.Document;
+import org.eclipse.wst.xml.vex.core.internal.dom.Element;
+import org.eclipse.wst.xml.vex.core.internal.dom.RootElement;
+import org.eclipse.wst.xml.vex.core.internal.layout.LayoutContext;
+import org.eclipse.wst.xml.vex.core.internal.layout.RootBox;
+
+import junit.framework.TestCase;
+
+/**
+ * Tests proper function of a block-level element within an inline element.
+ * These must be layed out as a block child of the containing block element.
+ */
+public class TestBlocksInInlines extends TestCase {
+
+ FakeGraphics g;
+ LayoutContext context;
+
+ public TestBlocksInInlines() throws Exception {
+ URL url = this.getClass().getResource("test.css");
+ StyleSheetReader reader = new StyleSheetReader();
+ StyleSheet ss = reader.read(url);
+
+ this.g = new FakeGraphics();
+
+ this.context = new LayoutContext();
+ this.context.setBoxFactory(new TestBoxFactory());
+ this.context.setGraphics(this.g);
+ this.context.setStyleSheet(ss);
+ }
+
+ public void testBlockInInline() throws Exception {
+ RootElement root = new RootElement("root");
+ Document doc = new Document(root);
+
+ doc.insertText(1, "one five");
+ doc.insertElement(5, new Element("b"));
+ doc.insertText(6, "two four");
+ doc.insertElement(10, new Element("p"));
+ doc.insertText(11, "three");
+
+ RootBox rootBox = new RootBox(this.context, root, 500);
+ rootBox.layout(this.context, 0, Integer.MAX_VALUE);
+
+
+ }
+}
diff --git a/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/src/org/eclipse/wst/xml/vex/core/internal/layout/TestBoxFactory.java b/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/src/org/eclipse/wst/xml/vex/core/internal/layout/TestBoxFactory.java
new file mode 100644
index 0000000..b6d12e5
--- /dev/null
+++ b/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/src/org/eclipse/wst/xml/vex/core/internal/layout/TestBoxFactory.java
@@ -0,0 +1,48 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.layout;
+
+import org.eclipse.wst.xml.vex.core.internal.dom.Element;
+import org.eclipse.wst.xml.vex.core.internal.layout.BlockElementBox;
+import org.eclipse.wst.xml.vex.core.internal.layout.Box;
+import org.eclipse.wst.xml.vex.core.internal.layout.CssBoxFactory;
+import org.eclipse.wst.xml.vex.core.internal.layout.LayoutContext;
+import org.eclipse.wst.xml.vex.core.internal.layout.SpaceBox;
+
+
+
+/**
+ * A box factory that, for an element named <space>, returns a SpaceBox
+ * with height and width given by attributes of those names, e.g.
+ * <space height="100" width="200"/>
+ */
+public class TestBoxFactory extends CssBoxFactory {
+
+ public Box createBox(LayoutContext context, Element element,
+ BlockElementBox parent, int width) {
+
+ if (element.getName().equals("space")) {
+ int w = 0;
+ int h = 0;
+ try {
+ w = Integer.parseInt(element.getAttribute("width"));
+ } catch (NumberFormatException ex) {
+ }
+ try {
+ h = Integer.parseInt(element.getAttribute("height"));
+ } catch (NumberFormatException ex) {
+ }
+ return new SpaceBox(w, h);
+ }
+ // TODO Auto-generated method stub
+ return super.createBox(context, element, parent, width);
+ }
+}
diff --git a/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/src/org/eclipse/wst/xml/vex/core/internal/layout/TestDocumentTextBox.java b/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/src/org/eclipse/wst/xml/vex/core/internal/layout/TestDocumentTextBox.java
new file mode 100644
index 0000000..afcfe03
--- /dev/null
+++ b/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/src/org/eclipse/wst/xml/vex/core/internal/layout/TestDocumentTextBox.java
@@ -0,0 +1,173 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.layout;
+
+import java.net.URL;
+
+import org.eclipse.wst.xml.vex.core.internal.css.StyleSheet;
+import org.eclipse.wst.xml.vex.core.internal.css.StyleSheetReader;
+import org.eclipse.wst.xml.vex.core.internal.css.Styles;
+import org.eclipse.wst.xml.vex.core.internal.dom.Document;
+import org.eclipse.wst.xml.vex.core.internal.dom.RootElement;
+import org.eclipse.wst.xml.vex.core.internal.layout.CssBoxFactory;
+import org.eclipse.wst.xml.vex.core.internal.layout.DocumentTextBox;
+import org.eclipse.wst.xml.vex.core.internal.layout.InlineBox;
+import org.eclipse.wst.xml.vex.core.internal.layout.LayoutContext;
+
+import junit.framework.TestCase;
+
+/**
+ * Tests the DocumentTestBox class. We focus here on proper offsets, since
+ * text splitting is tested thoroughly in TestStaticTextBox.
+ */
+public class TestDocumentTextBox extends TestCase {
+
+ FakeGraphics g;
+ LayoutContext context;
+
+ public TestDocumentTextBox() throws Exception {
+
+ URL url = this.getClass().getResource("test.css");
+ StyleSheetReader reader = new StyleSheetReader();
+ StyleSheet ss = reader.read(url);
+
+ this.g = new FakeGraphics();
+
+ this.context = new LayoutContext();
+ this.context.setBoxFactory(new CssBoxFactory());
+ this.context.setGraphics(this.g);
+ this.context.setStyleSheet(ss);
+ }
+
+ public void testSplit() throws Exception {
+ RootElement root = new RootElement("root");
+ Document doc = new Document(root);
+
+ Styles styles = this.context.getStyleSheet().getStyles(root);
+
+ int width = g.getCharWidth();
+
+ // 0 6 13 21
+ // / / / /
+ // baggy orange trousers
+
+ doc.insertText(1, "baggy orange trousers");
+ DocumentTextBox box = new DocumentTextBox(this.context, root, 1, 22);
+ assertEquals(box.getText().length() * width, box.getWidth());
+ assertEquals(styles.getLineHeight(), box.getHeight());
+ assertSplit(box, 22, false, "baggy orange trousers", null);
+ assertSplit(box, 21, false, "baggy orange trousers", null);
+ assertSplit(box, 20, false, "baggy orange ", "trousers");
+ assertSplit(box, 13, false, "baggy orange ", "trousers");
+ assertSplit(box, 12, false, "baggy ", "orange trousers");
+ assertSplit(box, 6, false, "baggy ", "orange trousers");
+ assertSplit(box, 5, false, null, "baggy orange trousers");
+ assertSplit(box, 1, false, null, "baggy orange trousers");
+ assertSplit(box, 0, false, null, "baggy orange trousers");
+ assertSplit(box, -1, false, null, "baggy orange trousers");
+
+ assertSplit(box, 22, true, "baggy orange trousers", null);
+ assertSplit(box, 21, true, "baggy orange trousers", null);
+ assertSplit(box, 20, true, "baggy orange ", "trousers");
+ assertSplit(box, 13, true, "baggy orange ", "trousers");
+ assertSplit(box, 12, true, "baggy ", "orange trousers");
+ assertSplit(box, 6, true, "baggy ", "orange trousers");
+ assertSplit(box, 5, true, "baggy", " orange trousers");
+ assertSplit(box, 4, true, "bagg", "y orange trousers");
+ assertSplit(box, 3, true, "bag", "gy orange trousers");
+ assertSplit(box, 2, true, "ba", "ggy orange trousers");
+ assertSplit(box, 1, true, "b", "aggy orange trousers");
+ assertSplit(box, 0, true, "b", "aggy orange trousers");
+ assertSplit(box, -1, true, "b", "aggy orange trousers");
+
+ doc.delete(1, 22);
+
+ // 0 5 10
+ // / / /
+ doc.insertText(1, "red green");
+ box = new DocumentTextBox(this.context, root, 1, 11);
+ assertSplit(box, 11, false, "red green", null);
+ assertSplit(box, 10, false, "red green", null);
+ assertSplit(box, 9, false, "red ", "green");
+ assertSplit(box, 5, false, "red ", "green");
+
+ //
+ // This is the way it should work from a formatting point-of-view, but
+ // it could be problematic when it gets to positioning the caret, e.g.
+ // if we had lots of spaces to the right of a word it would format
+ // properly, but the caret would get carried out of the formatted area.
+ //
+// assertSplit(box, 4, false, null, "red green");
+// assertSplit(box, 1, false, null, "red green");
+// assertSplit(box, 0, false, null, "red green");
+// assertSplit(box, -1, false, null, "red green");
+
+ //
+ // This solves the caret problem at the expense of the formatting
+ // problem. It also happens to be how my initial implementation works!
+ // In the end it doesn't much matter, since Vex should collapse
+ // contiguous space into a single space character.
+ //
+ assertSplit(box, 4, false, "red ", " green");
+ assertSplit(box, 3, false, null, "red green");
+ assertSplit(box, 1, false, null, "red green");
+ assertSplit(box, 0, false, null, "red green");
+ assertSplit(box, -1, false, null, "red green");
+
+ assertSplit(box, 4, true, "red ", " green");
+ assertSplit(box, 3, true, "red", " green");
+ assertSplit(box, 1, true, "r", "ed green");
+ assertSplit(box, 0, true, "r", "ed green");
+ assertSplit(box, -1, true, "r", "ed green");
+
+ }
+
+ private void assertSplit(DocumentTextBox box, int splitPos, boolean force, String left, String right) {
+
+ Styles styles = this.context.getStyleSheet().getStyles(box.getElement());
+
+ int width = g.getCharWidth();
+
+ InlineBox.Pair pair = box.split(context, splitPos * width, force);
+
+ DocumentTextBox leftBox = (DocumentTextBox) pair.getLeft();
+ DocumentTextBox rightBox = (DocumentTextBox) pair.getRight();
+
+ int leftOffset = 1;
+ int midOffset = leftOffset + (left == null ? 0 : left.length());
+ int rightOffset = leftOffset + box.getText().length();
+
+ if (left == null) {
+ assertNull(leftBox);
+ } else {
+ assertNotNull(leftBox);
+ assertEquals(left, leftBox.getText());
+ assertEquals(left.length() * width, leftBox.getWidth());
+ assertEquals(styles.getLineHeight(), leftBox.getHeight());
+ assertEquals(leftOffset, leftBox.getStartOffset());
+ assertEquals(midOffset - 1, leftBox.getEndOffset());
+ }
+
+ if (right == null) {
+ assertNull(rightBox);
+ } else {
+ assertNotNull(rightBox);
+ assertEquals(right, rightBox.getText());
+ assertEquals(right.length() * width, rightBox.getWidth());
+ assertEquals(styles.getLineHeight(), rightBox.getHeight());
+ assertEquals(midOffset, rightBox.getStartOffset());
+ assertEquals(rightOffset - 1, rightBox.getEndOffset());
+ }
+
+ }
+}
+
+
diff --git a/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/src/org/eclipse/wst/xml/vex/core/internal/layout/TestParagraphBox.java b/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/src/org/eclipse/wst/xml/vex/core/internal/layout/TestParagraphBox.java
new file mode 100644
index 0000000..b8468dd
--- /dev/null
+++ b/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/src/org/eclipse/wst/xml/vex/core/internal/layout/TestParagraphBox.java
@@ -0,0 +1,74 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.layout;
+
+import java.net.URL;
+
+import org.eclipse.wst.xml.vex.core.internal.css.StyleSheet;
+import org.eclipse.wst.xml.vex.core.internal.css.StyleSheetReader;
+import org.eclipse.wst.xml.vex.core.internal.layout.CssBoxFactory;
+import org.eclipse.wst.xml.vex.core.internal.layout.LayoutContext;
+
+import junit.framework.TestCase;
+
+public class TestParagraphBox extends TestCase {
+
+ FakeGraphics g;
+ LayoutContext context;
+
+ public TestParagraphBox() throws Exception {
+
+ URL url = this.getClass().getResource("test.css");
+ StyleSheetReader reader = new StyleSheetReader();
+ StyleSheet ss = reader.read(url);
+
+ this.g = new FakeGraphics();
+
+ this.context = new LayoutContext();
+ this.context.setBoxFactory(new CssBoxFactory());
+ this.context.setGraphics(this.g);
+ this.context.setStyleSheet(ss);
+ }
+
+ /*
+ public void testWordWrap() throws Exception {
+ RootElement root = new RootElement("root");
+ Document doc = new Document(root);
+
+ Styles styles = this.context.getStyleSheet().getStyles(root);
+
+ FontMetrics fm = this.g.getFontMetrics();
+
+ // Test Case 1: check the offsets
+ //
+ // UPPER CASE indicates static text
+ // lower case indicates document text
+ // [ ] represent element start and end
+ //
+ // BLACK WHITE GRAY
+ // RED [orange] YELLOW (line is 1:8, last=false)
+ // BLACK WHITE GRAY
+ // [blue] GREEN [pink] (line is 9:20 last=true)
+ // BLACK WHITE GRAY
+ //
+ // Document looks like this (# chars are element sentinels
+ // 2 8 16 20
+ // / / / /
+ // ##orange##blue##pink##
+ // \ \
+ // 10 14
+ //
+
+ }
+ */
+}
+
+
diff --git a/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/src/org/eclipse/wst/xml/vex/core/internal/layout/TestStaticTextBox.java b/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/src/org/eclipse/wst/xml/vex/core/internal/layout/TestStaticTextBox.java
new file mode 100644
index 0000000..59e4236
--- /dev/null
+++ b/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/src/org/eclipse/wst/xml/vex/core/internal/layout/TestStaticTextBox.java
@@ -0,0 +1,154 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.layout;
+
+import java.net.URL;
+
+import org.eclipse.wst.xml.vex.core.internal.css.StyleSheet;
+import org.eclipse.wst.xml.vex.core.internal.css.StyleSheetReader;
+import org.eclipse.wst.xml.vex.core.internal.css.Styles;
+import org.eclipse.wst.xml.vex.core.internal.dom.RootElement;
+import org.eclipse.wst.xml.vex.core.internal.layout.CssBoxFactory;
+import org.eclipse.wst.xml.vex.core.internal.layout.InlineBox;
+import org.eclipse.wst.xml.vex.core.internal.layout.LayoutContext;
+import org.eclipse.wst.xml.vex.core.internal.layout.StaticTextBox;
+
+import junit.framework.TestCase;
+
+public class TestStaticTextBox extends TestCase {
+
+ FakeGraphics g;
+ LayoutContext context;
+
+ public TestStaticTextBox() throws Exception {
+
+ URL url = this.getClass().getResource("test.css");
+ StyleSheetReader reader = new StyleSheetReader();
+ StyleSheet ss = reader.read(url);
+
+ this.g = new FakeGraphics();
+
+ this.context = new LayoutContext();
+ this.context.setBoxFactory(new CssBoxFactory());
+ this.context.setGraphics(this.g);
+ this.context.setStyleSheet(ss);
+ }
+
+ public void testSplit() throws Exception {
+ RootElement root = new RootElement("root");
+
+ Styles styles = this.context.getStyleSheet().getStyles(root);
+
+ int width = g.getCharWidth();
+
+ // 0 6 13 21
+ // / / / /
+ // baggy orange trousers
+
+ StaticTextBox box = new StaticTextBox(this.context, root, "baggy orange trousers");
+ assertEquals(box.getText().length() * width, box.getWidth());
+ assertEquals(styles.getLineHeight(), box.getHeight());
+ assertSplit(box, 22, false, "baggy orange trousers", null);
+ assertSplit(box, 21, false, "baggy orange trousers", null);
+ assertSplit(box, 20, false, "baggy orange ", "trousers");
+ assertSplit(box, 13, false, "baggy orange ", "trousers");
+ assertSplit(box, 12, false, "baggy ", "orange trousers");
+ assertSplit(box, 6, false, "baggy ", "orange trousers");
+ assertSplit(box, 5, false, null, "baggy orange trousers");
+ assertSplit(box, 1, false, null, "baggy orange trousers");
+ assertSplit(box, 0, false, null, "baggy orange trousers");
+ assertSplit(box, -1, false, null, "baggy orange trousers");
+
+ assertSplit(box, 22, true, "baggy orange trousers", null);
+ assertSplit(box, 21, true, "baggy orange trousers", null);
+ assertSplit(box, 20, true, "baggy orange ", "trousers");
+ assertSplit(box, 13, true, "baggy orange ", "trousers");
+ assertSplit(box, 12, true, "baggy ", "orange trousers");
+ assertSplit(box, 6, true, "baggy ", "orange trousers");
+ assertSplit(box, 5, true, "baggy", " orange trousers");
+ assertSplit(box, 4, true, "bagg", "y orange trousers");
+ assertSplit(box, 3, true, "bag", "gy orange trousers");
+ assertSplit(box, 2, true, "ba", "ggy orange trousers");
+ assertSplit(box, 1, true, "b", "aggy orange trousers");
+ assertSplit(box, 0, true, "b", "aggy orange trousers");
+ assertSplit(box, -1, true, "b", "aggy orange trousers");
+
+ // 0 5 10
+ // / / /
+ box = new StaticTextBox(this.context, root, "red green");
+ assertSplit(box, 11, false, "red green", null);
+ assertSplit(box, 10, false, "red green", null);
+ assertSplit(box, 9, false, "red ", "green");
+ assertSplit(box, 5, false, "red ", "green");
+
+ //
+ // This is the way it should work from a formatting point-of-view, but
+ // it could be problematic when it gets to positioning the caret, e.g.
+ // if we had lots of spaces to the right of a word it would format
+ // properly, but the caret would get carried out of the formatted area.
+ //
+// assertSplit(box, 4, false, null, "red green");
+// assertSplit(box, 1, false, null, "red green");
+// assertSplit(box, 0, false, null, "red green");
+// assertSplit(box, -1, false, null, "red green");
+
+ //
+ // This solves the caret problem at the expense of the formatting
+ // problem. It also happens to be how my initial implementation works!
+ // In the end it doesn't much matter, since Vex should collapse
+ // contiguous space into a single space character.
+ //
+ assertSplit(box, 4, false, "red ", " green");
+ assertSplit(box, 3, false, null, "red green");
+ assertSplit(box, 1, false, null, "red green");
+ assertSplit(box, 0, false, null, "red green");
+ assertSplit(box, -1, false, null, "red green");
+
+ assertSplit(box, 4, true, "red ", " green");
+ assertSplit(box, 3, true, "red", " green");
+ assertSplit(box, 1, true, "r", "ed green");
+ assertSplit(box, 0, true, "r", "ed green");
+ assertSplit(box, -1, true, "r", "ed green");
+
+ }
+
+ private void assertSplit(StaticTextBox box, int splitPos, boolean force, String left, String right) {
+
+ Styles styles = this.context.getStyleSheet().getStyles(box.getElement());
+ int width = g.getCharWidth();
+
+ InlineBox.Pair pair = box.split(context, splitPos * width, force);
+
+ StaticTextBox leftBox = (StaticTextBox) pair.getLeft();
+ StaticTextBox rightBox = (StaticTextBox) pair.getRight();
+
+ if (left == null) {
+ assertNull(leftBox);
+ } else {
+ assertNotNull(leftBox);
+ assertEquals(left, leftBox.getText());
+ assertEquals(left.length() * width, leftBox.getWidth());
+ assertEquals(styles.getLineHeight(), leftBox.getHeight());
+ }
+
+ if (right == null) {
+ assertNull(rightBox);
+ } else {
+ assertNotNull(rightBox);
+ assertEquals(right, rightBox.getText());
+ assertEquals(right.length() * width, rightBox.getWidth());
+ assertEquals(styles.getLineHeight(), rightBox.getHeight());
+ }
+
+ }
+}
+
+
diff --git a/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/src/org/eclipse/wst/xml/vex/core/internal/layout/before-after.css b/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/src/org/eclipse/wst/xml/vex/core/internal/layout/before-after.css
new file mode 100644
index 0000000..e4817c7
--- /dev/null
+++ b/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/src/org/eclipse/wst/xml/vex/core/internal/layout/before-after.css
@@ -0,0 +1,77 @@
+
+iia {
+ display: inline;
+}
+
+iia:after {
+ display: inline;
+ content: 'after';
+}
+
+iib {
+ display: inline;
+}
+
+iib:before {
+ display: inline;
+ content: 'before';
+}
+
+
+iba {
+ display: inline;
+}
+
+iba:after {
+ display: block;
+ content: 'AFTER';
+}
+
+ibb {
+ display: inline;
+}
+
+ibb:before {
+ display: block;
+ content: 'BEFORE';
+}
+
+
+
+bia {
+ display: block;
+}
+
+bia:after {
+ display: inline;
+ content: 'after';
+}
+
+bib {
+ display: block;
+}
+
+bib:before {
+ display: inline;
+ content: 'before';
+}
+
+
+bba {
+ display: block;
+}
+
+bba:after {
+ display: block;
+ content: 'AFTER';
+}
+
+bbb {
+ display: block;
+}
+
+bbb:before {
+ display: block;
+ content: 'BEFORE';
+}
+
diff --git a/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/src/org/eclipse/wst/xml/vex/core/internal/layout/before-after.xml b/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/src/org/eclipse/wst/xml/vex/core/internal/layout/before-after.xml
new file mode 100644
index 0000000..586ebb1
--- /dev/null
+++ b/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/src/org/eclipse/wst/xml/vex/core/internal/layout/before-after.xml
@@ -0,0 +1,145 @@
+<?xml version='1.0'?>
+<testcases css="before-after.css">
+
+ <test id="Block with Block Before" layoutWidth="100">
+ <doc><![CDATA[ <root><bbb>wuzzle</bbb></root> ]]></doc>
+ <result>
+ <box class="RootBox">
+ <box class="BlockElementBox" element="root">
+
+ <box class="BlockElementBox" element="bbb">
+
+ <box class="BlockPseudoElementBox">
+ <box class="ParagraphBox">
+ <box class="LineBox">
+ <box class="StaticTextBox" text="BEFORE" />
+ </box>
+ </box>
+ </box>
+
+ <box class="ParagraphBox">
+ <box class="LineBox">
+ <box class="DocumentTextBox" text="wuzzle" />
+ <box class="PlaceholderBox"/>
+ </box>
+ </box>
+
+ </box>
+ </box>
+ </box>
+ </result>
+ </test>
+
+
+
+ <test id="Block with Block After" layoutWidth="100">
+ <doc><![CDATA[ <root><bba>wuzzle</bba></root> ]]></doc>
+ <result>
+ <box class="RootBox">
+ <box class="BlockElementBox" element="root">
+
+ <box class="BlockElementBox" element="bba">
+
+ <box class="ParagraphBox">
+ <box class="LineBox">
+ <box class="DocumentTextBox" text="wuzzle" />
+ <box class="PlaceholderBox"/>
+ </box>
+ </box>
+
+ <box class="BlockPseudoElementBox">
+ <box class="ParagraphBox">
+ <box class="LineBox">
+ <box class="StaticTextBox" text="AFTER" />
+ </box>
+ </box>
+ </box>
+
+ </box>
+ </box>
+ </box>
+ </result>
+ </test>
+
+
+ <test id="Block with Inline Before" layoutWidth="100">
+ <doc><![CDATA[ <root><bib>wuzzle</bib></root> ]]></doc>
+ <result>
+ <box class="RootBox">
+ <box class="BlockElementBox" element="root">
+
+ <box class="BlockElementBox" element="bib">
+
+ <box class="ParagraphBox">
+ <box class="LineBox">
+ <box class="StaticTextBox" text="before" />
+ <box class="DocumentTextBox" text="wuzzle" />
+ <box class="PlaceholderBox"/>
+ </box>
+ </box>
+
+ </box>
+ </box>
+ </box>
+ </result>
+ </test>
+
+ <test id="Block with Inline After" layoutWidth="100">
+ <doc><![CDATA[ <root><bia>wuzzle</bia></root> ]]></doc>
+ <result>
+ <box class="RootBox">
+ <box class="BlockElementBox" element="root">
+
+ <box class="BlockElementBox" element="bia">
+
+ <box class="ParagraphBox">
+ <box class="LineBox">
+ <box class="DocumentTextBox" text="wuzzle" />
+ <box class="PlaceholderBox"/>
+ <box class="StaticTextBox" text="after" />
+ </box>
+ </box>
+
+ </box>
+ </box>
+ </box>
+ </result>
+ </test>
+
+
+<!--
+ <test id="Inline with Block Before" layoutWidth="100">
+ <doc><![CDATA[ <root><ibb>wuzzle</ibb></root> ]]></doc>
+ <result>
+ <box class="RootBox">
+ <box class="BlockElementBox" element="root">
+
+ <box class="BlockPseudoElementBox">
+ <box class="ParagraphBox">
+ <box class="LineBox">
+ <box class="StaticTextBox" text="BEFORE" />
+ </box>
+ </box>
+ </box>
+
+ <box class="ParagraphBox">
+ <box class="LineBox">
+ <box class="InlineElementBox" element="ibb">
+ <box class="DrawableBox" />
+ <box class="DocumentTextBox" text="wuzzle" />
+ <box class="PlaceholderBox"/>
+ <box class="DrawbleBox" />
+ </box>
+ <box class="PlaceholderBox"/>
+ </box>
+ </box>
+
+ </box>
+ </box>
+ </result>
+ </test>
+-->
+
+
+
+</testcases>
diff --git a/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/src/org/eclipse/wst/xml/vex/core/internal/layout/block-inline.css b/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/src/org/eclipse/wst/xml/vex/core/internal/layout/block-inline.css
new file mode 100644
index 0000000..ba32596
--- /dev/null
+++ b/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/src/org/eclipse/wst/xml/vex/core/internal/layout/block-inline.css
@@ -0,0 +1,15 @@
+
+root {
+ display: block;
+ font: 10pt monospaced;
+}
+
+
+b {
+ display: inline;
+}
+
+p {
+ display: block;
+}
+
diff --git a/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/src/org/eclipse/wst/xml/vex/core/internal/layout/block-inline.xml b/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/src/org/eclipse/wst/xml/vex/core/internal/layout/block-inline.xml
new file mode 100644
index 0000000..c9a4701
--- /dev/null
+++ b/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/src/org/eclipse/wst/xml/vex/core/internal/layout/block-inline.xml
@@ -0,0 +1,343 @@
+<?xml version='1.0'?>
+<testcases css="block-inline.css">
+
+ <test id="Empty Root" layoutWidth="100">
+ <doc><![CDATA[ <root/> ]]></doc>
+ <result>
+ <box class="RootBox">
+ <box class="BlockElementBox" element="root">
+ <box class="ParagraphBox">
+ <box class="LineBox">
+ <box class="PlaceholderBox"/>
+ </box>
+ </box>
+ </box>
+ </box>
+ </result>
+ </test>
+
+ <test id="Simple Text" layoutWidth="100">
+ <doc><![CDATA[ <root>Hello, world!</root> ]]></doc>
+ <result>
+ <box class="RootBox">
+ <box class="BlockElementBox" element="root">
+ <box class="ParagraphBox">
+ <box class="LineBox">
+ <box class="DocumentTextBox" text="Hello, "/>
+ <box class="DocumentTextBox" text="world!"/>
+ <box class="PlaceholderBox"/>
+ </box>
+ </box>
+ </box>
+ </box>
+ </result>
+ </test>
+
+
+
+ <!--
+ A simple wrap. 42 = length("Hello, ") * 6
+ -->
+ <test id="Simple Wrap" layoutWidth="42">
+ <doc><![CDATA[ <root>Hello, world!</root> ]]></doc>
+ <result>
+ <box class="RootBox">
+ <box class="BlockElementBox" element="root">
+ <box class="ParagraphBox">
+ <box class="LineBox">
+ <box class="DocumentTextBox" text="Hello, " />
+ </box>
+ <box class="LineBox">
+ <box class="DocumentTextBox" text="world!" />
+ <box class="PlaceholderBox"/>
+ </box>
+ </box>
+ </box>
+ </box>
+ </result>
+ </test>
+
+
+ <!--
+ Attempts to split the text inside the space after "Hello,".
+ The space should remain with "Hello," and not be split on to its
+ own line
+ -->
+ <test id="Split at First Space" layoutWidth="40">
+ <doc><![CDATA[ <root>Hello, world!</root> ]]></doc>
+ <result>
+ <box class="RootBox">
+ <box class="BlockElementBox" element="root">
+ <box class="ParagraphBox">
+ <box class="LineBox">
+ <box class="DocumentTextBox" text="Hello, " />
+ </box>
+ <box class="LineBox">
+ <box class="DocumentTextBox" text="world!" />
+ <box class="PlaceholderBox"/>
+ </box>
+ </box>
+ </box>
+ </box>
+ </result>
+ </test>
+
+
+ <test id="Empty Inline" layoutWidth="100">
+ <doc><![CDATA[ <root><b></b></root> ]]></doc>
+ <result>
+ <box class="RootBox">
+ <box class="BlockElementBox" element="root">
+ <box class="ParagraphBox">
+ <box class="LineBox">
+ <box class="PlaceholderBox"/>
+ <box class="InlineElementBox">
+ <box class="DrawableBox"/>
+ <box class="PlaceholderBox"/>
+ <box class="DrawableBox"/>
+ </box>
+ <box class="PlaceholderBox"/>
+ </box>
+ </box>
+ </box>
+ </box>
+ </result>
+ </test>
+
+
+ <test id="Simple Inline" layoutWidth="100">
+ <doc><![CDATA[ <root><b>cat sat</b></root> ]]></doc>
+ <result>
+ <box class="RootBox">
+ <box class="BlockElementBox" element="root">
+ <box class="ParagraphBox">
+ <box class="LineBox">
+ <box class="PlaceholderBox"/>
+ <box class="InlineElementBox">
+ <box class="DrawableBox"/>
+ <box class="DocumentTextBox" text="cat " />
+ <box class="DocumentTextBox" text="sat" />
+ <box class="PlaceholderBox"/>
+ <box class="DrawableBox"/>
+ </box>
+ <box class="PlaceholderBox"/>
+ </box>
+ </box>
+ </box>
+ </box>
+ </result>
+ </test>
+
+
+ <test id="Simple Inline Split 1" layoutWidth="36">
+ <doc><![CDATA[ <root><b>cat sat</b></root> ]]></doc>
+ <result>
+ <box class="RootBox">
+ <box class="BlockElementBox" element="root">
+ <box class="ParagraphBox">
+ <box class="LineBox">
+ <box class="PlaceholderBox"/>
+ <box class="InlineElementBox">
+ <box class="DrawableBox"/>
+ <box class="DocumentTextBox" text="cat " />
+ </box>
+ </box>
+ <box class="LineBox">
+ <box class="InlineElementBox">
+ <box class="DocumentTextBox" text="sat" />
+ <box class="PlaceholderBox"/>
+ <box class="DrawableBox"/>
+ </box>
+ <box class="PlaceholderBox"/>
+ </box>
+ </box>
+ </box>
+ </box>
+ </result>
+ </test>
+
+
+ <test id="Inline With Surrounding Text" layoutWidth="100">
+ <doc><![CDATA[ <root>hat <b>cat sat</b> bat</root> ]]></doc>
+ <result>
+ <box class="RootBox">
+ <box class="BlockElementBox" element="root">
+ <box class="ParagraphBox">
+ <box class="LineBox">
+ <box class="DocumentTextBox" text="hat " />
+ <box class="PlaceholderBox"/>
+ <box class="InlineElementBox">
+ <box class="DrawableBox"/>
+ <box class="DocumentTextBox" text="cat " />
+ <box class="DocumentTextBox" text="sat" />
+ <box class="PlaceholderBox"/>
+ <box class="DrawableBox"/>
+ </box>
+ <box class="DocumentTextBox" text=" " />
+ <box class="DocumentTextBox" text="bat" />
+ <box class="PlaceholderBox"/>
+ </box>
+ </box>
+ </box>
+ </box>
+ </result>
+ </test>
+
+
+ <test id="Inline With Surrounding Text Split 1" layoutWidth="76">
+ <doc><![CDATA[ <root>hat <b>cat sat</b> bat</root> ]]></doc>
+ <result>
+ <box class="RootBox">
+ <box class="BlockElementBox" element="root">
+ <box class="ParagraphBox">
+ <box class="LineBox">
+ <box class="DocumentTextBox" text="hat "/>
+ <box class="PlaceholderBox"/>
+ <box class="InlineElementBox">
+ <box class="DrawableBox"/>
+ <box class="DocumentTextBox" text="cat " />
+ <box class="DocumentTextBox" text="sat" />
+ <box class="PlaceholderBox"/>
+ <box class="DrawableBox"/>
+ </box>
+ <box class="DocumentTextBox" text=" " />
+ </box>
+ <box class="LineBox">
+ <box class="DocumentTextBox" text="bat" />
+ <box class="PlaceholderBox"/>
+ </box>
+ </box>
+ </box>
+ </box>
+ </result>
+ </test>
+
+
+ <test id="Inline With Surrounding Text Split 2" layoutWidth="75">
+ <doc><![CDATA[ <root>hat <b>cat sat</b> bat</root> ]]></doc>
+ <result>
+ <box class="RootBox">
+ <box class="BlockElementBox" element="root">
+ <box class="ParagraphBox">
+ <box class="LineBox">
+ <box class="DocumentTextBox" text="hat " />
+ <box class="PlaceholderBox"/>
+ <box class="InlineElementBox">
+ <box class="DrawableBox"/>
+ <box class="DocumentTextBox" text="cat " />
+ </box>
+ </box>
+ <box class="LineBox">
+ <box class="InlineElementBox">
+ <box class="DocumentTextBox" text="sat" />
+ <box class="PlaceholderBox"/>
+ <box class="DrawableBox"/>
+ </box>
+ <box class="DocumentTextBox" text=" " />
+ <box class="DocumentTextBox" text="bat" />
+ <box class="PlaceholderBox"/>
+ </box>
+ </box>
+ </box>
+ </box>
+ </result>
+ </test>
+
+
+
+ <test id="Block Child w/ Inline Before" layoutWidth="75">
+ <doc><![CDATA[ <root>Paris <p>Garters</p></root> ]]></doc>
+ <result>
+ <box class="RootBox">
+ <box class="BlockElementBox" element="root">
+
+ <box class="ParagraphBox">
+ <box class="LineBox">
+ <box class="DocumentTextBox" text="Paris" />
+ <box class="PlaceholderBox"/>
+ </box>
+ </box>
+
+ <box class="BlockElementBox">
+ <box class="ParagraphBox">
+ <box class="LineBox">
+ <box class="DocumentTextBox" text="Garters" />
+ <box class="PlaceholderBox"/>
+ </box>
+ </box>
+ </box>
+
+ </box>
+ </box>
+ </result>
+ </test>
+
+
+
+ <test id="Block Child w/ Inline After" layoutWidth="100">
+ <doc><![CDATA[ <root><p>Harris</p> Tweed</root> ]]></doc>
+ <result>
+ <box class="RootBox">
+ <box class="BlockElementBox" element="root">
+
+ <box class="BlockElementBox">
+ <box class="ParagraphBox">
+ <box class="LineBox">
+ <box class="DocumentTextBox" text="Harris" />
+ <box class="PlaceholderBox"/>
+ </box>
+ </box>
+ </box>
+
+ <box class="ParagraphBox">
+ <box class="LineBox">
+ <box class="DocumentTextBox" text="Tweed" />
+ <box class="PlaceholderBox"/>
+ </box>
+ </box>
+
+ </box>
+ </box>
+ </result>
+ </test>
+
+
+ <test id="Block Child w/ Surrounding Inlines" layoutWidth="200">
+ <doc><![CDATA[ <root>Oliver <p>Boliver</p> Butt</root> ]]></doc>
+ <result>
+ <box class="RootBox">
+ <box class="BlockElementBox" element="root">
+
+ <box class="ParagraphBox">
+ <box class="LineBox">
+ <box class="DocumentTextBox" text="Oliver" />
+ <box class="PlaceholderBox"/>
+ </box>
+ </box>
+
+ <box class="BlockElementBox">
+ <box class="ParagraphBox">
+ <box class="LineBox">
+ <box class="DocumentTextBox" text="Boliver" />
+ <box class="PlaceholderBox"/>
+ </box>
+ </box>
+ </box>
+
+ <box class="ParagraphBox">
+ <box class="LineBox">
+ <box class="DocumentTextBox" text="Butt" />
+ <box class="PlaceholderBox"/>
+ </box>
+ </box>
+
+ </box>
+ </box>
+ </result>
+ </test>
+
+
+ <!-- TODO test blocks inside inlines -->
+
+
+</testcases>
diff --git a/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/src/org/eclipse/wst/xml/vex/core/internal/layout/linebreaks.css b/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/src/org/eclipse/wst/xml/vex/core/internal/layout/linebreaks.css
new file mode 100644
index 0000000..895714a
--- /dev/null
+++ b/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/src/org/eclipse/wst/xml/vex/core/internal/layout/linebreaks.css
@@ -0,0 +1,17 @@
+
+root {
+ display: block;
+}
+
+p {
+ display: block;
+}
+
+pre {
+ display: block;
+ white-space: pre;
+}
+
+b {
+ display: inline;
+}
diff --git a/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/src/org/eclipse/wst/xml/vex/core/internal/layout/linebreaks.xml b/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/src/org/eclipse/wst/xml/vex/core/internal/layout/linebreaks.xml
new file mode 100644
index 0000000..12eafd8
--- /dev/null
+++ b/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/src/org/eclipse/wst/xml/vex/core/internal/layout/linebreaks.xml
@@ -0,0 +1,253 @@
+<?xml version='1.0'?>
+<testcases css="linebreaks.css">
+
+
+ <test id="LF" layoutWidth="100">
+ <doc><![CDATA[ <root><pre>newline
end</pre></root> ]]></doc>
+ <result>
+ <box class="RootBox">
+ <box class="BlockElementBox" element="root">
+
+ <box class="BlockElementBox" element="pre">
+
+ <box class="ParagraphBox">
+ <box class="LineBox">
+ <box class="DocumentTextBox" text="newline " />
+ </box>
+ <box class="LineBox">
+ <box class="DocumentTextBox" text="end" />
+ <box class="PlaceholderBox"/>
+ </box>
+ </box>
+
+ </box>
+ </box>
+ </box>
+ </result>
+ </test>
+
+
+ <test id="Two LFs" layoutWidth="100">
+ <doc><![CDATA[ <root><pre>newline

end</pre></root> ]]></doc>
+ <result>
+ <box class="RootBox">
+ <box class="BlockElementBox" element="root">
+
+ <box class="BlockElementBox" element="pre">
+
+ <box class="ParagraphBox">
+ <box class="LineBox">
+ <box class="DocumentTextBox" text="newline " />
+ </box>
+ <box class="LineBox">
+ <box class="DocumentTextBox" text=" " />
+ </box>
+ <box class="LineBox">
+ <box class="DocumentTextBox" text="end" />
+ <box class="PlaceholderBox"/>
+ </box>
+ </box>
+
+ </box>
+ </box>
+ </box>
+ </result>
+ </test>
+
+
+ <test id="Newline w/ Surrounding Space" layoutWidth="100">
+ <doc><![CDATA[ <root><pre>newline 
 end</pre></root> ]]></doc>
+ <result>
+ <box class="RootBox">
+ <box class="BlockElementBox" element="root">
+
+ <box class="BlockElementBox" element="pre">
+
+ <box class="ParagraphBox">
+ <box class="LineBox">
+ <box class="DocumentTextBox" text="newline 
" />
+ </box>
+ <box class="LineBox">
+ <box class="DocumentTextBox" text=" " />
+ <box class="DocumentTextBox" text="end" />
+ <box class="PlaceholderBox"/>
+ </box>
+ </box>
+
+ </box>
+ </box>
+ </box>
+ </result>
+ </test>
+
+
+ <test id="Carriage Return" layoutWidth="100">
+ <doc><![CDATA[ <root><pre>line1
line2</pre></root> ]]></doc>
+ <result>
+ <box class="RootBox">
+ <box class="BlockElementBox" element="root">
+
+ <box class="BlockElementBox" element="pre">
+
+ <box class="ParagraphBox">
+ <box class="LineBox">
+ <box class="DocumentTextBox" text="line1
" />
+ </box>
+ <box class="LineBox">
+ <box class="DocumentTextBox" text="line2" />
+ <box class="PlaceholderBox"/>
+ </box>
+ </box>
+
+ </box>
+ </box>
+ </box>
+ </result>
+ </test>
+
+
+ <test id="Two CRs" layoutWidth="100">
+ <doc><![CDATA[ <root><pre>line1

line2</pre></root> ]]></doc>
+ <result>
+ <box class="RootBox">
+ <box class="BlockElementBox" element="root">
+
+ <box class="BlockElementBox" element="pre">
+
+ <box class="ParagraphBox">
+ <box class="LineBox">
+ <box class="DocumentTextBox" text="line1
" />
+ </box>
+ <box class="LineBox">
+ <box class="DocumentTextBox" text="
" />
+ </box>
+ <box class="LineBox">
+ <box class="DocumentTextBox" text="line2" />
+ <box class="PlaceholderBox"/>
+ </box>
+ </box>
+
+ </box>
+ </box>
+ </box>
+ </result>
+ </test>
+
+
+ <test id="CRLF" layoutWidth="100">
+ <doc><![CDATA[ <root><pre>line1
line2</pre></root> ]]></doc>
+ <result>
+ <box class="RootBox">
+ <box class="BlockElementBox" element="root">
+
+ <box class="BlockElementBox" element="pre">
+
+ <box class="ParagraphBox">
+ <box class="LineBox">
+ <box class="DocumentTextBox" text="line1
" />
+ </box>
+ <box class="LineBox">
+ <box class="DocumentTextBox" text="line2" />
+ <box class="PlaceholderBox"/>
+ </box>
+ </box>
+
+ </box>
+ </box>
+ </box>
+ </result>
+ </test>
+
+
+ <test id="CRCRLF" layoutWidth="100">
+ <doc><![CDATA[ <root><pre>line1

line2</pre></root> ]]></doc>
+ <result>
+ <box class="RootBox">
+ <box class="BlockElementBox" element="root">
+
+ <box class="BlockElementBox" element="pre">
+
+ <box class="ParagraphBox">
+ <box class="LineBox">
+ <box class="DocumentTextBox" text="line1
" />
+ </box>
+ <box class="LineBox">
+ <box class="DocumentTextBox" text="
" />
+ </box>
+ <box class="LineBox">
+ <box class="DocumentTextBox" text="line2" />
+ <box class="PlaceholderBox"/>
+ </box>
+ </box>
+
+ </box>
+ </box>
+ </box>
+ </result>
+ </test>
+
+
+ <test id="CRLFCRLF" layoutWidth="100">
+ <doc><![CDATA[ <root><pre>line1

line2</pre></root> ]]></doc>
+ <result>
+ <box class="RootBox">
+ <box class="BlockElementBox" element="root">
+
+ <box class="BlockElementBox" element="pre">
+
+ <box class="ParagraphBox">
+ <box class="LineBox">
+ <box class="DocumentTextBox" text="line1
" />
+ </box>
+ <box class="LineBox">
+ <box class="DocumentTextBox" text="
" />
+ </box>
+ <box class="LineBox">
+ <box class="DocumentTextBox" text="line2" />
+ <box class="PlaceholderBox"/>
+ </box>
+ </box>
+
+ </box>
+ </box>
+ </box>
+ </result>
+ </test>
+
+
+ <test id="Newline Inside Inline" layoutWidth="100">
+ <doc><![CDATA[ <root><pre><b>newline
+end</b></pre></root> ]]></doc>
+ <result>
+ <box class="RootBox">
+ <box class="BlockElementBox" element="root">
+
+ <box class="BlockElementBox" element="pre">
+
+ <box class="ParagraphBox">
+ <box class="LineBox">
+ <box class="PlaceholderBox"/>
+ <box class="InlineElementBox" element="b">
+ <box class="DrawableBox"/>
+ <box class="DocumentTextBox" text="newline " />
+ </box>
+ </box>
+ <box class="LineBox">
+ <box class="InlineElementBox" element="b">
+ <box class="DocumentTextBox" text="end" />
+ <box class="PlaceholderBox"/>
+ <box class="DrawableBox"/>
+ </box>
+ <box class="PlaceholderBox"/>
+ </box>
+ </box>
+
+ </box>
+ </box>
+ </box>
+ </result>
+ </test>
+
+
+
+</testcases>
diff --git a/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/src/org/eclipse/wst/xml/vex/core/internal/layout/tables.css b/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/src/org/eclipse/wst/xml/vex/core/internal/layout/tables.css
new file mode 100644
index 0000000..ca4bc34
--- /dev/null
+++ b/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/src/org/eclipse/wst/xml/vex/core/internal/layout/tables.css
@@ -0,0 +1,36 @@
+
+root {
+ display: block;
+}
+
+div {
+ display: block;
+}
+
+table {
+ display: table;
+}
+
+tbody {
+ display: table-row-group;
+}
+
+thead {
+ display: table-header-group;
+}
+
+tr {
+ display: table-row;
+}
+
+td {
+ display: table-cell;
+}
+
+b {
+ display: inline;
+}
+
+p {
+ display: block;
+}
diff --git a/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/src/org/eclipse/wst/xml/vex/core/internal/layout/tables.xml b/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/src/org/eclipse/wst/xml/vex/core/internal/layout/tables.xml
new file mode 100644
index 0000000..4fd860e
--- /dev/null
+++ b/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/src/org/eclipse/wst/xml/vex/core/internal/layout/tables.xml
@@ -0,0 +1,538 @@
+<?xml version='1.0'?>
+<testcases css="tables.css">
+
+ <test id="Table 1R 1C (empty)" layoutWidth="100">
+ <doc><![CDATA[ <root><table><tr><td></td></tr></table></root> ]]></doc>
+ <result>
+ <box class="RootBox">
+ <box class="BlockElementBox" element="root">
+ <box class="TableBox" element="table">
+ <box class="TableBodyBox">
+ <box class="TableRowGroupBox">
+ <box class="TableRowBox" element="tr">
+ <box class="TableCellBox" element="td">
+ <box class="ParagraphBox">
+ <box class="LineBox">
+ <box class="PlaceholderBox"/>
+ </box>
+ </box>
+ </box>
+ </box>
+ </box>
+ </box>
+ </box>
+ </box>
+ </box>
+ </result>
+ </test>
+
+ <test id="Table 1R 1C" layoutWidth="100">
+ <doc><![CDATA[ <root><table><tr><td>foo</td></tr></table></root> ]]></doc>
+ <result>
+ <box class="RootBox">
+ <box class="BlockElementBox" element="root">
+ <box class="TableBox" element="table">
+ <box class="TableBodyBox">
+ <box class="TableRowGroupBox">
+ <box class="TableRowBox" element="tr">
+ <box class="TableCellBox" element="td">
+ <box class="ParagraphBox">
+ <box class="LineBox">
+ <box class="DocumentTextBox" text="foo"/>
+ <box class="PlaceholderBox"/>
+ </box>
+ </box>
+ </box>
+ </box>
+ </box>
+ </box>
+ </box>
+ </box>
+ </box>
+ </result>
+ </test>
+
+ <test id="Table 1R 2C" layoutWidth="100">
+ <doc><![CDATA[ <root><table><tr><td>foo</td><td>bar</td></tr></table></root> ]]></doc>
+ <result>
+ <box class="RootBox">
+ <box class="BlockElementBox" element="root">
+ <box class="TableBox" element="table">
+ <box class="TableBodyBox">
+ <box class="TableRowGroupBox">
+
+ <box class="TableRowBox" element="tr">
+
+ <box class="TableCellBox" element="td">
+ <box class="ParagraphBox">
+ <box class="LineBox">
+ <box class="DocumentTextBox" text="foo"/>
+ <box class="PlaceholderBox"/>
+ </box>
+ </box>
+ </box>
+
+ <box class="TableCellBox" element="td">
+ <box class="ParagraphBox">
+ <box class="LineBox">
+ <box class="DocumentTextBox" text="bar"/>
+ <box class="PlaceholderBox"/>
+ </box>
+ </box>
+ </box>
+
+ </box>
+
+ </box>
+ </box>
+ </box>
+ </box>
+ </box>
+ </result>
+ </test>
+
+ <test id="Table 1R 1 Anonymous Cell" layoutWidth="100">
+ <doc><![CDATA[ <root><table><tr>foo</tr></table></root> ]]></doc>
+ <result>
+ <box class="RootBox">
+ <box class="BlockElementBox" element="root">
+ <box class="TableBox" element="table">
+ <box class="TableBodyBox">
+ <box class="TableRowGroupBox">
+
+ <box class="TableRowBox" element="tr">
+ <box class="TableCellBox">
+ <box class="ParagraphBox">
+ <box class="LineBox">
+ <box class="DocumentTextBox" text="foo"/>
+ <box class="PlaceholderBox"/>
+ </box>
+ </box>
+ </box>
+ </box>
+
+ </box>
+ </box>
+ </box>
+ </box>
+ </box>
+ </result>
+ </test>
+
+ <test id="Anonymous Cells 2" layoutWidth="100">
+ <doc><![CDATA[ <root><table><tr>foo <b>bar</b></tr></table></root> ]]></doc>
+ <result>
+ <box class="RootBox">
+ <box class="BlockElementBox" element="root">
+ <box class="TableBox" element="table">
+ <box class="TableBodyBox">
+ <box class="TableRowGroupBox">
+
+ <box class="TableRowBox" element="tr">
+ <box class="TableCellBox">
+ <box class="ParagraphBox">
+ <box class="LineBox">
+ <box class="DocumentTextBox" text="foo "/>
+ <box class="PlaceholderBox"/>
+ <box class="InlineElementBox" element="b">
+ <box class="DrawableBox"/>
+ <box class="DocumentTextBox" text="bar"/>
+ <box class="PlaceholderBox"/>
+ <box class="DrawableBox"/>
+ </box>
+ <box class="PlaceholderBox"/>
+ </box>
+ </box>
+ </box>
+ </box>
+
+ </box>
+ </box>
+ </box>
+ </box>
+ </box>
+ </result>
+ </test>
+
+ <test id="Anonymous Cells 3" layoutWidth="100">
+ <doc><![CDATA[ <root><table><tr>foo <p>bar</p></tr></table></root> ]]></doc>
+ <result>
+ <box class="RootBox">
+ <box class="BlockElementBox" element="root">
+ <box class="TableBox" element="table">
+ <box class="TableBodyBox">
+ <box class="TableRowGroupBox">
+
+
+ <box class="TableRowBox" element="tr">
+ <box class="TableCellBox">
+
+ <box class="ParagraphBox">
+ <box class="LineBox">
+ <box class="DocumentTextBox" text="foo"/>
+ <box class="PlaceholderBox"/>
+ </box>
+ </box>
+
+ <box class="BlockElementBox" element="p">
+ <box class="ParagraphBox">
+ <box class="LineBox">
+ <box class="DocumentTextBox" text="bar"/>
+ <box class="PlaceholderBox"/>
+ </box>
+ </box>
+ </box>
+
+ </box>
+ </box>
+
+ </box>
+ </box>
+ </box>
+ </box>
+ </box>
+ </result>
+ </test>
+
+ <test id="Anonymous Cells 4" layoutWidth="100">
+ <doc><![CDATA[ <root><table><tr>foo <td>bar</td></tr></table></root> ]]></doc>
+ <result>
+ <box class="RootBox">
+ <box class="BlockElementBox" element="root">
+ <box class="TableBox" element="table">
+ <box class="TableBodyBox">
+ <box class="TableRowGroupBox">
+
+ <box class="TableRowBox" element="tr">
+
+ <box class="TableCellBox">
+ <box class="ParagraphBox">
+ <box class="LineBox">
+ <box class="DocumentTextBox" text="foo"/>
+ <box class="PlaceholderBox"/>
+ </box>
+ </box>
+ </box>
+
+ <box class="TableCellBox" element="td">
+ <box class="ParagraphBox">
+ <box class="LineBox">
+ <box class="DocumentTextBox" text="bar"/>
+ <box class="PlaceholderBox"/>
+ </box>
+ </box>
+ </box>
+
+ </box>
+
+ </box>
+ </box>
+ </box>
+ </box>
+ </box>
+ </result>
+ </test>
+
+
+ <test id="Anonymous Cells 5" layoutWidth="100">
+ <doc><![CDATA[ <root><table><tr><td>bar</td> foo</tr></table></root> ]]></doc>
+ <result>
+ <box class="RootBox">
+ <box class="BlockElementBox" element="root">
+ <box class="TableBox" element="table">
+ <box class="TableBodyBox">
+ <box class="TableRowGroupBox">
+
+ <box class="TableRowBox" element="tr">
+
+ <box class="TableCellBox" element="td">
+ <box class="ParagraphBox">
+ <box class="LineBox">
+ <box class="DocumentTextBox" text="bar"/>
+ <box class="PlaceholderBox"/>
+ </box>
+ </box>
+ </box>
+
+ <box class="TableCellBox">
+ <box class="ParagraphBox">
+ <box class="LineBox">
+ <box class="DocumentTextBox" text="foo"/>
+ <box class="PlaceholderBox"/>
+ </box>
+ </box>
+ </box>
+
+ </box>
+
+ </box>
+ </box>
+ </box>
+ </box>
+ </box>
+ </result>
+ </test>
+
+
+ <test id="Empty Table" layoutWidth="100">
+ <doc><![CDATA[ <root><table/></root> ]]></doc>
+ <result>
+ <box class="RootBox">
+ <box class="BlockElementBox" element="root">
+ <box class="TableBox">
+ </box>
+ </box>
+ </box>
+ </result>
+ </test>
+
+
+ <test id="Anonymous Table and Row - Empty" layoutWidth="100">
+ <doc><![CDATA[ <root><td></td></root> ]]></doc>
+ <result>
+ <box class="RootBox">
+ <box class="BlockElementBox" element="root">
+ <box class="TableBox">
+ <box class="TableBodyBox">
+ <box class="TableRowGroupBox">
+
+ <box class="TableRowBox">
+
+ <box class="TableCellBox" element="td">
+ <box class="ParagraphBox">
+ <box class="LineBox">
+ <box class="PlaceholderBox"/>
+ </box>
+ </box>
+ </box>
+
+ </box>
+
+ </box>
+ </box>
+ </box>
+ </box>
+ </box>
+ </result>
+ </test>
+
+ <test id="Anonymous Table and Row" layoutWidth="100">
+ <doc><![CDATA[ <root><td>bar</td></root> ]]></doc>
+ <result>
+ <box class="RootBox">
+ <box class="BlockElementBox" element="root">
+ <box class="TableBox">
+ <box class="TableBodyBox">
+ <box class="TableRowGroupBox">
+
+ <box class="TableRowBox">
+
+ <box class="TableCellBox" element="td">
+ <box class="ParagraphBox">
+ <box class="LineBox">
+ <box class="DocumentTextBox" text="bar"/>
+ <box class="PlaceholderBox"/>
+ </box>
+ </box>
+ </box>
+
+ </box>
+
+ </box>
+ </box>
+ </box>
+ </box>
+ </box>
+ </result>
+ </test>
+
+ <test id="Anonymous Table and Cell" layoutWidth="100">
+ <doc><![CDATA[ <root><tr>bar</tr></root> ]]></doc>
+ <result>
+ <box class="RootBox">
+ <box class="BlockElementBox" element="root">
+ <box class="TableBox">
+ <box class="TableBodyBox">
+ <box class="TableRowGroupBox">
+
+ <box class="TableRowBox" element="tr">
+
+ <box class="TableCellBox">
+ <box class="ParagraphBox">
+ <box class="LineBox">
+ <box class="DocumentTextBox" text="bar"/>
+ <box class="PlaceholderBox"/>
+ </box>
+ </box>
+ </box>
+
+ </box>
+
+ </box>
+ </box>
+ </box>
+ </box>
+ </box>
+ </result>
+ </test>
+
+
+ <test id="Anonymous Row and Cell" layoutWidth="100">
+ <doc><![CDATA[ <root><table>bar</table></root> ]]></doc>
+ <result>
+ <box class="RootBox">
+ <box class="BlockElementBox" element="root">
+ <box class="TableBox" element="table">
+ <box class="TableBodyBox">
+ <box class="TableRowGroupBox">
+
+ <box class="TableRowBox">
+
+ <box class="TableCellBox">
+ <box class="ParagraphBox">
+ <box class="LineBox">
+ <box class="DocumentTextBox" text="bar"/>
+ <box class="PlaceholderBox"/>
+ </box>
+ </box>
+ </box>
+
+ </box>
+
+ </box>
+ </box>
+ </box>
+ </box>
+ </box>
+ </result>
+ </test>
+
+
+ <test id="Empty table-row-group" layoutWidth="100">
+ <doc><![CDATA[ <root><table><tbody/></table></root> ]]></doc>
+ <result>
+ <box class="RootBox">
+ <box class="BlockElementBox" element="root">
+ <box class="TableBox" element="table">
+ <box class="TableBodyBox">
+ <box class="TableRowGroupBox" element="tbody">
+
+ </box>
+ </box>
+ </box>
+ </box>
+ </box>
+ </result>
+ </test>
+
+
+
+ <test id="table-row-group With Content" layoutWidth="100">
+ <doc><![CDATA[ <root><table><tbody>bar</tbody></table></root> ]]></doc>
+ <result>
+ <box class="RootBox">
+ <box class="BlockElementBox" element="root">
+ <box class="TableBox" element="table">
+ <box class="TableBodyBox">
+ <box class="TableRowGroupBox" element="tbody">
+
+ <box class="TableRowBox">
+
+ <box class="TableCellBox">
+ <box class="ParagraphBox">
+ <box class="LineBox">
+ <box class="DocumentTextBox" text="bar"/>
+ <box class="PlaceholderBox"/>
+ </box>
+ </box>
+ </box>
+
+ </box>
+
+ </box>
+ </box>
+ </box>
+ </box>
+ </box>
+ </result>
+ </test>
+
+
+ <test id="Empty table-header-group" layoutWidth="100">
+ <doc><![CDATA[ <root><table><thead/></table></root> ]]></doc>
+ <result>
+ <box class="RootBox">
+ <box class="BlockElementBox" element="root">
+ <box class="TableBox" element="table">
+ <box class="TableBodyBox">
+ <box class="TableRowGroupBox" element="thead">
+
+ </box>
+ </box>
+ </box>
+ </box>
+ </box>
+ </result>
+ </test>
+
+
+
+ <test id="table-header-group With Content" layoutWidth="100">
+ <doc><![CDATA[ <root><table><thead>bar</thead></table></root> ]]></doc>
+ <result>
+ <box class="RootBox">
+ <box class="BlockElementBox" element="root">
+ <box class="TableBox" element="table">
+ <box class="TableBodyBox">
+ <box class="TableRowGroupBox" element="thead">
+
+ <box class="TableRowBox">
+
+ <box class="TableCellBox">
+ <box class="ParagraphBox">
+ <box class="LineBox">
+ <box class="DocumentTextBox" text="bar"/>
+ <box class="PlaceholderBox"/>
+ </box>
+ </box>
+ </box>
+
+ </box>
+
+ </box>
+ </box>
+ </box>
+ </box>
+ </box>
+ </result>
+ </test>
+
+
+ <test id="Table inna Table" layoutWidth="100">
+ <doc><![CDATA[ <root><table><table/></table></root> ]]></doc>
+ <result>
+ <box class="RootBox">
+ <box class="BlockElementBox" element="root">
+ <box class="TableBox" element="table">
+ <box class="TableBodyBox">
+ <box class="TableRowGroupBox">
+
+ <box class="TableRowBox">
+
+ <box class="TableCellBox">
+ <box class="TableBox">
+ </box>
+ </box>
+
+ </box>
+
+ </box>
+ </box>
+ </box>
+ </box>
+ </box>
+ </result>
+ </test>
+
+
+</testcases>
diff --git a/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/src/org/eclipse/wst/xml/vex/core/internal/layout/test.css b/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/src/org/eclipse/wst/xml/vex/core/internal/layout/test.css
new file mode 100644
index 0000000..42ccd67
--- /dev/null
+++ b/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/src/org/eclipse/wst/xml/vex/core/internal/layout/test.css
@@ -0,0 +1,46 @@
+/*
+ * Style sheet used for layout unit testing.
+ */
+root {
+ display: block;
+ font: 10pt monospaced;
+}
+
+
+beforeBlock {
+ display: block;
+}
+
+beforeBlock:before {
+ display: block;
+ content: 'Before';
+}
+
+b {
+ display: inline;
+}
+
+p {
+ display: block;
+}
+
+.blockAfter:after {
+ display: block;
+ content: 'AFTER';
+}
+
+.blockBefore:before {
+ display: block;
+ content: 'BEFORE';
+}
+
+.inlineAfter:after {
+ display: inline;
+ content: 'after';
+}
+
+.inlineBefore:before {
+ display: inline;
+ content: 'before';
+}
+
diff --git a/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/src/org/eclipse/wst/xml/vex/core/tests/VEXCoreTestPlugin.java b/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/src/org/eclipse/wst/xml/vex/core/tests/VEXCoreTestPlugin.java
new file mode 100644
index 0000000..4b98420
--- /dev/null
+++ b/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/src/org/eclipse/wst/xml/vex/core/tests/VEXCoreTestPlugin.java
@@ -0,0 +1,50 @@
+package org.eclipse.wst.xml.vex.core.tests;
+
+import org.eclipse.ui.plugin.AbstractUIPlugin;
+import org.osgi.framework.BundleContext;
+
+/**
+ * The activator class controls the plug-in life cycle
+ */
+public class VEXCoreTestPlugin extends AbstractUIPlugin {
+
+ // The plug-in ID
+ public static final String PLUGIN_ID = "org.eclipse.wst.xml.vex.core.tests";
+
+ // The shared instance
+ private static VEXCoreTestPlugin plugin;
+
+ /**
+ * The constructor
+ */
+ public VEXCoreTestPlugin() {
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.eclipse.ui.plugin.AbstractUIPlugin#start(org.osgi.framework.BundleContext)
+ */
+ public void start(BundleContext context) throws Exception {
+ super.start(context);
+ plugin = this;
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.eclipse.ui.plugin.AbstractUIPlugin#stop(org.osgi.framework.BundleContext)
+ */
+ public void stop(BundleContext context) throws Exception {
+ plugin = null;
+ super.stop(context);
+ }
+
+ /**
+ * Returns the shared instance
+ *
+ * @return the shared instance
+ */
+ public static VEXCoreTestPlugin getDefault() {
+ return plugin;
+ }
+
+}
diff --git a/sourceediting/tests/org.eclipse.wst.xml.vex.ui.tests/.classpath b/sourceediting/tests/org.eclipse.wst.xml.vex.ui.tests/.classpath
new file mode 100644
index 0000000..64c5e31
--- /dev/null
+++ b/sourceediting/tests/org.eclipse.wst.xml.vex.ui.tests/.classpath
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+ <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/J2SE-1.5"/>
+ <classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>
+ <classpathentry kind="src" path="src"/>
+ <classpathentry kind="output" path="bin"/>
+</classpath>
diff --git a/sourceediting/tests/org.eclipse.wst.xml.vex.ui.tests/.project b/sourceediting/tests/org.eclipse.wst.xml.vex.ui.tests/.project
new file mode 100644
index 0000000..8f21bd3
--- /dev/null
+++ b/sourceediting/tests/org.eclipse.wst.xml.vex.ui.tests/.project
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+ <name>org.eclipse.wst.xml.vex.ui.tests</name>
+ <comment></comment>
+ <projects>
+ </projects>
+ <buildSpec>
+ <buildCommand>
+ <name>org.eclipse.jdt.core.javabuilder</name>
+ <arguments>
+ </arguments>
+ </buildCommand>
+ <buildCommand>
+ <name>org.eclipse.pde.ManifestBuilder</name>
+ <arguments>
+ </arguments>
+ </buildCommand>
+ <buildCommand>
+ <name>org.eclipse.pde.SchemaBuilder</name>
+ <arguments>
+ </arguments>
+ </buildCommand>
+ </buildSpec>
+ <natures>
+ <nature>org.eclipse.pde.PluginNature</nature>
+ <nature>org.eclipse.jdt.core.javanature</nature>
+ </natures>
+</projectDescription>
diff --git a/sourceediting/tests/org.eclipse.wst.xml.vex.ui.tests/.settings/org.eclipse.jdt.core.prefs b/sourceediting/tests/org.eclipse.wst.xml.vex.ui.tests/.settings/org.eclipse.jdt.core.prefs
new file mode 100644
index 0000000..9640e53
--- /dev/null
+++ b/sourceediting/tests/org.eclipse.wst.xml.vex.ui.tests/.settings/org.eclipse.jdt.core.prefs
@@ -0,0 +1,7 @@
+#Wed Oct 01 02:46:26 GMT 2008
+eclipse.preferences.version=1
+org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.5
+org.eclipse.jdt.core.compiler.compliance=1.5
+org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
+org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
+org.eclipse.jdt.core.compiler.source=1.5
diff --git a/sourceediting/tests/org.eclipse.wst.xml.vex.ui.tests/META-INF/MANIFEST.MF b/sourceediting/tests/org.eclipse.wst.xml.vex.ui.tests/META-INF/MANIFEST.MF
new file mode 100644
index 0000000..017c090
--- /dev/null
+++ b/sourceediting/tests/org.eclipse.wst.xml.vex.ui.tests/META-INF/MANIFEST.MF
@@ -0,0 +1,13 @@
+Manifest-Version: 1.0
+Bundle-ManifestVersion: 2
+Bundle-Name: VEX UI Test Plugin
+Bundle-SymbolicName: org.eclipse.wst.xml.vex.ui.tests
+Bundle-Version: 0.5.0.qualifier
+Bundle-Activator: org.eclipse.wst.xml.vex.ui.tests.Activator
+Bundle-Vendor: Eclipse
+Require-Bundle: org.eclipse.ui,
+ org.eclipse.core.runtime,
+ org.eclipse.wst.xml.vex.ui;bundle-version="0.5.0",
+ org.junit;bundle-version="3.8.1"
+Bundle-RequiredExecutionEnvironment: J2SE-1.5
+Bundle-ActivationPolicy: lazy
diff --git a/sourceediting/tests/org.eclipse.wst.xml.vex.ui.tests/build.properties b/sourceediting/tests/org.eclipse.wst.xml.vex.ui.tests/build.properties
new file mode 100644
index 0000000..34d2e4d
--- /dev/null
+++ b/sourceediting/tests/org.eclipse.wst.xml.vex.ui.tests/build.properties
@@ -0,0 +1,4 @@
+source.. = src/
+output.. = bin/
+bin.includes = META-INF/,\
+ .
diff --git a/sourceediting/tests/org.eclipse.wst.xml.vex.ui.tests/src/org/eclipse/wst/xml/vex/ui/internal/tests/AssociationTest.java b/sourceediting/tests/org.eclipse.wst.xml.vex.ui.tests/src/org/eclipse/wst/xml/vex/ui/internal/tests/AssociationTest.java
new file mode 100644
index 0000000..0197660
--- /dev/null
+++ b/sourceediting/tests/org.eclipse.wst.xml.vex.ui.tests/src/org/eclipse/wst/xml/vex/ui/internal/tests/AssociationTest.java
@@ -0,0 +1,298 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.ui.internal.tests;
+
+import java.util.Collection;
+
+import org.eclipse.wst.xml.vex.ui.internal.editor.Association;
+
+import junit.framework.TestCase;
+
+public class AssociationTest extends TestCase {
+
+ public void testAddRemove() throws Exception {
+
+ Association assoc = new Association();
+
+ Collection c;
+
+ c = assoc.getRightsForLeft("l0");
+ assertNotNull(c);
+ assertEquals(0, c.size());
+
+ c = assoc.getLeftsForRight("r0");
+ assertNotNull(c);
+ assertEquals(0, c.size());
+
+ assoc.add("l0", "r0");
+
+ c = assoc.getRightsForLeft("l0");
+ assertNotNull(c);
+ assertEquals(1, c.size());
+ assertEquals("r0", c.toArray()[0]);
+
+ c = assoc.getLeftsForRight("r0");
+ assertNotNull(c);
+ assertEquals(1, c.size());
+ assertEquals("l0", c.toArray()[0]);
+
+ assoc.add("l0", "r1");
+ assoc.add("l1", "r0");
+ assoc.add("l1", "r1");
+
+ c = assoc.getRightsForLeft("l0");
+ assertNotNull(c);
+ assertEquals(2, c.size());
+ assertTrue(c.contains("r0"));
+ assertTrue(c.contains("r1"));
+
+ c = assoc.getRightsForLeft("l1");
+ assertNotNull(c);
+ assertEquals(2, c.size());
+ assertTrue(c.contains("r0"));
+ assertTrue(c.contains("r1"));
+
+ c = assoc.getLeftsForRight("r0");
+ assertNotNull(c);
+ assertEquals(2, c.size());
+ assertTrue(c.contains("l0"));
+ assertTrue(c.contains("l1"));
+
+ c = assoc.getLeftsForRight("r1");
+ assertNotNull(c);
+ assertEquals(2, c.size());
+ assertTrue(c.contains("l0"));
+ assertTrue(c.contains("l1"));
+
+ assoc.remove("l0", "r0");
+
+ c = assoc.getRightsForLeft("l0");
+ assertNotNull(c);
+ assertEquals(1, c.size());
+ assertTrue(c.contains("r1"));
+
+ c = assoc.getRightsForLeft("l1");
+ assertNotNull(c);
+ assertEquals(2, c.size());
+ assertTrue(c.contains("r0"));
+ assertTrue(c.contains("r1"));
+
+ c = assoc.getLeftsForRight("r0");
+ assertNotNull(c);
+ assertEquals(1, c.size());
+ assertTrue(c.contains("l1"));
+
+ c = assoc.getLeftsForRight("r1");
+ assertNotNull(c);
+ assertEquals(2, c.size());
+ assertTrue(c.contains("l0"));
+ assertTrue(c.contains("l1"));
+
+ assoc.remove("l0", "r1");
+
+ c = assoc.getRightsForLeft("l0");
+ assertNotNull(c);
+ assertEquals(0, c.size());
+
+ c = assoc.getRightsForLeft("l1");
+ assertNotNull(c);
+ assertEquals(2, c.size());
+ assertTrue(c.contains("r0"));
+ assertTrue(c.contains("r1"));
+
+ c = assoc.getLeftsForRight("r0");
+ assertNotNull(c);
+ assertEquals(1, c.size());
+ assertTrue(c.contains("l1"));
+
+ c = assoc.getLeftsForRight("r1");
+ assertNotNull(c);
+ assertEquals(1, c.size());
+ assertTrue(c.contains("l1"));
+
+ assoc.remove("l1", "r0");
+
+ c = assoc.getRightsForLeft("l0");
+ assertNotNull(c);
+ assertEquals(0, c.size());
+
+ c = assoc.getRightsForLeft("l1");
+ assertNotNull(c);
+ assertEquals(1, c.size());
+ assertTrue(c.contains("r1"));
+
+ c = assoc.getLeftsForRight("r0");
+ assertNotNull(c);
+ assertEquals(0, c.size());
+
+ c = assoc.getLeftsForRight("r1");
+ assertNotNull(c);
+ assertEquals(1, c.size());
+ assertTrue(c.contains("l1"));
+
+ assoc.remove("l1", "r1");
+
+ c = assoc.getRightsForLeft("l0");
+ assertNotNull(c);
+ assertEquals(0, c.size());
+
+ c = assoc.getRightsForLeft("l1");
+ assertNotNull(c);
+ assertEquals(0, c.size());
+
+ c = assoc.getLeftsForRight("r0");
+ assertNotNull(c);
+ assertEquals(0, c.size());
+
+ c = assoc.getLeftsForRight("r1");
+ assertNotNull(c);
+ assertEquals(0, c.size());
+ }
+
+ public void testRemoveLeft() throws Exception {
+
+ Association assoc = new Association();
+
+ Collection c;
+
+ assoc.add("l0", "r0");
+ assoc.add("l0", "r1");
+ assoc.add("l1", "r0");
+ assoc.add("l1", "r1");
+
+
+ // These should be no-ops
+ assoc.removeLeft("r0");
+ assoc.removeLeft("r1");
+ assoc.removeRight("l0");
+ assoc.removeRight("l1");
+
+ c = assoc.getRightsForLeft("l0");
+ assertNotNull(c);
+ assertEquals(2, c.size());
+ assertTrue(c.contains("r0"));
+ assertTrue(c.contains("r1"));
+
+ c = assoc.getRightsForLeft("l1");
+ assertNotNull(c);
+ assertEquals(2, c.size());
+ assertTrue(c.contains("r0"));
+ assertTrue(c.contains("r1"));
+
+ c = assoc.getLeftsForRight("r0");
+ assertNotNull(c);
+ assertEquals(2, c.size());
+ assertTrue(c.contains("l0"));
+ assertTrue(c.contains("l1"));
+
+ c = assoc.getLeftsForRight("r1");
+ assertNotNull(c);
+ assertEquals(2, c.size());
+ assertTrue(c.contains("l0"));
+ assertTrue(c.contains("l1"));
+
+
+ assoc.removeLeft("l0");
+
+ c = assoc.getRightsForLeft("l0");
+ assertNotNull(c);
+ assertEquals(0, c.size());
+
+ c = assoc.getRightsForLeft("l1");
+ assertNotNull(c);
+ assertEquals(2, c.size());
+ assertTrue(c.contains("r0"));
+ assertTrue(c.contains("r1"));
+
+ c = assoc.getLeftsForRight("r0");
+ assertNotNull(c);
+ assertEquals(1, c.size());
+ assertTrue(c.contains("l1"));
+
+ c = assoc.getLeftsForRight("r1");
+ assertNotNull(c);
+ assertEquals(1, c.size());
+ assertTrue(c.contains("l1"));
+
+
+ assoc.removeLeft("l1");
+
+ c = assoc.getRightsForLeft("l0");
+ assertNotNull(c);
+ assertEquals(0, c.size());
+
+ c = assoc.getRightsForLeft("l1");
+ assertNotNull(c);
+ assertEquals(0, c.size());
+
+ c = assoc.getLeftsForRight("r0");
+ assertNotNull(c);
+ assertEquals(0, c.size());
+
+ c = assoc.getLeftsForRight("r1");
+ assertNotNull(c);
+ assertEquals(0, c.size());
+ }
+
+
+ public void testRemoveRight() throws Exception {
+
+ Association assoc = new Association();
+
+ Collection c;
+
+ assoc.add("l0", "r0");
+ assoc.add("l0", "r1");
+ assoc.add("l1", "r0");
+ assoc.add("l1", "r1");
+
+ assoc.removeRight("r0");
+
+ c = assoc.getRightsForLeft("l0");
+ assertNotNull(c);
+ assertEquals(1, c.size());
+ assertTrue(c.contains("r1"));
+
+ c = assoc.getRightsForLeft("l1");
+ assertNotNull(c);
+ assertEquals(1, c.size());
+ assertTrue(c.contains("r1"));
+
+ c = assoc.getLeftsForRight("r0");
+ assertNotNull(c);
+ assertEquals(0, c.size());
+
+ c = assoc.getLeftsForRight("r1");
+ assertNotNull(c);
+ assertEquals(2, c.size());
+ assertTrue(c.contains("l0"));
+ assertTrue(c.contains("l1"));
+
+
+ assoc.removeRight("r1");
+
+ c = assoc.getRightsForLeft("l0");
+ assertNotNull(c);
+ assertEquals(0, c.size());
+
+ c = assoc.getRightsForLeft("l1");
+ assertNotNull(c);
+ assertEquals(0, c.size());
+
+ c = assoc.getLeftsForRight("r0");
+ assertNotNull(c);
+ assertEquals(0, c.size());
+
+ c = assoc.getLeftsForRight("r1");
+ assertNotNull(c);
+ assertEquals(0, c.size());
+ }
+}
diff --git a/sourceediting/tests/org.eclipse.wst.xml.vex.ui.tests/src/org/eclipse/wst/xml/vex/ui/internal/tests/ResourceTrackerTest.java b/sourceediting/tests/org.eclipse.wst.xml.vex.ui.tests/src/org/eclipse/wst/xml/vex/ui/internal/tests/ResourceTrackerTest.java
new file mode 100644
index 0000000..676fd92
--- /dev/null
+++ b/sourceediting/tests/org.eclipse.wst.xml.vex.ui.tests/src/org/eclipse/wst/xml/vex/ui/internal/tests/ResourceTrackerTest.java
@@ -0,0 +1,23 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.ui.internal.tests;
+
+import junit.framework.TestCase;
+
+/**
+ * Test the ResourceTracker class.
+ */
+public class ResourceTrackerTest extends TestCase {
+
+ public void testAll() throws Exception {
+
+ }
+}
diff --git a/sourceediting/tests/org.eclipse.wst.xml.vex.ui.tests/src/org/eclipse/wst/xml/vex/ui/tests/Activator.java b/sourceediting/tests/org.eclipse.wst.xml.vex.ui.tests/src/org/eclipse/wst/xml/vex/ui/tests/Activator.java
new file mode 100644
index 0000000..dd66697
--- /dev/null
+++ b/sourceediting/tests/org.eclipse.wst.xml.vex.ui.tests/src/org/eclipse/wst/xml/vex/ui/tests/Activator.java
@@ -0,0 +1,50 @@
+package org.eclipse.wst.xml.vex.ui.tests;
+
+import org.eclipse.ui.plugin.AbstractUIPlugin;
+import org.osgi.framework.BundleContext;
+
+/**
+ * The activator class controls the plug-in life cycle
+ */
+public class Activator extends AbstractUIPlugin {
+
+ // The plug-in ID
+ public static final String PLUGIN_ID = "org.eclipse.wst.xml.vex.ui.tests";
+
+ // The shared instance
+ private static Activator plugin;
+
+ /**
+ * The constructor
+ */
+ public Activator() {
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.eclipse.ui.plugin.AbstractUIPlugin#start(org.osgi.framework.BundleContext)
+ */
+ public void start(BundleContext context) throws Exception {
+ super.start(context);
+ plugin = this;
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.eclipse.ui.plugin.AbstractUIPlugin#stop(org.osgi.framework.BundleContext)
+ */
+ public void stop(BundleContext context) throws Exception {
+ plugin = null;
+ super.stop(context);
+ }
+
+ /**
+ * Returns the shared instance
+ *
+ * @return the shared instance
+ */
+ public static Activator getDefault() {
+ return plugin;
+ }
+
+}