Skip to main content
aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorPierre-Charles David2015-03-18 09:09:26 +0000
committerPierre-Charles David2015-03-18 09:49:33 +0000
commit62d40e3ba733f3f3e5ee408640460d9bdf2ca71c (patch)
treee3c54d53aea7beaa9ea4f0c5c3dfae082924d976
parent70a0644d0f65d135d14b7b0712e5c93e3564f71f (diff)
downloadorg.eclipse.sirius-62d40e3ba733f3f3e5ee408640460d9bdf2ca71c.tar.gz
org.eclipse.sirius-62d40e3ba733f3f3e5ee408640460d9bdf2ca71c.tar.xz
org.eclipse.sirius-62d40e3ba733f3f3e5ee408640460d9bdf2ca71c.zip
[441554] Re-enable table printing using a private copy of Paperclips
To work around the absence of official release of Nebula Paperclips we could consume, integrate a copy of the code from org.eclipse.nebula.paperclips.core rebranded in an internal package of the org.eclipse.sirius.table.ui bundle. Original sources taken from http://git.eclipse.org/c/nebula/org.eclipse.nebula.git/tree/widgets/paperclips/org.eclipse.nebula.paperclips.core This allows us to re-integrate the actual "table printing" feature, see oes.table.ui.tools.internal.editor.action.PrintAction. See also http://dev.eclipse.org/ipzilla/show_bug.cgi?id=7371 for some more context. Bug: 441554 Change-Id: I243dc97d1c3271cba7b5237b0bc2c5bfc82d0299 Signed-off-by: Pierre-Charles David <pierre-charles.david@obeo.fr>
-rw-r--r--plugins/org.eclipse.sirius.doc/doc/Release Notes.html1
-rw-r--r--plugins/org.eclipse.sirius.doc/doc/Release Notes.textile1
-rw-r--r--plugins/org.eclipse.sirius.table.ui/.checkstyle1
-rw-r--r--plugins/org.eclipse.sirius.table.ui/.settings/org.eclipse.pde.prefs2
-rw-r--r--plugins/org.eclipse.sirius.table.ui/META-INF/MANIFEST.MF28
-rw-r--r--plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/editor/action/PrintAction.java88
-rw-r--r--plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/editor/print/PaperClipsPrintHelper.java159
-rw-r--r--plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/editor/provider/DTableActionBarContributor.java57
-rw-r--r--plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/AbstractIterator.java58
-rw-r--r--plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/AbstractPiece.java63
-rw-r--r--plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/AlignPrint.java189
-rw-r--r--plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/BackgroundPrint.java183
-rw-r--r--plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/BigPrint.java219
-rw-r--r--plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/BreakPrint.java79
-rw-r--r--plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/ColumnPrint.java345
-rw-r--r--plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/CompositeEntry.java54
-rw-r--r--plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/CompositePiece.java124
-rw-r--r--plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/DebugPrint.java90
-rw-r--r--plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/EmptyPrint.java143
-rw-r--r--plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/ImagePrint.java256
-rw-r--r--plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/LayerEntry.java44
-rw-r--r--plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/LayerEntryIterator.java14
-rw-r--r--plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/LayerPrint.java101
-rw-r--r--plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/LinePrint.java268
-rw-r--r--plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/Margins.java95
-rw-r--r--plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/Messages.java49
-rw-r--r--plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/NoBreakPrint.java120
-rw-r--r--plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/NullPrintPiece.java27
-rw-r--r--plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/PageEnumeration.java113
-rw-r--r--plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/PaperClips.java507
-rw-r--r--plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/Print.java36
-rw-r--r--plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/PrintIterator.java111
-rw-r--r--plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/PrintJob.java170
-rw-r--r--plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/PrintPiece.java50
-rw-r--r--plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/RotatePrint.java210
-rw-r--r--plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/ScalePrint.java262
-rw-r--r--plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/SeriesPrint.java164
-rw-r--r--plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/SidewaysPrint.java214
-rw-r--r--plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/border/AbstractBorderPainter.java71
-rw-r--r--plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/border/Border.java34
-rw-r--r--plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/border/BorderPainter.java133
-rw-r--r--plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/border/BorderPrint.java93
-rw-r--r--plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/border/GapBorder.java178
-rw-r--r--plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/border/LineBorder.java212
-rw-r--r--plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/border/internal/BorderIterator.java149
-rw-r--r--plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/border/internal/BorderPiece.java58
-rw-r--r--plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/grid/BasicGridLookPainter.java250
-rw-r--r--plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/grid/CellBackgroundProvider.java36
-rw-r--r--plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/grid/DefaultCellBackgroundProvider.java109
-rw-r--r--plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/grid/DefaultGridLook.java454
-rw-r--r--plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/grid/GridCell.java60
-rw-r--r--plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/grid/GridColumn.java331
-rw-r--r--plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/grid/GridLook.java32
-rw-r--r--plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/grid/GridLookPainter.java89
-rw-r--r--plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/grid/GridMargins.java112
-rw-r--r--plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/grid/GridPrint.java1033
-rw-r--r--plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/grid/internal/DefaultGridLookPainter.java141
-rw-r--r--plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/grid/internal/DefaultGridMargins.java86
-rw-r--r--plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/grid/internal/GridCellImpl.java159
-rw-r--r--plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/grid/internal/GridCellIterator.java57
-rw-r--r--plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/grid/internal/GridIterator.java1040
-rw-r--r--plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/grid/internal/GridLookPainterPiece.java122
-rw-r--r--plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/internal/LayerEntryImpl.java95
-rw-r--r--plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/internal/LayerEntryIteratorImpl.java48
-rw-r--r--plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/internal/LayerIterator.java125
-rw-r--r--plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/internal/RotatePiece.java101
-rw-r--r--plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/internal/piece/EmptyPiece.java45
-rw-r--r--plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/internal/util/PaperClipsUtil.java231
-rw-r--r--plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/internal/util/PrintSizeStrategy.java49
-rw-r--r--plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/internal/util/ResourcePool.java109
-rw-r--r--plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/internal/util/SWTUtil.java187
-rw-r--r--plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/internal/util/Util.java170
-rw-r--r--plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/messages.properties1
-rw-r--r--plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/messages_de.properties1
-rw-r--r--plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/messages_en.properties1
-rw-r--r--plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/messages_fr.properties1
-rw-r--r--plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/package.html6
-rw-r--r--plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/page/DefaultPageNumberFormat.java42
-rw-r--r--plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/page/PageDecoration.java35
-rw-r--r--plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/page/PageNumber.java35
-rw-r--r--plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/page/PageNumberFormat.java27
-rw-r--r--plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/page/PageNumberPageDecoration.java183
-rw-r--r--plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/page/PageNumberPrint.java443
-rw-r--r--plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/page/PagePrint.java551
-rw-r--r--plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/page/SimplePageDecoration.java67
-rw-r--r--plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/text/LineBreakPrint.java124
-rw-r--r--plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/text/StyledTextPrint.java295
-rw-r--r--plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/text/TextPrint.java562
-rw-r--r--plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/text/TextStyle.java416
-rw-r--r--plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/text/internal/TextPiece.java140
-rw-r--r--plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/text/internal/TextPrintPiece.java22
91 files changed, 13822 insertions, 24 deletions
diff --git a/plugins/org.eclipse.sirius.doc/doc/Release Notes.html b/plugins/org.eclipse.sirius.doc/doc/Release Notes.html
index de75676ed2..d6788a2796 100644
--- a/plugins/org.eclipse.sirius.doc/doc/Release Notes.html
+++ b/plugins/org.eclipse.sirius.doc/doc/Release Notes.html
@@ -44,6 +44,7 @@
<h3 id="UserVisibleChanges">User-Visible Changes</h3>
<ul>
<li>In the Viewpoint Selection Dialog and ViewpointsSelectionWizardPage, the &#171;plug-in&#187; decorator for viewpoints loaded from plug-ins is removed, and a &#171;Folder&#187; decorator for viewpoints loaded from the current workspace has been added on the top of the default viewpoint icon.</li>
+ <li>The ability to print table representations has been re-introduced.</li>
</ul>
<h3 id="SpecifierVisibleChanges">Specifier-Visible Changes</h3>
<ul>
diff --git a/plugins/org.eclipse.sirius.doc/doc/Release Notes.textile b/plugins/org.eclipse.sirius.doc/doc/Release Notes.textile
index b3680e9ff8..661d1cd961 100644
--- a/plugins/org.eclipse.sirius.doc/doc/Release Notes.textile
+++ b/plugins/org.eclipse.sirius.doc/doc/Release Notes.textile
@@ -9,6 +9,7 @@ h2(#sirius3.0.0). Changes in Sirius 3.0.0 (from Sirius 2.0.0)
h3. User-Visible Changes
* In the Viewpoint Selection Dialog and ViewpointsSelectionWizardPage, the "plug-in" decorator for viewpoints loaded from plug-ins is removed, and a "Folder" decorator for viewpoints loaded from the current workspace has been added on the top of the default viewpoint icon.
+* The ability to print table representations has been re-introduced.
h3. Specifier-Visible Changes
diff --git a/plugins/org.eclipse.sirius.table.ui/.checkstyle b/plugins/org.eclipse.sirius.table.ui/.checkstyle
index 3c756ff819..b753aac46d 100644
--- a/plugins/org.eclipse.sirius.table.ui/.checkstyle
+++ b/plugins/org.eclipse.sirius.table.ui/.checkstyle
@@ -9,5 +9,6 @@
</fileset>
<filter name="FilesFromPackage" enabled="true">
<filter-data value="src-gen"/>
+ <filter-data value="src/org/eclipse/sirius/table/ui/tools/internal/paperclips"/>
</filter>
</fileset-config>
diff --git a/plugins/org.eclipse.sirius.table.ui/.settings/org.eclipse.pde.prefs b/plugins/org.eclipse.sirius.table.ui/.settings/org.eclipse.pde.prefs
index 740eeeb99a..a1c0f1e991 100644
--- a/plugins/org.eclipse.sirius.table.ui/.settings/org.eclipse.pde.prefs
+++ b/plugins/org.eclipse.sirius.table.ui/.settings/org.eclipse.pde.prefs
@@ -13,7 +13,7 @@ compilers.p.build.src.includes=0
compilers.p.deprecated=1
compilers.p.discouraged-class=1
compilers.p.internal=0
-compilers.p.missing-packages=0
+compilers.p.missing-packages=1
compilers.p.missing-version-export-package=1
compilers.p.missing-version-import-package=1
compilers.p.missing-version-require-bundle=0
diff --git a/plugins/org.eclipse.sirius.table.ui/META-INF/MANIFEST.MF b/plugins/org.eclipse.sirius.table.ui/META-INF/MANIFEST.MF
index 72f2145966..aa54181d24 100644
--- a/plugins/org.eclipse.sirius.table.ui/META-INF/MANIFEST.MF
+++ b/plugins/org.eclipse.sirius.table.ui/META-INF/MANIFEST.MF
@@ -11,21 +11,21 @@ Bundle-RequiredExecutionEnvironment: JavaSE-1.6
Export-Package: org.eclipse.sirius.table.metamodel.table.description.provider;version="2.0.2",
org.eclipse.sirius.table.metamodel.table.provider;version="3.0.0",
org.eclipse.sirius.table.ui.business.api.helper;version="2.0.2",
- org.eclipse.sirius.table.ui.business.internal.dialect;x-internal:=true;version="2.1.0",
+ org.eclipse.sirius.table.ui.business.internal.dialect;version="2.1.0";x-internal:=true,
org.eclipse.sirius.table.ui.tools.api.editor;version="2.0.2",
- org.eclipse.sirius.table.ui.tools.internal.commands;x-internal:=true;version="2.0.2",
- org.eclipse.sirius.table.ui.tools.internal.editor;x-internal:=true;version="2.0.2",
- org.eclipse.sirius.table.ui.tools.internal.editor.action;x-internal:=true;version="2.1.0",
- org.eclipse.sirius.table.ui.tools.internal.editor.listeners;x-internal:=true;version="2.0.2",
- org.eclipse.sirius.table.ui.tools.internal.editor.print;x-internal:=true;version="2.0.2",
- org.eclipse.sirius.table.ui.tools.internal.editor.provider;x-internal:=true;version="2.0.2",
- org.eclipse.sirius.table.ui.tools.internal.editor.utils;x-internal:=true;version="2.0.2",
- org.eclipse.sirius.table.ui.tools.internal.export;x-internal:=true;version="2.0.2",
- org.eclipse.sirius.table.ui.tools.internal.properties.propertysource;x-internal:=true;version="2.0.2",
- org.eclipse.sirius.table.ui.tools.internal.properties.section.common;x-internal:=true;version="2.0.2",
- org.eclipse.sirius.table.ui.tools.internal.properties.section.core;x-internal:=true;version="2.0.2",
- org.eclipse.sirius.table.ui.tools.internal.properties.section.semantic;x-internal:=true;version="2.0.2",
- org.eclipse.sirius.table.ui.tools.internal.properties.section.style;x-internal:=true;version="2.0.2"
+ org.eclipse.sirius.table.ui.tools.internal.commands;version="2.0.2";x-internal:=true,
+ org.eclipse.sirius.table.ui.tools.internal.editor;version="2.0.2";x-internal:=true,
+ org.eclipse.sirius.table.ui.tools.internal.editor.action;version="2.1.0";x-internal:=true,
+ org.eclipse.sirius.table.ui.tools.internal.editor.listeners;version="2.0.2";x-internal:=true,
+ org.eclipse.sirius.table.ui.tools.internal.editor.print;version="2.0.2";x-internal:=true,
+ org.eclipse.sirius.table.ui.tools.internal.editor.provider;version="2.0.2";x-internal:=true,
+ org.eclipse.sirius.table.ui.tools.internal.editor.utils;version="2.0.2";x-internal:=true,
+ org.eclipse.sirius.table.ui.tools.internal.export;version="2.0.2";x-internal:=true,
+ org.eclipse.sirius.table.ui.tools.internal.properties.propertysource;version="2.0.2";x-internal:=true,
+ org.eclipse.sirius.table.ui.tools.internal.properties.section.common;version="2.0.2";x-internal:=true,
+ org.eclipse.sirius.table.ui.tools.internal.properties.section.core;version="2.0.2";x-internal:=true,
+ org.eclipse.sirius.table.ui.tools.internal.properties.section.semantic;version="2.0.2";x-internal:=true,
+ org.eclipse.sirius.table.ui.tools.internal.properties.section.style;version="2.0.2";x-internal:=true
Require-Bundle: org.eclipse.core.runtime;bundle-version="3.8.0",
org.eclipse.sirius.table;bundle-version="2.0.0";visibility:=reexport,
org.eclipse.emf.edit;bundle-version="2.8.0";visibility:=reexport,
diff --git a/plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/editor/action/PrintAction.java b/plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/editor/action/PrintAction.java
new file mode 100644
index 0000000000..3b13a8a862
--- /dev/null
+++ b/plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/editor/action/PrintAction.java
@@ -0,0 +1,88 @@
+/*******************************************************************************
+ * Copyright (c) 2011 THALES GLOBAL SERVICES.
+ * 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:
+ * Obeo - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.sirius.table.ui.tools.internal.editor.action;
+
+import org.eclipse.jface.action.Action;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.SWTError;
+import org.eclipse.swt.printing.PrintDialog;
+import org.eclipse.swt.printing.PrinterData;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.swt.widgets.Display;
+import org.eclipse.swt.widgets.MessageBox;
+import org.eclipse.swt.widgets.Shell;
+import org.eclipse.ui.IEditorPart;
+
+import org.eclipse.sirius.table.ui.tools.internal.editor.AbstractDTableEditor;
+import org.eclipse.sirius.table.ui.tools.internal.editor.print.PaperClipsPrintHelper;
+import org.eclipse.sirius.table.ui.tools.internal.editor.print.PrintHelper;
+
+/**
+ * An action to print tables.
+ *
+ * @author mchauvin
+ */
+public class PrintAction extends Action {
+
+ private IEditorPart editorPart;
+
+ private Control controlToPrint;
+
+ /**
+ * Set the active editor part.
+ *
+ * @param editor
+ * the current active editor part
+ */
+ public void setEditor(final IEditorPart editor) {
+ this.editorPart = editor;
+ }
+
+ @Override
+ public void run() {
+ if (editorPart instanceof AbstractDTableEditor) {
+ controlToPrint = ((AbstractDTableEditor) editorPart).getTableViewer().getControl();
+ if (controlToPrint != null) {
+ final Display display = controlToPrint.getDisplay();
+ final Shell shell = display.getActiveShell();
+ final String tableName = editorPart.getTitle() != null ? editorPart.getTitle() : "table without name";
+ try {
+ final PrinterData data = openPrintDialog(shell);
+ if (data == null) {
+ return;
+ }
+ launchPrintWithPaperclip(data, tableName);
+ } catch (final SWTError e) {
+ final MessageBox box = new MessageBox(shell, SWT.ICON_ERROR);
+ box.setMessage("An error occurs during printing : " + e.getMessage());
+ box.open();
+ }
+ }
+ }
+ }
+
+ private PrinterData openPrintDialog(final Shell shell) {
+ final PrintDialog dialog = new PrintDialog(shell, SWT.NULL);
+ final PrinterData printerData = dialog.open();
+ return printerData;
+ }
+
+ private void launchPrintWithPaperclip(final PrinterData data, final String tableName) {
+ final PrintHelper helper = new PaperClipsPrintHelper(data, controlToPrint);
+ launch(helper, tableName);
+ }
+
+ private void launch(final PrintHelper helper, final String tableName) {
+ helper.launchPrintJob(tableName);
+ helper.dispose();
+ }
+
+}
diff --git a/plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/editor/print/PaperClipsPrintHelper.java b/plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/editor/print/PaperClipsPrintHelper.java
new file mode 100644
index 0000000000..f6ce88318e
--- /dev/null
+++ b/plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/editor/print/PaperClipsPrintHelper.java
@@ -0,0 +1,159 @@
+/*******************************************************************************
+ * Copyright (c) 2010 THALES GLOBAL SERVICES.
+ * 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:
+ * Obeo - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.sirius.table.ui.tools.internal.editor.print;
+
+import org.eclipse.sirius.table.ui.tools.internal.paperclips.ImagePrint;
+import org.eclipse.sirius.table.ui.tools.internal.paperclips.LinePrint;
+import org.eclipse.sirius.table.ui.tools.internal.paperclips.PaperClips;
+import org.eclipse.sirius.table.ui.tools.internal.paperclips.Print;
+import org.eclipse.sirius.table.ui.tools.internal.paperclips.PrintJob;
+import org.eclipse.sirius.table.ui.tools.internal.paperclips.ScalePrint;
+import org.eclipse.sirius.table.ui.tools.internal.paperclips.border.LineBorder;
+import org.eclipse.sirius.table.ui.tools.internal.paperclips.grid.DefaultGridLook;
+import org.eclipse.sirius.table.ui.tools.internal.paperclips.grid.GridColumn;
+import org.eclipse.sirius.table.ui.tools.internal.paperclips.grid.GridPrint;
+import org.eclipse.sirius.table.ui.tools.internal.paperclips.page.PageDecoration;
+import org.eclipse.sirius.table.ui.tools.internal.paperclips.page.PageNumberPageDecoration;
+import org.eclipse.sirius.table.ui.tools.internal.paperclips.page.PagePrint;
+import org.eclipse.sirius.table.ui.tools.internal.paperclips.page.SimplePageDecoration;
+import org.eclipse.sirius.table.ui.tools.internal.paperclips.text.TextPrint;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.graphics.Image;
+import org.eclipse.swt.graphics.RGB;
+import org.eclipse.swt.printing.PrinterData;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.swt.widgets.Tree;
+import org.eclipse.swt.widgets.TreeColumn;
+import org.eclipse.swt.widgets.TreeItem;
+
+/**
+ * An helper to handle print with paperclips library.
+ *
+ * @author mchauvin
+ */
+public class PaperClipsPrintHelper implements PrintHelper {
+
+ private final PrinterData printerData;
+
+ private final Control control;
+
+ /**
+ * Create a new PaperClips helper.
+ *
+ * @param printerData
+ * the printer data
+ * @param control
+ * the control to print
+ */
+ public PaperClipsPrintHelper(final PrinterData printerData, final Control control) {
+ this.printerData = printerData;
+ this.control = control;
+ }
+
+ @Override
+ public void launchPrintJob(final String name) {
+ Print print = createPrint(name);
+ PaperClips.print(new PrintJob(name, print).setMargins(72), printerData);
+ }
+
+ private Print createPrint(final String name) {
+ Print body = getBody();
+ PageDecoration header = getHeader(name);
+ PageDecoration footer = getFooter();
+ return new PagePrint(header, body, footer);
+ }
+
+ private Print getBody() {
+ final Tree tree = findTree();
+
+ /* Create GridPrint with all columns at default size. */
+ final DefaultGridLook look = new DefaultGridLook();
+ look.setCellBorder(new LineBorder());
+ final RGB background = tree.getDisplay().getSystemColor(SWT.COLOR_WIDGET_BACKGROUND).getRGB();
+ look.setHeaderBackground(background);
+ look.setFooterBackground(background);
+
+ final GridPrint grid = new GridPrint(look);
+
+ /* Add header and footer to match table column names. */
+
+ final TreeColumn[] columns = tree.getColumns();
+ for (final TreeColumn col : columns) {
+ /*
+ * Add the column to the grid with alignment applied, default width,
+ * no weight
+ */
+ grid.addColumn(new GridColumn(col.getAlignment(), SWT.DEFAULT, 0));
+
+ final Print cell = createCell("", col.getImage(), col.getText(), SWT.CENTER);
+ grid.addHeader(cell);
+ }
+
+ /* Add content rows */
+ createTreeItemRows("", grid, tree.getItems(), columns);
+ return new ScalePrint(grid);
+
+ }
+
+ private Tree findTree() {
+ Tree tree = null;
+ if (control instanceof Tree) {
+ tree = (Tree) control;
+ } else if (control instanceof Composite && ((Composite) control).getChildren()[0] instanceof Tree) {
+ tree = (Tree) ((Composite) control).getChildren()[0];
+ }
+ return tree;
+ }
+
+ private void createTreeItemRows(final String offset, final GridPrint grid, final TreeItem[] items, final TreeColumn[] columns) {
+ for (TreeItem item : items) {
+ createTreeItemrow(offset, grid, item, columns);
+ }
+ }
+
+ private void createTreeItemrow(final String offset, final GridPrint grid, final TreeItem item, final TreeColumn[] columns) {
+ for (int i = 0; i < columns.length; i++) {
+ grid.add(createCell(offset, item.getImage(i), item.getText(i), columns[i].getAlignment()));
+ }
+ final String newOffset = offset + " ";
+ createTreeItemRows(newOffset, grid, item.getItems(), columns);
+ }
+
+ private Print createCell(final String offset, final Image image, final String text, final int align) {
+ if (image == null) {
+ return new TextPrint(text, align);
+ }
+
+ final GridPrint grid = new GridPrint("p, p, d");
+ grid.add(new TextPrint(offset));
+ grid.add(new ImagePrint(image.getImageData(), image.getDevice().getDPI()));
+ grid.add(new TextPrint(text, align));
+ return grid;
+ }
+
+ private PageDecoration getHeader(final String name) {
+ final GridPrint headerGrid = new GridPrint("d:g");
+ headerGrid.add(new TextPrint(name));
+ headerGrid.add(new LinePrint(SWT.HORIZONTAL), GridPrint.REMAINDER);
+ return new SimplePageDecoration(headerGrid);
+ }
+
+ private PageDecoration getFooter() {
+ return new PageNumberPageDecoration();
+ }
+
+ @Override
+ public void dispose() {
+ // do nothing
+ }
+
+}
diff --git a/plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/editor/provider/DTableActionBarContributor.java b/plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/editor/provider/DTableActionBarContributor.java
index c5e9794ba2..8977f54a9c 100644
--- a/plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/editor/provider/DTableActionBarContributor.java
+++ b/plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/editor/provider/DTableActionBarContributor.java
@@ -12,15 +12,22 @@ package org.eclipse.sirius.table.ui.tools.internal.editor.provider;
import org.eclipse.emf.edit.ui.action.EditingDomainActionBarContributor;
import org.eclipse.jface.action.IToolBarManager;
+import org.eclipse.ui.IActionBars;
+import org.eclipse.ui.IEditorActionBarContributor;
+import org.eclipse.ui.IEditorPart;
+import org.eclipse.ui.ISharedImages;
+import org.eclipse.ui.IWorkbenchActionConstants;
+import org.eclipse.ui.PlatformUI;
+import org.eclipse.ui.actions.ActionFactory;
+import org.eclipse.ui.part.IPage;
+
import org.eclipse.sirius.table.ui.tools.internal.editor.AbstractDTableEditor;
import org.eclipse.sirius.table.ui.tools.internal.editor.DTableCrossEditor;
import org.eclipse.sirius.table.ui.tools.internal.editor.DTableViewerManager;
import org.eclipse.sirius.table.ui.tools.internal.editor.action.EditorCreateLineMenuAction;
import org.eclipse.sirius.table.ui.tools.internal.editor.action.EditorCreateTargetColumnMenuAction;
+import org.eclipse.sirius.table.ui.tools.internal.editor.action.PrintAction;
import org.eclipse.sirius.ui.tools.internal.editor.AbstractDTableViewerManager;
-import org.eclipse.ui.IEditorActionBarContributor;
-import org.eclipse.ui.IEditorPart;
-import org.eclipse.ui.IWorkbenchActionConstants;
/**
* This is a contributor for an DTable editor.
@@ -28,11 +35,44 @@ import org.eclipse.ui.IWorkbenchActionConstants;
* @author <a href="mailto:laurent.redor@obeo.fr">Laurent Redor</a>
*/
public class DTableActionBarContributor extends EditingDomainActionBarContributor {
- /**
- * {@inheritDoc}
- *
- * @see org.eclipse.emf.edit.ui.action.EditingDomainActionBarContributor#setActiveEditor(org.eclipse.ui.IEditorPart)
- */
+
+ /* ISharedImages.IMG_ETOOL_PRINT_EDIT only since 3.4 */
+ private static final String IMG_ETOOL_PRINT_EDIT = "IMG_ETOOL_PRINT_EDIT";
+
+ private PrintAction printAction;
+
+ @Override
+ public void init(final IActionBars actionBars) {
+ super.init(actionBars);
+ final ISharedImages sharedImages = PlatformUI.getWorkbench().getSharedImages();
+ printAction = new PrintAction();
+ printAction.setImageDescriptor(sharedImages.getImageDescriptor(IMG_ETOOL_PRINT_EDIT));
+ actionBars.setGlobalActionHandler(ActionFactory.PRINT.getId(), printAction);
+ }
+
+ @Override
+ public void shareGlobalActions(final IPage page, final IActionBars actionBars) {
+ super.shareGlobalActions(page, actionBars);
+ actionBars.setGlobalActionHandler(ActionFactory.PRINT.getId(), printAction);
+ }
+
+ @Override
+ public void deactivate() {
+ super.deactivate();
+ printAction.setEditor(null);
+ }
+
+ @Override
+ public void activate() {
+ super.activate();
+ printAction.setEditor(activeEditor);
+ }
+
+ @Override
+ public void contributeToToolBar(final IToolBarManager toolBarManager) {
+ super.contributeToToolBar(toolBarManager);
+ }
+
@Override
public void setActiveEditor(IEditorPart part) {
boolean updateCreateMenus = part != activeEditor && part != null;
@@ -84,5 +124,4 @@ public class DTableActionBarContributor extends EditingDomainActionBarContributo
toolBarManager.appendToGroup(IWorkbenchActionConstants.MB_ADDITIONS, editorCreateTargetColumnMenuAction);
toolBarManager.update(true);
}
-
}
diff --git a/plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/AbstractIterator.java b/plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/AbstractIterator.java
new file mode 100644
index 0000000000..792049dd4f
--- /dev/null
+++ b/plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/AbstractIterator.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (c) 2005 Matthew Hall 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:
+ * Matthew Hall - initial API and implementation
+ */
+package org.eclipse.sirius.table.ui.tools.internal.paperclips;
+
+import org.eclipse.sirius.table.ui.tools.internal.paperclips.internal.util.Util;
+import org.eclipse.swt.graphics.Device;
+import org.eclipse.swt.graphics.GC;
+
+/**
+ * An abstract PrintIterator class which maintains references to the device and
+ * gc arguments passed to {@link Print#iterator(Device, GC) }.
+ *
+ * @author Matthew Hall
+ */
+public abstract class AbstractIterator implements PrintIterator {
+ /**
+ * The device being printed to.
+ */
+ protected final Device device;
+
+ /**
+ * A GC used for measuring document elements.
+ */
+ protected final GC gc;
+
+ /**
+ * Constructs an AbstractIterator with the given Device and GC.
+ *
+ * @param device
+ * the device being printed to.
+ * @param gc
+ * a GC used for drawing on the print device.
+ */
+ protected AbstractIterator(Device device, GC gc) {
+ Util.notNull(device, gc);
+ this.device = device;
+ this.gc = gc;
+ }
+
+ /**
+ * Copy constructor.
+ *
+ * @param that
+ * the AbstractIterator being copied.
+ */
+ protected AbstractIterator(AbstractIterator that) {
+ this.device = that.device;
+ this.gc = that.gc;
+ }
+} \ No newline at end of file
diff --git a/plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/AbstractPiece.java b/plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/AbstractPiece.java
new file mode 100644
index 0000000000..29c277106c
--- /dev/null
+++ b/plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/AbstractPiece.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (c) 2005 Matthew Hall 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:
+ * Matthew Hall - initial API and implementation
+ */
+package org.eclipse.sirius.table.ui.tools.internal.paperclips;
+
+import org.eclipse.sirius.table.ui.tools.internal.paperclips.internal.util.Util;
+import org.eclipse.swt.graphics.Device;
+import org.eclipse.swt.graphics.GC;
+import org.eclipse.swt.graphics.Point;
+
+/**
+ * An abstract PrintPiece class.
+ *
+ * @author Matthew Hall
+ */
+public abstract class AbstractPiece implements PrintPiece {
+ /**
+ * The device being printed to.
+ */
+ protected final Device device;
+
+ private final Point size;
+
+ /**
+ * Constructs an AbstractPiece.
+ *
+ * @param device
+ * the device being printed to.
+ * @param gc
+ * a GC for drawing on the print device.
+ * @param size
+ * the value to be returned by getSize().
+ */
+ protected AbstractPiece(Device device, GC gc, Point size) {
+ Util.notNull(device, gc, size);
+ this.device = device;
+ this.size = size;
+ }
+
+ /**
+ * Constructos an AbstractPiece.
+ *
+ * @param iter
+ * an AbstractIterator containing references to a Device and GC
+ * which will be used for printing.
+ * @param size
+ * the value to be returned by getSize().
+ */
+ protected AbstractPiece(AbstractIterator iter, Point size) {
+ this(iter.device, iter.gc, size);
+ }
+
+ public final Point getSize() {
+ return new Point(size.x, size.y);
+ }
+}
diff --git a/plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/AlignPrint.java b/plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/AlignPrint.java
new file mode 100644
index 0000000000..77c6f85736
--- /dev/null
+++ b/plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/AlignPrint.java
@@ -0,0 +1,189 @@
+/*
+ * Copyright (c) 2005 Matthew Hall 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:
+ * Matthew Hall - initial API and implementation
+ */
+package org.eclipse.sirius.table.ui.tools.internal.paperclips;
+
+import org.eclipse.sirius.table.ui.tools.internal.paperclips.internal.util.Util;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.graphics.Device;
+import org.eclipse.swt.graphics.GC;
+import org.eclipse.swt.graphics.Point;
+
+/**
+ * A wrapper print that aligns its target vertically and/or horizontally. An
+ * AlignPrint is vertically greedy when the vertical alignment is SWT.CENTER or
+ * SWT.BOTTOM, and horizontally greedy when the horizontal alignment is
+ * SWT.CENTER and SWT.RIGHT.
+ *
+ * @author Matthew Hall
+ */
+public class AlignPrint implements Print {
+ private static final int DEFAULT_HORIZONTAL_ALIGN = SWT.LEFT;
+ private static final int DEFAULT_VERTICAL_ALIGN = SWT.TOP;
+
+ final Print target;
+ final int hAlign;
+ final int vAlign;
+
+ /**
+ * Constructs a new AlignPrint.
+ *
+ * @param target
+ * the print being aligned.
+ * @param hAlign
+ * the horizontal alignment. One of SWT.LEFT, SWT.CENTER,
+ * SWT.RIGHT, or SWT.DEFAULT.
+ * @param vAlign
+ * the vertical alignment. One of SWT.TOP, SWT.CENTER,
+ * SWT.BOTTOM, or SWT.DEFAULT.
+ */
+ public AlignPrint(Print target, int hAlign, int vAlign) {
+ Util.notNull(target);
+ this.target = target;
+ this.hAlign = checkHAlign(hAlign);
+ this.vAlign = checkVAlign(vAlign);
+ }
+
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + hAlign;
+ result = prime * result + ((target == null) ? 0 : target.hashCode());
+ result = prime * result + vAlign;
+ return result;
+ }
+
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (obj == null)
+ return false;
+ if (getClass() != obj.getClass())
+ return false;
+ AlignPrint other = (AlignPrint) obj;
+ if (hAlign != other.hAlign)
+ return false;
+ if (target == null) {
+ if (other.target != null)
+ return false;
+ } else if (!target.equals(other.target))
+ return false;
+ if (vAlign != other.vAlign)
+ return false;
+ return true;
+ }
+
+ /**
+ * Returns the wrapped print being aligned
+ *
+ * @return the wrapped print being aligned
+ */
+ public Print getTarget() {
+ return target;
+ }
+
+ /**
+ * Returns a Point with the x and y fields set to the horizontal and
+ * vertical alignment, respectively.
+ *
+ * @return a Point with the x and y fields set to the horizontal and
+ * vertical alignment, respectively.
+ */
+ public Point getAlignment() {
+ return new Point(hAlign, vAlign);
+ }
+
+ private static int checkHAlign(int hAlign) {
+ if (hAlign == SWT.LEFT || hAlign == SWT.CENTER || hAlign == SWT.RIGHT)
+ return hAlign;
+ if (hAlign == SWT.DEFAULT)
+ return DEFAULT_HORIZONTAL_ALIGN;
+ PaperClips.error(SWT.ERROR_INVALID_ARGUMENT,
+ "hAlign must be one of SWT.LEFT, SWT.CENTER or SWT.RIGHT"); //$NON-NLS-1$
+ return hAlign;
+ }
+
+ private static int checkVAlign(int vAlign) {
+ if (vAlign == SWT.TOP || vAlign == SWT.CENTER || vAlign == SWT.BOTTOM)
+ return vAlign;
+ if (vAlign == SWT.DEFAULT)
+ return DEFAULT_VERTICAL_ALIGN;
+ PaperClips.error(SWT.ERROR_INVALID_ARGUMENT,
+ "vAlign must be one of SWT.TOP, SWT.CENTER or SWT.BOTTOM"); //$NON-NLS-1$
+ return vAlign;
+ }
+
+ public PrintIterator iterator(Device device, GC gc) {
+ return new AlignIterator(this, device, gc);
+ }
+}
+
+class AlignIterator implements PrintIterator {
+ private final PrintIterator target;
+ private final int hAlign;
+ private final int vAlign;
+
+ AlignIterator(AlignPrint print, Device device, GC gc) {
+ this.target = print.target.iterator(device, gc);
+ this.hAlign = print.hAlign;
+ this.vAlign = print.vAlign;
+ }
+
+ AlignIterator(AlignIterator that) {
+ this.target = that.target.copy();
+ this.hAlign = that.hAlign;
+ this.vAlign = that.vAlign;
+ }
+
+ public boolean hasNext() {
+ return target.hasNext();
+ }
+
+ public Point minimumSize() {
+ return target.minimumSize();
+ }
+
+ public Point preferredSize() {
+ return target.preferredSize();
+ }
+
+ public PrintPiece next(int width, int height) {
+ PrintPiece piece = PaperClips.next(target, width, height);
+ if (piece == null)
+ return null;
+
+ Point size = piece.getSize();
+ Point offset = new Point(0, 0);
+
+ if (hAlign == SWT.CENTER)
+ offset.x = (width - size.x) / 2;
+ else if (hAlign == SWT.RIGHT)
+ offset.x = width - size.x;
+
+ if (hAlign != SWT.LEFT)
+ size.x = width;
+
+ if (vAlign == SWT.CENTER)
+ offset.y = (height - size.y) / 2;
+ else if (vAlign == SWT.BOTTOM)
+ offset.y = height - size.y;
+
+ if (vAlign != SWT.TOP)
+ size.y = height;
+
+ CompositeEntry entry = new CompositeEntry(piece, offset);
+
+ return new CompositePiece(new CompositeEntry[] { entry }, size);
+ }
+
+ public PrintIterator copy() {
+ return new AlignIterator(this);
+ }
+} \ No newline at end of file
diff --git a/plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/BackgroundPrint.java b/plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/BackgroundPrint.java
new file mode 100644
index 0000000000..e62e4f267c
--- /dev/null
+++ b/plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/BackgroundPrint.java
@@ -0,0 +1,183 @@
+/*
+ * Copyright (c) 2006 Matthew Hall 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:
+ * Matthew Hall - initial API and implementation
+ */
+package org.eclipse.sirius.table.ui.tools.internal.paperclips;
+
+import org.eclipse.sirius.table.ui.tools.internal.paperclips.internal.util.ResourcePool;
+import org.eclipse.sirius.table.ui.tools.internal.paperclips.internal.util.Util;
+import org.eclipse.swt.graphics.Color;
+import org.eclipse.swt.graphics.Device;
+import org.eclipse.swt.graphics.GC;
+import org.eclipse.swt.graphics.Point;
+import org.eclipse.swt.graphics.RGB;
+
+/**
+ * A decorator that paints a background color behind it's target.
+ *
+ * @author Matthew Hall
+ */
+public class BackgroundPrint implements Print {
+ Print target;
+ RGB background;
+
+ /**
+ * Constructs a BackgroundPrint with the given target and background color.
+ *
+ * @param target
+ * the
+ * @param background
+ */
+ public BackgroundPrint(Print target, RGB background) {
+ Util.notNull(target, background);
+ this.target = target;
+ this.background = background;
+ }
+
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result
+ + ((background == null) ? 0 : background.hashCode());
+ result = prime * result + ((target == null) ? 0 : target.hashCode());
+ return result;
+ }
+
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (obj == null)
+ return false;
+ if (getClass() != obj.getClass())
+ return false;
+ BackgroundPrint other = (BackgroundPrint) obj;
+ if (background == null) {
+ if (other.background != null)
+ return false;
+ } else if (!background.equals(other.background))
+ return false;
+ if (target == null) {
+ if (other.target != null)
+ return false;
+ } else if (!target.equals(other.target))
+ return false;
+ return true;
+ }
+
+ /**
+ * Returns the wrapped print to which the background color is being applied.
+ *
+ * @return the wrapped print to which the background color is being applied.
+ */
+ public Print getTarget() {
+ return target;
+ }
+
+ /**
+ * Returns the background color.
+ *
+ * @return the background color.
+ */
+ public RGB getBackground() {
+ return background;
+ }
+
+ /**
+ * Sets the background color.
+ *
+ * @param background
+ * the new background color.
+ */
+ public void setBackground(RGB background) {
+ Util.notNull(background);
+ this.background = background;
+ }
+
+ public PrintIterator iterator(Device device, GC gc) {
+ return new BackgroundIterator(this, device, gc);
+ }
+}
+
+class BackgroundIterator implements PrintIterator {
+ private final PrintIterator target;
+ private final RGB background;
+ private final Device device;
+
+ BackgroundIterator(BackgroundPrint print, Device device, GC gc) {
+ Util.notNull(print, device, gc);
+ this.device = device;
+ this.target = print.target.iterator(device, gc);
+ this.background = print.background;
+ }
+
+ BackgroundIterator(BackgroundIterator that) {
+ this.target = that.target.copy();
+ this.background = that.background;
+ this.device = that.device;
+ }
+
+ public Point minimumSize() {
+ return target.minimumSize();
+ }
+
+ public Point preferredSize() {
+ return target.preferredSize();
+ }
+
+ public boolean hasNext() {
+ return target.hasNext();
+ }
+
+ public PrintPiece next(int width, int height) {
+ PrintPiece targetPiece = PaperClips.next(target, width, height);
+ if (targetPiece == null)
+ return null;
+ return new BackgroundPiece(targetPiece, background, device);
+ }
+
+ public PrintIterator copy() {
+ return new BackgroundIterator(this);
+ }
+}
+
+class BackgroundPiece implements PrintPiece {
+ private final PrintPiece target;
+ private final Device device;
+ private final RGB background;
+
+ BackgroundPiece(PrintPiece target, RGB background, Device device) {
+ Util.notNull(target, background, device);
+ this.target = target;
+ this.device = device;
+ this.background = background;
+ }
+
+ public Point getSize() {
+ return target.getSize();
+ }
+
+ public void paint(GC gc, int x, int y) {
+ paintBackground(gc, x, y);
+ target.paint(gc, x, y);
+ }
+
+ private void paintBackground(GC gc, int x, int y) {
+ Color oldBackground = gc.getBackground();
+
+ gc.setBackground(ResourcePool.forDevice(device).getColor(background));
+ Point size = getSize();
+ gc.fillRectangle(x, y, size.x, size.y);
+
+ gc.setBackground(oldBackground);
+ }
+
+ public void dispose() {
+ target.dispose();
+ }
+} \ No newline at end of file
diff --git a/plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/BigPrint.java b/plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/BigPrint.java
new file mode 100644
index 0000000000..ac71111e91
--- /dev/null
+++ b/plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/BigPrint.java
@@ -0,0 +1,219 @@
+/*
+ * Copyright (c) 2006 Matthew Hall 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:
+ * Matthew Hall - initial API and implementation
+ */
+package org.eclipse.sirius.table.ui.tools.internal.paperclips;
+
+import org.eclipse.sirius.table.ui.tools.internal.paperclips.internal.util.Util;
+import org.eclipse.swt.graphics.Device;
+import org.eclipse.swt.graphics.GC;
+import org.eclipse.swt.graphics.Point;
+import org.eclipse.swt.graphics.Region;
+
+/**
+ * A wrapper for prints whose minimum size is too large to fit on one page. The
+ * target's content is divided across multiple pages like a spreadsheet. Pages
+ * are printed in order left-to-right, then top-to-bottom.
+ * <p>
+ * <em>Note that this print lays out content under the assumption that every page will have the same
+ * pixel width and height.</em> If a BigPrint is wrapped in a print that
+ * violates this expectation, it is likely that the output will skip and/or
+ * repeat certain portions of the target's content. Some examples of this
+ * behavior:
+ * <ul>
+ * <li>BorderPrint changes the available page height of the target, depending on
+ * whether the top and bottom borders are open or closed.
+ * <li>ColumnPrint often changes the width from column to column, if the total
+ * width is not evenly divisible by the number of columns.
+ * </ul>
+ *
+ * @author Matthew Hall
+ */
+public final class BigPrint implements Print {
+ private final Print target;
+
+ /**
+ * Constructs a BigPrint.
+ *
+ * @param target
+ */
+ public BigPrint(Print target) {
+ Util.notNull(target);
+ this.target = target;
+ }
+
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + ((target == null) ? 0 : target.hashCode());
+ return result;
+ }
+
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (obj == null)
+ return false;
+ if (getClass() != obj.getClass())
+ return false;
+ BigPrint other = (BigPrint) obj;
+ if (target == null) {
+ if (other.target != null)
+ return false;
+ } else if (!target.equals(other.target))
+ return false;
+ return true;
+ }
+
+ /**
+ * Returns the wrapped print which is being split across pages.
+ *
+ * @return the wrapped print which is being split across pages.
+ */
+ public Print getTarget() {
+ return target;
+ }
+
+ public PrintIterator iterator(Device device, GC gc) {
+ return new BigIterator(target, device, gc);
+ }
+}
+
+class BigIterator implements PrintIterator {
+ private final PrintIterator target;
+ private final Device device;
+
+ private PrintPiece currentPiece;
+ private int xOffset;
+ private int yOffset;
+
+ BigIterator(Print target, Device device, GC gc) {
+ Util.notNull(device, gc, target);
+ this.target = target.iterator(device, gc);
+ this.device = device;
+ currentPiece = null;
+ xOffset = 0;
+ yOffset = 0;
+ }
+
+ BigIterator(BigIterator that) {
+ this.target = that.target.copy();
+ this.device = that.device;
+
+ this.currentPiece = that.currentPiece;
+ this.xOffset = that.xOffset;
+ this.yOffset = that.yOffset;
+ }
+
+ public Point minimumSize() {
+ return target.minimumSize();
+ }
+
+ public Point preferredSize() {
+ return target.preferredSize();
+ }
+
+ public boolean hasNext() {
+ return currentPiece != null || target.hasNext();
+ }
+
+ // Returns a point whose x and y fields represent the required pages wide
+ // and tall, respectively
+ private Point estimatePagesRequired(int width, int height) {
+ if (width <= 0 || height <= 0)
+ return new Point(0, 0);
+
+ Point pref = target.preferredSize();
+ Point prefPages = new Point(pref.x / width, pref.y / height);
+
+ Point min = target.minimumSize();
+
+ // Adding width-1 rounds up page count w/out floating point op
+ // Same goes for adding height-1
+ Point minPages = new Point(Math.max((min.x + width - 1) / width, 1),
+ Math.max((min.y + height - 1) / height, 1));
+
+ return new Point(Math.max(prefPages.x, minPages.x), Math.max(
+ prefPages.y, minPages.y));
+ }
+
+ public PrintPiece next(int width, int height) {
+ if (!hasNext())
+ PaperClips.error("No more content"); //$NON-NLS-1$
+
+ if (currentPiece == null) {
+ Point pages = estimatePagesRequired(width, height);
+ currentPiece = PaperClips.next(target, width * pages.x, height
+ * pages.y);
+ if (currentPiece == null)
+ return null; // Iteration fails
+
+ // Reset the offset for the new piece.
+ xOffset = 0;
+ yOffset = 0;
+ }
+
+ PrintPiece result = new BigPiece(currentPiece,
+ new Point(width, height), xOffset, yOffset);
+
+ // Advance cursor on current piece.
+ xOffset += width;
+ if (xOffset >= currentPiece.getSize().x) {
+ xOffset = 0;
+ yOffset += height;
+ }
+ if (yOffset >= currentPiece.getSize().y) {
+ currentPiece = null;
+ }
+
+ return result;
+ }
+
+ public PrintIterator copy() {
+ return new BigIterator(this);
+ }
+}
+
+class BigPiece implements PrintPiece {
+ private final PrintPiece target;
+ private final Point size;
+ private final Point offset;
+
+ BigPiece(PrintPiece target, Point size, int xOffset, int yOffset) {
+ Util.notNull(target, size);
+ this.target = target;
+ this.size = new Point(size.x, size.y);
+ this.offset = new Point(xOffset, yOffset);
+ }
+
+ public Point getSize() {
+ return new Point(size.x, size.y);
+ }
+
+ public void paint(GC gc, int x, int y) {
+ // Remember clipping region
+ Region region = new Region();
+ gc.getClipping(region);
+
+ // Set clipping region so only the portion of the target we want is
+ // printed.
+ gc.setClipping(x, y, size.x, size.y);
+
+ // Paint the target.
+ target.paint(gc, x - offset.x, y - offset.y);
+
+ // Restore clipping region
+ gc.setClipping(region);
+ region.dispose();
+ }
+
+ public void dispose() {
+ target.dispose();
+ }
+} \ No newline at end of file
diff --git a/plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/BreakPrint.java b/plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/BreakPrint.java
new file mode 100644
index 0000000000..8ed6b454d2
--- /dev/null
+++ b/plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/BreakPrint.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (c) 2006 Matthew Hall 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:
+ * Matthew Hall - initial API and implementation
+ */
+package org.eclipse.sirius.table.ui.tools.internal.paperclips;
+
+import org.eclipse.sirius.table.ui.tools.internal.paperclips.internal.piece.EmptyPiece;
+import org.eclipse.sirius.table.ui.tools.internal.paperclips.internal.util.Util;
+import org.eclipse.swt.graphics.Device;
+import org.eclipse.swt.graphics.GC;
+import org.eclipse.swt.graphics.Point;
+
+/**
+ * A print which inserts a page break (or a column break, if inside a
+ * ColumnPrint).
+ * <p>
+ * This class is horizontally and vertically greedy. Greedy prints take up all
+ * the available space on the page.
+ *
+ * @author Matthew Hall
+ */
+public class BreakPrint implements Print {
+ /**
+ * Constructs a BreakPrint.
+ */
+ public BreakPrint() {
+ // Nothing to do
+ }
+
+ public boolean equals(Object obj) {
+ return Util.sameClass(this, obj);
+ }
+
+ public int hashCode() {
+ return 39 * 29;
+ }
+
+ public PrintIterator iterator(Device device, GC gc) {
+ return new BreakIterator();
+ }
+}
+
+class BreakIterator implements PrintIterator {
+ boolean hasNext;
+
+ BreakIterator() {
+ hasNext = true;
+ }
+
+ public PrintIterator copy() {
+ return hasNext ? new BreakIterator() : this;
+ }
+
+ public boolean hasNext() {
+ return hasNext;
+ }
+
+ public Point minimumSize() {
+ return new Point(0, 0);
+ }
+
+ public Point preferredSize() {
+ return new Point(0, 0);
+ }
+
+ public PrintPiece next(int width, int height) {
+ if (!hasNext)
+ PaperClips.error("No more content"); //$NON-NLS-1$
+
+ hasNext = false;
+ return new EmptyPiece(new Point(width, height));
+ }
+} \ No newline at end of file
diff --git a/plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/ColumnPrint.java b/plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/ColumnPrint.java
new file mode 100644
index 0000000000..cf13e03c86
--- /dev/null
+++ b/plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/ColumnPrint.java
@@ -0,0 +1,345 @@
+/*
+ * Copyright (c) 2005 Matthew Hall 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:
+ * Matthew Hall - initial API and implementation
+ */
+package org.eclipse.sirius.table.ui.tools.internal.paperclips;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+import org.eclipse.sirius.table.ui.tools.internal.paperclips.internal.util.Util;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.graphics.Device;
+import org.eclipse.swt.graphics.GC;
+import org.eclipse.swt.graphics.Point;
+
+/**
+ * A wrapper Print which splits its child print into multiple columns.
+ * <p>
+ * This class is horizontally greedy. Greedy prints take up all the available
+ * space on the page.
+ * <p>
+ * ColumnPrint attempts to use the minimum possible vertical space on the page
+ * if isCompressed() returns true (the default). This behavior can be disabled
+ * by calling setCompressed(false).
+ *
+ * @author Matthew Hall
+ */
+public class ColumnPrint implements Print {
+ final Print target;
+ final int columns;
+ final int spacing;
+
+ boolean compressed;
+
+ /**
+ * Constructs a ColumnPrint with the given target, number of columns, and
+ * column spacing (expressed in points). 72 points = 1".
+ *
+ * @param target
+ * the print which will be split into columns.
+ * @param columns
+ * the number of columns to display
+ * @param spacing
+ * the spacing between each column.
+ */
+ public ColumnPrint(Print target, int columns, int spacing) {
+ this(target, columns, spacing, true);
+ }
+
+ /**
+ * Constructs a ColumnPrint with the given target, column count, column
+ * spacing, and compression.
+ *
+ * @param target
+ * the print to display in columns.
+ * @param columns
+ * the number of columns to display.
+ * @param spacing
+ * the spacing between each column, expressed in points. 72
+ * points = 1".
+ * @param compressed
+ * whether the columns on the final page are to be
+ */
+ public ColumnPrint(Print target, int columns, int spacing,
+ boolean compressed) {
+ Util.notNull(target);
+ if (spacing < 0)
+ PaperClips
+ .error(SWT.ERROR_INVALID_ARGUMENT, "spacing must be >= 0"); //$NON-NLS-1$
+ if (columns < 2)
+ PaperClips
+ .error(SWT.ERROR_INVALID_ARGUMENT, "columns must be >= 2"); //$NON-NLS-1$
+
+ this.target = target;
+ this.spacing = spacing;
+ this.columns = columns;
+ this.compressed = compressed;
+ }
+
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + columns;
+ result = prime * result + (compressed ? 1231 : 1237);
+ result = prime * result + spacing;
+ result = prime * result + ((target == null) ? 0 : target.hashCode());
+ return result;
+ }
+
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (obj == null)
+ return false;
+ if (getClass() != obj.getClass())
+ return false;
+ ColumnPrint other = (ColumnPrint) obj;
+ if (columns != other.columns)
+ return false;
+ if (compressed != other.compressed)
+ return false;
+ if (spacing != other.spacing)
+ return false;
+ if (target == null) {
+ if (other.target != null)
+ return false;
+ } else if (!target.equals(other.target))
+ return false;
+ return true;
+ }
+
+ /**
+ * Returns the target print being split into columns.
+ *
+ * @return the target print being split into columns.
+ */
+ public Print getTarget() {
+ return target;
+ }
+
+ /**
+ * Returns the number of columns per page.
+ *
+ * @return the number of columns per page.
+ */
+ public int getColumnCount() {
+ return columns;
+ }
+
+ /**
+ * Returns the spacing between columns, in points. 72 points = 1".
+ *
+ * @return the spacing between columns, in points.
+ */
+ public int getColumnSpacing() {
+ return spacing;
+ }
+
+ /**
+ * Returns whether the columns are compressed to the smallest possible
+ * height on the last page.
+ *
+ * @return whether the columns are compressed to the smallest possible
+ * height on the last page.
+ */
+ public boolean isCompressed() {
+ return compressed;
+ }
+
+ /**
+ * Sets whether the columns are compressed to the smallest possible height
+ * on the last page.
+ *
+ * @param compressed
+ * whether to compress the columns.
+ */
+ public void setCompressed(boolean compressed) {
+ this.compressed = compressed;
+ }
+
+ public PrintIterator iterator(Device device, GC gc) {
+ return new ColumnIterator(this, device, gc);
+ }
+}
+
+class ColumnIterator implements PrintIterator {
+ private PrintIterator target;
+ private final int columns;
+ private final int spacing;
+ private final boolean compressed;
+
+ ColumnIterator(ColumnPrint print, Device device, GC gc) {
+ this.target = print.target.iterator(device, gc);
+ this.columns = print.columns;
+ this.spacing = Math.round(print.spacing * device.getDPI().x / 72f);
+ this.compressed = print.compressed;
+ }
+
+ ColumnIterator(ColumnIterator that) {
+ this.target = that.target.copy();
+ this.columns = that.columns;
+ this.spacing = that.spacing;
+ this.compressed = that.compressed;
+ }
+
+ public Point minimumSize() {
+ return computeSize(target.minimumSize());
+ }
+
+ public Point preferredSize() {
+ return computeSize(target.preferredSize());
+ }
+
+ private Point computeSize(Point targetSize) {
+ return new Point(targetSize.x * columns + spacing * (columns - 1),
+ targetSize.y);
+ }
+
+ public boolean hasNext() {
+ return target.hasNext();
+ }
+
+ int[] computeColSizes(int width) {
+ int[] colSizes = new int[columns];
+ int availableWidth = width - spacing * (columns - 1);
+ for (int i = 0; i < colSizes.length; i++) {
+ colSizes[i] = availableWidth / (columns - i);
+ availableWidth -= colSizes[i];
+ }
+ return colSizes;
+ }
+
+ Point[] computeColOffsets(int[] colSizes) {
+ Point[] colOffsets = new Point[columns];
+ int xOffset = 0;
+ for (int i = 0; i < columns; i++) {
+ colOffsets[i] = new Point(xOffset, 0);
+ xOffset += colSizes[i] + spacing;
+ }
+ return colOffsets;
+ }
+
+ /**
+ * Iterates across the given column sizes and returns an array of
+ * PrintPieces to fill those columns, or null if there was insufficient room
+ * to continue iterating. A backup of the given iterator should be taken
+ * before invoking this method! If null is returned, the given iterator is
+ * corrupt and should no longer be used!
+ *
+ * @param colSizes
+ * an array of column sizes
+ * @param height
+ * the height
+ * @return an array of PrintPieces for the given column sizes, or null
+ */
+ PrintPiece[] nextColumns(PrintIterator iterator, int[] colSizes, int height) {
+ List pieces = new ArrayList();
+ for (int i = 0; i < columns && iterator.hasNext(); i++) {
+ PrintPiece piece = PaperClips.next(iterator, colSizes[i], height);
+
+ if (piece == null)
+ return disposePieces(pieces);
+
+ pieces.add(piece);
+ }
+
+ return (PrintPiece[]) pieces.toArray(new PrintPiece[pieces.size()]);
+ }
+
+ private PrintPiece[] disposePieces(List pieces) {
+ for (Iterator iter = pieces.iterator(); iter.hasNext();) {
+ PrintPiece piece = (PrintPiece) iter.next();
+ piece.dispose();
+ }
+ return null;
+ }
+
+ PrintPiece createResult(PrintPiece[] pieces, int[] colSizes) {
+ CompositeEntry[] entries = new CompositeEntry[pieces.length];
+
+ Point[] offsets = computeColOffsets(colSizes);
+ for (int i = 0; i < pieces.length; i++)
+ entries[i] = new CompositeEntry(pieces[i], offsets[i]);
+
+ return new CompositePiece(entries);
+ }
+
+ public PrintPiece next(int width, int height) {
+ int[] colSizes = computeColSizes(width);
+
+ // Iterate on a copy in case any column fails to layout.
+ PrintIterator iter = target.copy();
+ PrintPiece[] columns = nextColumns(iter, colSizes, height);
+ if (columns == null)
+ return null;
+
+ // The target was completely consumed. If compressed property is true,
+ // close the gap until we find the
+ // smallest height that completely consumes the target's contents.
+ if (!iter.hasNext() && compressed)
+ return nextCompressed(colSizes, iter, columns);
+
+ this.target = iter;
+ return createResult(columns, colSizes);
+ }
+
+ private PrintPiece nextCompressed(int[] colSizes, PrintIterator iter,
+ PrintPiece[] columns) {
+ int highestInvalidHeight = 0;
+ int lowestValidHeight = getMaxHeight(columns);
+
+ // Remember the best results
+ PrintIterator bestIteration = iter;
+ PrintPiece[] bestColumns = columns;
+
+ while (lowestValidHeight > highestInvalidHeight + 1) {
+ int testHeight = (lowestValidHeight + highestInvalidHeight + 1) / 2;
+
+ iter = target.copy();
+ columns = nextColumns(iter, colSizes, testHeight);
+
+ if (columns == null) {
+ highestInvalidHeight = testHeight;
+ } else if (iter.hasNext()) {
+ highestInvalidHeight = testHeight;
+ disposePieces(columns);
+ } else {
+ disposePieces(bestColumns);
+
+ bestIteration = iter;
+ bestColumns = columns;
+ lowestValidHeight = getMaxHeight(bestColumns);
+ }
+ }
+
+ // Now that we've narrowed down the target's best iteration, we can
+ // update the state of this iterator and
+ // return the result.
+ this.target = bestIteration;
+ return createResult(bestColumns, colSizes);
+ }
+
+ private int getMaxHeight(PrintPiece[] pieces) {
+ int result = 0;
+ for (int i = 0; i < pieces.length; i++)
+ result = Math.max(result, pieces[i].getSize().y);
+ return result;
+ }
+
+ private void disposePieces(PrintPiece[] pieces) {
+ for (int i = 0; i < pieces.length; i++)
+ pieces[i].dispose();
+ }
+
+ public PrintIterator copy() {
+ return new ColumnIterator(this);
+ }
+} \ No newline at end of file
diff --git a/plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/CompositeEntry.java b/plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/CompositeEntry.java
new file mode 100644
index 0000000000..d7d427abc3
--- /dev/null
+++ b/plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/CompositeEntry.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (c) 2005 Matthew Hall 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:
+ * Matthew Hall - initial API and implementation
+ */
+package org.eclipse.sirius.table.ui.tools.internal.paperclips;
+
+import org.eclipse.sirius.table.ui.tools.internal.paperclips.internal.util.Util;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.graphics.Point;
+
+/**
+ * An entry in a CompositePiece.
+ *
+ * @author Matthew Hall
+ */
+public class CompositeEntry {
+ final PrintPiece piece;
+ final Point offset;
+
+ /**
+ * Constructs a CompositeEntry with the given PrintPiece and offset.
+ *
+ * @param piece
+ * the PrintPiece for this entry.
+ * @param offset
+ * the painting offset within the CompositePrint.
+ */
+ public CompositeEntry(PrintPiece piece, Point offset) {
+ Util.notNull(piece, offset);
+ checkOffset(offset);
+
+ this.piece = piece;
+ this.offset = offset;
+ }
+
+ private void checkOffset(Point offset) {
+ if (offset.x < 0 || offset.y < 0)
+ PaperClips.error(SWT.ERROR_INVALID_ARGUMENT,
+ "Offset cannot be negative: " + offset); //$NON-NLS-1$
+ }
+
+ /**
+ * Disposes this entry's print piece.
+ */
+ public void dispose() {
+ piece.dispose();
+ }
+} \ No newline at end of file
diff --git a/plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/CompositePiece.java b/plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/CompositePiece.java
new file mode 100644
index 0000000000..5bdffc4616
--- /dev/null
+++ b/plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/CompositePiece.java
@@ -0,0 +1,124 @@
+/*
+ * Copyright (c) 2005 Matthew Hall 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:
+ * Matthew Hall - initial API and implementation
+ */
+package org.eclipse.sirius.table.ui.tools.internal.paperclips;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.eclipse.sirius.table.ui.tools.internal.paperclips.internal.util.Util;
+import org.eclipse.swt.graphics.GC;
+import org.eclipse.swt.graphics.Point;
+
+/**
+ * A composite PrintPiece for displaying child PrintPieces. This class is
+ * especially useful for Print implementations that perform layout of multiple
+ * child Prints.
+ *
+ * @author Matthew Hall
+ */
+public class CompositePiece implements PrintPiece {
+ private final Point size;
+
+ private final CompositeEntry[] entries;
+
+ /**
+ * Constructs a CompositePiece with the given entries.
+ *
+ * @param entries
+ * an array of entries that make up this PrintPiece.
+ */
+ public CompositePiece(CompositeEntry[] entries) {
+ this(createList(entries));
+ }
+
+ /**
+ * Constructs a CompositePrintPiece with the given entries and explicit
+ * size. This constructor will increase the explicit size to completely
+ * contain any child entries which extend outside the given size.
+ *
+ * @param entries
+ * an array of entries that make up this PrintPiece.
+ * @param size
+ */
+ public CompositePiece(CompositeEntry[] entries, Point size) {
+ this(createList(entries), size);
+ }
+
+ private static List createList(CompositeEntry[] entries) {
+ List result = new ArrayList();
+ for (int i = 0; i < entries.length; i++)
+ result.add(entries[i]);
+ return result;
+ }
+
+ /**
+ * Constructs a composite PrintPiece with the given entries.
+ *
+ * @param entries
+ * an array of entries that make up this PrintPiece.
+ */
+ public CompositePiece(List entries) {
+ this(entries, new Point(0, 0));
+ }
+
+ /**
+ * Constructs a composite PrintPiece with the given entries and minimum
+ * size.
+ *
+ * @param entries
+ * a list of CompositeEntry objects describing the child
+ * PrintPieces.
+ * @param size
+ * a hint indicating the minimum size that should be reported
+ * from getSize(). This constructor increase this size to fit any
+ * entries that extend outside the given size.
+ */
+ public CompositePiece(List entries, Point size) {
+ Util.noNulls(entries);
+
+ this.entries = (CompositeEntry[]) entries
+ .toArray(new CompositeEntry[entries.size()]);
+ this.size = new Point(size.x, size.y);
+
+ for (int i = 0; i < this.entries.length; i++) {
+ CompositeEntry entry = this.entries[i];
+ Point pieceSize = entry.piece.getSize();
+ this.size.x = Math.max(this.size.x, entry.offset.x + pieceSize.x);
+ this.size.y = Math.max(this.size.y, entry.offset.y + pieceSize.y);
+ }
+ }
+
+ public Point getSize() {
+ return new Point(size.x, size.y);
+ }
+
+ public void paint(GC gc, int x, int y) {
+ // SWT on OSX has problems with the clipping. A GC(Printer) always
+ // returns a clipping rectangle of
+ // [0,0,0,0] so that inhibits our ability to check each entry against
+ // the hit rectangle. In addition it
+ // appears that a GC(Image(Printer))'s clipping on OSX is not affected
+ // by the GC's transform, so that
+ // screws up the hit clip as well. For this reason we are no longer
+ // checking entries to see if they
+ // intersect the clipping region before drawing them.
+
+ for (int i = 0; i < entries.length; i++) {
+ CompositeEntry entry = entries[i];
+ entry.piece.paint(gc, x + entry.offset.x, y + entry.offset.y);
+ }
+ }
+
+ public void dispose() {
+ for (int i = 0; i < entries.length; i++)
+ entries[i].dispose();
+ }
+}
diff --git a/plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/DebugPrint.java b/plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/DebugPrint.java
new file mode 100644
index 0000000000..cfcd182d53
--- /dev/null
+++ b/plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/DebugPrint.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (c) 2009 Matthew Hall 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:
+ * Matthew Hall - initial API and implementation
+ */
+package org.eclipse.sirius.table.ui.tools.internal.paperclips;
+
+import org.eclipse.swt.graphics.Device;
+import org.eclipse.swt.graphics.GC;
+import org.eclipse.swt.graphics.Point;
+
+/**
+ * Helper Print for debugging documents which fail to layout. Clients may set
+ * breakpoints inside the methods in this class (as well as DebugIterator and
+ * DebugPiece), then step into the target object's methods to trace the problem.
+ *
+ * @author Matthew Hall
+ * @deprecated Reminder to remove references to DebugPrint when you're done
+ * debugging a print job.
+ */
+public class DebugPrint implements Print {
+ private final Print target;
+
+ /**
+ * @param target
+ * the Print object to debug
+ */
+ public DebugPrint(Print target) {
+ this.target = target;
+ }
+
+ public PrintIterator iterator(Device device, GC gc) {
+ PrintIterator iterator = target.iterator(device, gc);
+ return new DebugIterator(iterator);
+ }
+}
+
+class DebugIterator implements PrintIterator {
+ private final PrintIterator target;
+
+ DebugIterator(PrintIterator target) {
+ this.target = target;
+ }
+
+ public PrintIterator copy() {
+ return new DebugIterator(target.copy());
+ }
+
+ public boolean hasNext() {
+ return target.hasNext();
+ }
+
+ public Point minimumSize() {
+ return target.minimumSize();
+ }
+
+ public PrintPiece next(int width, int height) {
+ PrintPiece piece = target.next(width, height);
+ return piece == null ? null : new DebugPiece(piece);
+ }
+
+ public Point preferredSize() {
+ return target.preferredSize();
+ }
+}
+
+class DebugPiece implements PrintPiece {
+ private final PrintPiece target;
+
+ DebugPiece(PrintPiece target) {
+ this.target = target;
+ }
+
+ public void dispose() {
+ target.dispose();
+ }
+
+ public Point getSize() {
+ return target.getSize();
+ }
+
+ public void paint(GC gc, int x, int y) {
+ target.paint(gc, x, y);
+ }
+} \ No newline at end of file
diff --git a/plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/EmptyPrint.java b/plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/EmptyPrint.java
new file mode 100644
index 0000000000..fb42517d38
--- /dev/null
+++ b/plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/EmptyPrint.java
@@ -0,0 +1,143 @@
+/*
+ * Copyright (c) 2005 Matthew Hall 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:
+ * Matthew Hall - initial API and implementation
+ */
+package org.eclipse.sirius.table.ui.tools.internal.paperclips;
+
+import org.eclipse.sirius.table.ui.tools.internal.paperclips.internal.piece.EmptyPiece;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.graphics.Device;
+import org.eclipse.swt.graphics.GC;
+import org.eclipse.swt.graphics.Point;
+
+/**
+ * A Print which displays nothing but takes up space. Useful for putting blank
+ * cells in a GridPrint.
+ *
+ * @author Matthew
+ */
+public class EmptyPrint implements Print {
+ final int width;
+ final int height;
+
+ /**
+ * Constructs an EmptyPrint with size (0, 0).
+ */
+ public EmptyPrint() {
+ this(0, 0);
+ }
+
+ /**
+ * Constructs an EmptyPrint with the given size.
+ *
+ * @param width
+ * width of the Print, in points (72pts = 1").
+ * @param height
+ * height of the Print, in points (72pts = 1").
+ */
+ public EmptyPrint(int width, int height) {
+ this.width = checkDimension(width);
+ this.height = checkDimension(height);
+ }
+
+ /**
+ * Constructs an EmptyPrint with the given size.
+ *
+ * @param size
+ * the size, in points (72pts = 1").
+ */
+ public EmptyPrint(Point size) {
+ this(size.x, size.y);
+ }
+
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + height;
+ result = prime * result + width;
+ return result;
+ }
+
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (obj == null)
+ return false;
+ if (getClass() != obj.getClass())
+ return false;
+ EmptyPrint other = (EmptyPrint) obj;
+ if (height != other.height)
+ return false;
+ if (width != other.width)
+ return false;
+ return true;
+ }
+
+ /**
+ * Returns the size of the empty space.
+ *
+ * @return the size of the empty space.
+ */
+ public Point getSize() {
+ return new Point(width, height);
+ }
+
+ private int checkDimension(int dim) {
+ if (dim < 0)
+ PaperClips.error(SWT.ERROR_INVALID_ARGUMENT,
+ "EmptyPrint dimensions must be >= 0"); //$NON-NLS-1$
+ return dim;
+ }
+
+ public PrintIterator iterator(Device device, GC gc) {
+ return new EmptyIterator(device, this);
+ }
+}
+
+class EmptyIterator implements PrintIterator {
+ private final Point size;
+
+ private boolean hasNext = true;
+
+ EmptyIterator(Device device, EmptyPrint target) {
+ Point dpi = device.getDPI();
+ this.size = new Point(Math.round(target.width * dpi.x / 72f), Math
+ .round(target.height * dpi.y / 72f));
+ }
+
+ EmptyIterator(EmptyIterator that) {
+ this.size = that.size;
+ this.hasNext = that.hasNext;
+ }
+
+ public boolean hasNext() {
+ return hasNext;
+ }
+
+ public PrintPiece next(int width, int height) {
+ if (size.x > width || size.y > height)
+ return null;
+
+ hasNext = false;
+
+ return new EmptyPiece(size);
+ }
+
+ public Point minimumSize() {
+ return new Point(size.x, size.y);
+ }
+
+ public Point preferredSize() {
+ return new Point(size.x, size.y);
+ }
+
+ public PrintIterator copy() {
+ return new EmptyIterator(this);
+ }
+} \ No newline at end of file
diff --git a/plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/ImagePrint.java b/plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/ImagePrint.java
new file mode 100644
index 0000000000..f875ad2522
--- /dev/null
+++ b/plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/ImagePrint.java
@@ -0,0 +1,256 @@
+/*
+ * Copyright (c) 2005 Matthew Hall 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:
+ * Matthew Hall - initial API and implementation
+ */
+package org.eclipse.sirius.table.ui.tools.internal.paperclips;
+
+import org.eclipse.sirius.table.ui.tools.internal.paperclips.internal.util.SWTUtil;
+import org.eclipse.sirius.table.ui.tools.internal.paperclips.internal.util.Util;
+import org.eclipse.swt.graphics.Device;
+import org.eclipse.swt.graphics.GC;
+import org.eclipse.swt.graphics.Image;
+import org.eclipse.swt.graphics.ImageData;
+import org.eclipse.swt.graphics.Point;
+
+/**
+ * A Print for displaying images.
+ *
+ * @author Matthew Hall
+ */
+public class ImagePrint implements Print {
+ ImageData imageData;
+ Point dpi;
+ Point size;
+
+ /**
+ * Constructs an ImagePrint with the given imageData, initialized at 72dpi.
+ *
+ * @param imageData
+ * the image to be displayed.
+ */
+ public ImagePrint(ImageData imageData) {
+ this(imageData, new Point(72, 72));
+ }
+
+ /**
+ * Constructs an ImagePrint with the given imageData and dpi.
+ *
+ * @param imageData
+ * the image to be displayed.
+ * @param dpi
+ * the DPI that the image will be displayed at.
+ */
+ public ImagePrint(ImageData imageData, Point dpi) {
+ Util.notNull(imageData, dpi);
+ this.imageData = imageData;
+ setDPI(dpi);
+ }
+
+ /**
+ * Returns the ImageData of the image being printed.
+ *
+ * @return the ImageData of the image being printed.
+ */
+ public ImageData getImageData() {
+ return imageData;
+ }
+
+ public boolean equals(Object obj) {
+ if (obj == this)
+ return true;
+ if (obj == null)
+ return false;
+ if (getClass() != obj.getClass())
+ return false;
+
+ ImagePrint that = (ImagePrint) obj;
+ return Util.equal(this.dpi, that.dpi)
+ && Util.equal(this.size, that.size)
+ && SWTUtil.equal(this.imageData, that.imageData);
+ }
+
+ public int hashCode() {
+ int prime = 31;
+ int result = 1;
+ result = prime * result + dpi.hashCode();
+ result = prime * result + size.hashCode();
+ result = prime * result + SWTUtil.hashCode(imageData);
+ return result;
+ }
+
+ /**
+ * Sets the ImagePrint to render the image at the given size, in points. 72
+ * points = 1".
+ *
+ * @param size
+ * the explicit size, in points, that the image be printed at.
+ */
+ public void setSize(Point size) {
+ // The DPI is rounded up, so that the specified width and height will
+ // not be exceeded.
+ Util.notNull(size);
+ dpi = new Point((int) Math.ceil(imageData.width * 72d / size.x),
+ (int) Math.ceil(imageData.height * 72d / size.y));
+ this.size = size;
+ }
+
+ /**
+ * Sets the ImagePrint to render the image at the given size, in points. 72
+ * points = 1".
+ *
+ * @param width
+ * the explicit width, in points, that the image will be printed
+ * at.
+ * @param height
+ * the explicit height, in points, that the image will be printed
+ * at.
+ */
+ public void setSize(int width, int height) {
+ setSize(new Point(width, height));
+ }
+
+ /**
+ * Returns the size that the image will be rendered at, in points. 72 points
+ * = 1".
+ *
+ * @return the size of the image, in points.
+ */
+ public Point getSize() {
+ return size;
+ }
+
+ /**
+ * Sets the ImagePrint to render the image at the DPI of the argument.
+ *
+ * @param dpi
+ * the DPI of the image.
+ */
+ public void setDPI(Point dpi) {
+ Util.notNull(dpi);
+ this.dpi = dpi;
+ size = new Point((int) Math.ceil(imageData.width * 72d / dpi.x),
+ (int) Math.ceil(imageData.height * 72d / dpi.y));
+ }
+
+ /**
+ * Sets the ImagePrint to render the image at the given DPI.
+ *
+ * @param dpiX
+ * the horizontal DPI the image will be rendered at.
+ * @param dpiY
+ * the vertical DPI the image will be rendered at.
+ */
+ public void setDPI(int dpiX, int dpiY) {
+ setDPI(new Point(dpiX, dpiY));
+ }
+
+ /**
+ * Returns the DPI that this image will be rendered at.
+ *
+ * @return the DPI the image will be rendered at.
+ */
+ public Point getDPI() {
+ return dpi;
+ }
+
+ public PrintIterator iterator(Device device, GC gc) {
+ return new ImageIterator(this, device);
+ }
+}
+
+class ImageIterator implements PrintIterator {
+ final Device device;
+
+ final ImageData imageData;
+ final Point size;
+
+ boolean hasNext;
+
+ ImageIterator(ImagePrint print, Device device) {
+ Util.notNull(print, device);
+ this.device = device;
+ this.imageData = print.imageData;
+ Point dpi = device.getDPI();
+ this.size = new Point(print.size.x * dpi.x / 72, print.size.y * dpi.y
+ / 72);
+ this.hasNext = true;
+ }
+
+ ImageIterator(ImageIterator that) {
+ this.device = that.device;
+ this.imageData = that.imageData;
+ this.size = that.size;
+ this.hasNext = that.hasNext;
+ }
+
+ public boolean hasNext() {
+ return hasNext;
+ }
+
+ public PrintPiece next(int width, int height) {
+ if (!hasNext())
+ PaperClips.error("No more content."); //$NON-NLS-1$
+
+ if (width < size.x || height < size.y)
+ return null;
+
+ hasNext = false;
+
+ return new ImagePiece(device, imageData, size);
+ }
+
+ public Point minimumSize() {
+ return new Point(size.x, size.y);
+ }
+
+ public Point preferredSize() {
+ return new Point(size.x, size.y);
+ }
+
+ public PrintIterator copy() {
+ return hasNext ? new ImageIterator(this) : this;
+ }
+}
+
+class ImagePiece implements PrintPiece {
+ private final Device device;
+ private final ImageData imageData;
+ private final Point size;
+
+ private Image image;
+
+ ImagePiece(Device device, ImageData imageData, Point size) {
+ Util.notNull(device, imageData, size);
+ this.device = device;
+ this.imageData = imageData;
+ this.size = size;
+ }
+
+ public Point getSize() {
+ return new Point(size.x, size.y);
+ }
+
+ private Image getImage() {
+ if (image == null)
+ image = new Image(device, imageData);
+ return image;
+ }
+
+ public void paint(GC gc, int x, int y) {
+ gc.drawImage(getImage(), 0, 0, imageData.width, imageData.height, x, y,
+ size.x, size.y);
+ }
+
+ public void dispose() {
+ if (image != null) {
+ image.dispose();
+ image = null;
+ }
+ }
+} \ No newline at end of file
diff --git a/plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/LayerEntry.java b/plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/LayerEntry.java
new file mode 100644
index 0000000000..82259ecb3f
--- /dev/null
+++ b/plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/LayerEntry.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (c) 2006 Matthew Hall 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:
+ * Matthew Hall - initial API and implementation
+ */
+package org.eclipse.sirius.table.ui.tools.internal.paperclips;
+
+import org.eclipse.swt.graphics.Device;
+import org.eclipse.swt.graphics.GC;
+
+/**
+ * Instances implementing this interface are representing an entry in a
+ * LayerPrint.
+ *
+ * @author Matthew Hall
+ */
+public interface LayerEntry {
+
+ /**
+ * Returns the target print of this entry.
+ *
+ * @return the target print of this entry.
+ */
+ Print getTarget();
+
+ /**
+ * Returns the horizontal alignment applied to the target.
+ *
+ * @return the horizontal alignment applied to the target.
+ */
+ int getHorizontalAlignment();
+
+ /**
+ * @param device
+ * @param gc
+ * @return
+ */
+ LayerEntryIterator iterator(Device device, GC gc);
+} \ No newline at end of file
diff --git a/plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/LayerEntryIterator.java b/plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/LayerEntryIterator.java
new file mode 100644
index 0000000000..4a07c24824
--- /dev/null
+++ b/plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/LayerEntryIterator.java
@@ -0,0 +1,14 @@
+package org.eclipse.sirius.table.ui.tools.internal.paperclips;
+
+/**
+ *
+ *
+ */
+public interface LayerEntryIterator {
+
+ PrintIterator getTarget();
+
+ int getAlignment();
+
+ LayerEntryIterator copy();
+} \ No newline at end of file
diff --git a/plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/LayerPrint.java b/plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/LayerPrint.java
new file mode 100644
index 0000000000..13fc1160fa
--- /dev/null
+++ b/plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/LayerPrint.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright (c) 2005 Matthew Hall 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:
+ * Matthew Hall - initial API and implementation
+ */
+package org.eclipse.sirius.table.ui.tools.internal.paperclips;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.eclipse.sirius.table.ui.tools.internal.paperclips.internal.LayerEntryImpl;
+import org.eclipse.sirius.table.ui.tools.internal.paperclips.internal.LayerIterator;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.graphics.Device;
+import org.eclipse.swt.graphics.GC;
+
+/**
+ * A Print which displays its child Prints on top each other.
+ *
+ * @author Matthew Hall
+ */
+public class LayerPrint implements Print {
+ /**
+ * Constant for the default alignment of child Prints. Value is SWT.LEFT.
+ */
+ public static final int DEFAULT_ALIGN = SWT.LEFT;
+
+ // List<LayerEntry>
+ final List entries = new ArrayList();
+
+ /**
+ * Constructs a new LayerPrint.
+ */
+ public LayerPrint() {
+ }
+
+ /**
+ * Adds the given Print to this LayerPrint using the default alignment.
+ *
+ * @param print
+ * the Print to add.
+ * @see #DEFAULT_ALIGN
+ */
+ public void add(Print print) {
+ entries.add(new LayerEntryImpl(print, DEFAULT_ALIGN));
+ }
+
+ /**
+ * Adds the given Print to this LayerPrint using the specified alignment.
+ *
+ * @param print
+ * the Print to add.
+ * @param align
+ * the alignment for the Print. May be one of SWT.LEFT,
+ * SWT.CENTER, or SWT.RIGHT.
+ */
+ public void add(Print print, int align) {
+ entries.add(new LayerEntryImpl(print, align));
+ }
+
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + ((entries == null) ? 0 : entries.hashCode());
+ return result;
+ }
+
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (obj == null)
+ return false;
+ if (getClass() != obj.getClass())
+ return false;
+ LayerPrint other = (LayerPrint) obj;
+ if (entries == null) {
+ if (other.entries != null)
+ return false;
+ } else if (!entries.equals(other.entries))
+ return false;
+ return true;
+ }
+
+ /**
+ * Returns an array of entries in this LayerPrint.
+ *
+ * @return an array of entries in this LayerPrint.
+ */
+ public LayerEntry[] getEntries() {
+ return (LayerEntry[]) entries.toArray(new LayerEntry[entries.size()]);
+ }
+
+ public PrintIterator iterator(Device device, GC gc) {
+ return new LayerIterator(this, device, gc);
+ }
+}
diff --git a/plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/LinePrint.java b/plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/LinePrint.java
new file mode 100644
index 0000000000..d7d69ff793
--- /dev/null
+++ b/plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/LinePrint.java
@@ -0,0 +1,268 @@
+/*
+ * Copyright (c) 2005 Matthew Hall 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:
+ * Matthew Hall - initial API and implementation
+ */
+package org.eclipse.sirius.table.ui.tools.internal.paperclips;
+
+import org.eclipse.sirius.table.ui.tools.internal.paperclips.internal.util.ResourcePool;
+import org.eclipse.sirius.table.ui.tools.internal.paperclips.internal.util.Util;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.graphics.Color;
+import org.eclipse.swt.graphics.Device;
+import org.eclipse.swt.graphics.GC;
+import org.eclipse.swt.graphics.Point;
+import org.eclipse.swt.graphics.RGB;
+
+/**
+ * A Print for drawing horizontal and vertical lines.
+ * <p>
+ * LinePrints are either horizontally or vertically greedy, according to the
+ * orientation of the line. Greedy prints take up all the available space on the
+ * page.
+ *
+ * @author Matthew Hall
+ */
+public class LinePrint implements Print {
+ final int orientation;
+
+ double thickness;
+
+ RGB rgb = new RGB(0, 0, 0);
+
+ /**
+ * Constructs a horizontal LinePrint.
+ */
+ public LinePrint() {
+ this(SWT.HORIZONTAL);
+ }
+
+ /**
+ * Constructs a LinePrint with the given orientation and 1-point thickness.
+ *
+ * @param orientation
+ * one of SWT#HORIZONTAL or SWT#VERTICAL.
+ */
+ public LinePrint(int orientation) {
+ this(orientation, 1.0);
+ }
+
+ /**
+ * Constructs a LinePrint with the given orientation and thickness.
+ *
+ * @param orientation
+ * one of SWT#HORIZONTAL or SWT#VERTICAL.
+ * @param thickness
+ * the line thickness, expressed in points.
+ */
+ public LinePrint(int orientation, double thickness) {
+ this.orientation = checkOrientation(orientation);
+ this.thickness = checkThickness(thickness);
+ }
+
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + orientation;
+ result = prime * result + ((rgb == null) ? 0 : rgb.hashCode());
+ long temp;
+ temp = Double.doubleToLongBits(thickness);
+ result = prime * result + (int) (temp ^ (temp >>> 32));
+ return result;
+ }
+
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (obj == null)
+ return false;
+ if (getClass() != obj.getClass())
+ return false;
+ LinePrint other = (LinePrint) obj;
+ if (orientation != other.orientation)
+ return false;
+ if (rgb == null) {
+ if (other.rgb != null)
+ return false;
+ } else if (!rgb.equals(other.rgb))
+ return false;
+ if (Double.doubleToLongBits(thickness) != Double
+ .doubleToLongBits(other.thickness))
+ return false;
+ return true;
+ }
+
+ /**
+ * Returns the line orientation (one of {@link SWT#HORIZONTAL} or
+ * {@link SWT#VERTICAL}).
+ *
+ * @return the line orientation.
+ */
+ public int getOrientation() {
+ return orientation;
+ }
+
+ private int checkOrientation(int orientation) {
+ if ((orientation & SWT.HORIZONTAL) == SWT.HORIZONTAL)
+ return SWT.HORIZONTAL;
+ else if ((orientation & SWT.VERTICAL) == SWT.VERTICAL)
+ return SWT.VERTICAL;
+ else
+ return SWT.HORIZONTAL;
+ }
+
+ private double checkThickness(double thickness) {
+ if (thickness < 0)
+ return 0;
+ return thickness;
+ }
+
+ /**
+ * Returns the line thickness, in points. 72 points = 1".
+ *
+ * @return the line thickness, in points.
+ */
+ public double getThickness() {
+ return thickness;
+ }
+
+ /**
+ * Sets the line thickness, in points. 72 points = 1".
+ *
+ * @param thickness
+ * the line thickness, in points.
+ */
+ public void setThickness(double thickness) {
+ this.thickness = thickness;
+ }
+
+ /**
+ * Sets the line color to the argument.
+ *
+ * @param foreground
+ * the new line color.
+ */
+ public void setRGB(RGB foreground) {
+ Util.notNull(foreground);
+ this.rgb = foreground;
+ }
+
+ /**
+ * Returns the line color.
+ *
+ * @return the line color.
+ */
+ public RGB getRGB() {
+ return rgb;
+ }
+
+ public PrintIterator iterator(Device device, GC gc) {
+ return new LineIterator(this, device, gc);
+ }
+}
+
+class LineIterator implements PrintIterator {
+ private final Device device;
+ private final GC gc;
+
+ final int orientation;
+ final Point thickness;
+ final RGB rgb;
+
+ private boolean hasNext = true;
+
+ LineIterator(LinePrint print, Device device, GC gc) {
+ this.device = device;
+ this.gc = gc;
+
+ this.orientation = print.orientation;
+ this.rgb = print.rgb;
+ Point dpi = device.getDPI();
+
+ // (convert from points to pixels on device)
+ this.thickness = new Point(Math.max(1, (int) Math.round(print.thickness
+ * dpi.x / 72)), Math.max(1, (int) Math.round(print.thickness
+ * dpi.y / 72)));
+ }
+
+ LineIterator(LineIterator that) {
+ this.device = that.device;
+ this.gc = that.gc;
+
+ this.orientation = that.orientation;
+ this.rgb = that.rgb;
+ this.hasNext = that.hasNext;
+ this.thickness = that.thickness;
+ }
+
+ public boolean hasNext() {
+ return hasNext;
+ }
+
+ Point getSize(int width, int height) {
+ return orientation == SWT.VERTICAL ? new Point(thickness.x, height)
+ : new Point(width, thickness.y);
+ }
+
+ public PrintPiece next(int width, int height) {
+ if (!hasNext())
+ PaperClips.error("No more content"); //$NON-NLS-1$
+
+ // Make sure the line fits :)
+ Point size = getSize(width, height);
+ if (size.x > width || size.y > height)
+ return null;
+
+ PrintPiece result = new LinePiece(device, size, rgb);
+ hasNext = false;
+
+ return result;
+ }
+
+ public Point minimumSize() {
+ return new Point(thickness.x, thickness.y);
+ }
+
+ public Point preferredSize() {
+ return new Point(thickness.x, thickness.y);
+ }
+
+ public PrintIterator copy() {
+ return new LineIterator(this);
+ }
+}
+
+class LinePiece implements PrintPiece {
+ private final Device device;
+ private final Point size;
+ private final RGB rgb;
+
+ LinePiece(Device device, Point size, RGB rgb) {
+ this.device = device;
+ this.size = size;
+ this.rgb = rgb;
+ }
+
+ public Point getSize() {
+ return new Point(size.x, size.y);
+ }
+
+ public void paint(GC gc, int x, int y) {
+ Color oldBackground = gc.getBackground();
+ Point size = getSize();
+ try {
+ gc.setBackground(ResourcePool.forDevice(device).getColor(rgb));
+ gc.fillRectangle(x, y, size.x, size.y);
+ } finally {
+ gc.setBackground(oldBackground);
+ }
+ }
+
+ public void dispose() {
+ } // Shared resources, nothing to dispose
+} \ No newline at end of file
diff --git a/plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/Margins.java b/plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/Margins.java
new file mode 100644
index 0000000000..d6c3fd52f2
--- /dev/null
+++ b/plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/Margins.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright (c) 2006 Matthew Hall 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:
+ * Matthew Hall - initial API and implementation
+ */
+package org.eclipse.sirius.table.ui.tools.internal.paperclips;
+
+/**
+ * Instances of this class represent the page margins to follow when processing
+ * a print job.
+ *
+ * @author Matthew Hall
+ */
+public class Margins {
+ /** The top margin. */
+ public int top;
+
+ /** The left margin. */
+ public int left;
+
+ /** The right margin. */
+ public int right;
+
+ /** The bottom margin. */
+ public int bottom;
+
+ /**
+ * Constructs a Margins with all sides set to 1" margins.
+ */
+ public Margins() {
+ this(72);
+ }
+
+ /**
+ * Constructs a Margins with all sides set to the argument.
+ *
+ * @param margins
+ * the page margins, expressed in points. 72 points = 1".
+ */
+ public Margins(int margins) {
+ top = left = right = bottom = margins;
+ }
+
+ /**
+ * Returns a Margins that is the result of rotating this Margins
+ * counter-clockwise 90 degrees. A job which is rotated 90 degrees (e.g. for
+ * landscape printing) needs to have its margins rotated to match. This is a
+ * convenience method for that purpose.
+ *
+ * @return a Margins that is the result of rotating this Margins
+ * counter-clockwise 90 degrees.
+ */
+ public Margins rotate() {
+ Margins result = new Margins();
+ result.top = right;
+ result.left = top;
+ result.right = bottom;
+ result.bottom = left;
+ return result;
+ }
+
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + bottom;
+ result = prime * result + left;
+ result = prime * result + right;
+ result = prime * result + top;
+ return result;
+ }
+
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (obj == null)
+ return false;
+ if (getClass() != obj.getClass())
+ return false;
+ Margins other = (Margins) obj;
+ if (bottom != other.bottom)
+ return false;
+ if (left != other.left)
+ return false;
+ if (right != other.right)
+ return false;
+ if (top != other.top)
+ return false;
+ return true;
+ }
+} \ No newline at end of file
diff --git a/plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/Messages.java b/plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/Messages.java
new file mode 100644
index 0000000000..5b6258612a
--- /dev/null
+++ b/plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/Messages.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) 2008 Matthew Hall 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:
+ * Matthew Hall - initial API and implementation
+ */
+package org.eclipse.sirius.table.ui.tools.internal.paperclips;
+
+import java.util.MissingResourceException;
+import java.util.ResourceBundle;
+
+/**
+ * Convenience methods for retrieving locale-specific messages.
+ *
+ * @author Matthew Hall
+ * @since 1.0.4
+ */
+public class Messages {
+ private static final String BUNDLE_NAME = "org.eclipse.sirius.table.ui.tools.internal.paperclips.messages"; //$NON-NLS-1$
+
+ private static final ResourceBundle RESOURCE_BUNDLE = ResourceBundle.getBundle(BUNDLE_NAME);
+
+ /**
+ * Key for "Page {x} of {y}" used by DefaultPageNumberFormat.
+ */
+ public static final String PAGE_X_OF_Y = "PAGE_X_OF_Y"; //$NON-NLS-1$
+
+ private Messages() {
+ }
+
+ /**
+ * Returns the locale-specific messages for the given key.
+ *
+ * @param key
+ * the key identifying the string to be retrieved.
+ * @return the locale-specific messages for the given key.
+ */
+ public static String getString(String key) {
+ try {
+ return RESOURCE_BUNDLE.getString(key);
+ } catch (MissingResourceException e) {
+ return '!' + key + '!';
+ }
+ }
+}
diff --git a/plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/NoBreakPrint.java b/plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/NoBreakPrint.java
new file mode 100644
index 0000000000..ba5ebab6eb
--- /dev/null
+++ b/plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/NoBreakPrint.java
@@ -0,0 +1,120 @@
+/*
+ * Copyright (c) 2006 Matthew Hall 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:
+ * Matthew Hall - initial API and implementation
+ */
+package org.eclipse.sirius.table.ui.tools.internal.paperclips;
+
+import org.eclipse.sirius.table.ui.tools.internal.paperclips.internal.util.Util;
+import org.eclipse.swt.graphics.Device;
+import org.eclipse.swt.graphics.GC;
+import org.eclipse.swt.graphics.Point;
+
+/**
+ * A print wrapper which prevents its target from being broken into multiple
+ * pieces when printed. If there isn't enough room to print the target in one
+ * piece on the current page (or column, if it's inside a ColumnPrint), it will
+ * be printed on the next page (or column).
+ *
+ * <p>
+ * Care must be taken when using this class to avoid unprintable documents. If
+ * the target of a NoBreakPrint does not fit in the available space on the print
+ * device, the entire document will fail to print.
+ *
+ * @author Matthew Hall
+ */
+public class NoBreakPrint implements Print {
+ private final Print target;
+
+ /**
+ * Constructs a NoBreakPrint with the given target.
+ *
+ * @param target
+ * the print to
+ */
+ public NoBreakPrint(Print target) {
+ Util.notNull(target);
+ this.target = target;
+ }
+
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + ((target == null) ? 0 : target.hashCode());
+ return result;
+ }
+
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (obj == null)
+ return false;
+ if (getClass() != obj.getClass())
+ return false;
+ NoBreakPrint other = (NoBreakPrint) obj;
+ if (target == null) {
+ if (other.target != null)
+ return false;
+ } else if (!target.equals(other.target))
+ return false;
+ return true;
+ }
+
+ /**
+ * Returns the print which will not be broken across pages.
+ *
+ * @return the print which will not be broken across pages.
+ */
+ public Print getTarget() {
+ return target;
+ }
+
+ public PrintIterator iterator(Device device, GC gc) {
+ return new NoBreakIterator(target.iterator(device, gc));
+ }
+}
+
+class NoBreakIterator implements PrintIterator {
+ private PrintIterator target;
+
+ NoBreakIterator(PrintIterator target) {
+ Util.notNull(target);
+ this.target = target;
+ }
+
+ public PrintIterator copy() {
+ return new NoBreakIterator(target.copy());
+ }
+
+ public boolean hasNext() {
+ return target.hasNext();
+ }
+
+ public Point minimumSize() {
+ return target.minimumSize();
+ }
+
+ public Point preferredSize() {
+ return target.preferredSize();
+ }
+
+ public PrintPiece next(int width, int height) {
+ // Use a test iterator so we preserve the original iterator
+ PrintIterator iter = target.copy();
+
+ PrintPiece result = PaperClips.next(iter, width, height);
+ if (result == null)
+ return result;
+
+ if (iter.hasNext()) // Failed to layout the whole target in one piece
+ return null;
+
+ this.target = iter;
+ return result;
+ }
+} \ No newline at end of file
diff --git a/plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/NullPrintPiece.java b/plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/NullPrintPiece.java
new file mode 100644
index 0000000000..7ada635542
--- /dev/null
+++ b/plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/NullPrintPiece.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright (c) 2007 Matthew Hall 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:
+ * Matthew Hall - initial API and implementation
+ */
+
+package org.eclipse.sirius.table.ui.tools.internal.paperclips;
+
+import org.eclipse.swt.graphics.GC;
+import org.eclipse.swt.graphics.Point;
+
+final class NullPrintPiece implements PrintPiece {
+ public Point getSize() {
+ return new Point(0, 0);
+ }
+
+ public void paint(GC gc, int x, int y) {
+ }
+
+ public void dispose() {
+ }
+} \ No newline at end of file
diff --git a/plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/PageEnumeration.java b/plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/PageEnumeration.java
new file mode 100644
index 0000000000..30a592119a
--- /dev/null
+++ b/plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/PageEnumeration.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright (c) 2007 Matthew Hall 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:
+ * Matthew Hall - initial API and implementation
+ */
+package org.eclipse.sirius.table.ui.tools.internal.paperclips;
+
+import org.eclipse.swt.graphics.GC;
+import org.eclipse.swt.graphics.Point;
+import org.eclipse.swt.graphics.Rectangle;
+import org.eclipse.swt.printing.Printer;
+
+/**
+ * An enumeration of pages for given print job on the given printer device. Each
+ * element in the enumeration has already had the page orientation and page
+ * margins applied. Therefore, when calling the paint(GC, int, int) method on
+ * each page, the printer's trim should be provided as the x and y arguments. In
+ * other words, the trim is taken as a minimum margin while applying calculating
+ * margins, but the position where the page's content is drawn is determined
+ * solely by the margin, and is not offset by the trim. This behavior is helpful
+ * for screen display, and is already compensated for in the
+ * {@link PaperClips#print(PrintJob, Printer) } method.
+ *
+ * @see PaperClips#getPages(PrintJob, Printer)
+ * @author Matthew Hall
+ */
+public class PageEnumeration {
+ private PrintIterator document;
+ private Rectangle marginBounds;
+ private Rectangle paperBounds;
+
+ private boolean hasNext;
+
+ PageEnumeration(PrintJob job, Printer printer, GC gc) {
+ // Rotate the document (and margins with it) depending on print job
+ // orientation.
+ job = applyOrientation(job, printer);
+ Margins margins = job.getMargins();
+
+ marginBounds = PaperClips.getMarginBounds(margins, printer);
+ paperBounds = PaperClips.getPaperBounds(printer);
+
+ document = job.getDocument().iterator(printer, gc);
+ hasNext = document.hasNext();
+ }
+
+ /**
+ * Returns whether any pages remain.
+ *
+ * @return whether any pages remain.
+ */
+ public boolean hasNext() {
+ return hasNext;
+ }
+
+ /**
+ * Returns the next page.
+ *
+ * @return the next page.
+ */
+ public PrintPiece nextPage() {
+ if (!hasNext)
+ return null;
+
+ PrintPiece page = PaperClips.next(document, marginBounds.width,
+ marginBounds.height);
+ hasNext = notNull(page) && notDebugPiece(page) && document.hasNext();
+ PrintPiece result = page == null ? null : createPagePiece(page);
+ if (!hasNext) {
+ document = null;
+ marginBounds = null;
+ paperBounds = null;
+ }
+ return result;
+ }
+
+ private PrintPiece createPagePiece(PrintPiece page) {
+ Point offset = new Point(marginBounds.x - paperBounds.x, marginBounds.y
+ - paperBounds.y);
+ CompositeEntry entry = new CompositeEntry(page, offset);
+ Point size = new Point(paperBounds.width, paperBounds.height);
+ return new CompositePiece(new CompositeEntry[] { entry }, size);
+ }
+
+ private static boolean notNull(PrintPiece page) {
+ return page != null;
+ }
+
+ private static boolean notDebugPiece(PrintPiece page) {
+ return !(PaperClips.debug && page instanceof NullPrintPiece);
+ }
+
+ private static PrintJob applyOrientation(PrintJob printJob, Printer printer) {
+ int orientation = printJob.getOrientation();
+
+ Rectangle paperBounds = PaperClips.getPaperBounds(printer);
+ if (((orientation == PaperClips.ORIENTATION_LANDSCAPE) && (paperBounds.width < paperBounds.height))
+ || ((orientation == PaperClips.ORIENTATION_PORTRAIT) && (paperBounds.height < paperBounds.width))) {
+ String name = printJob.getName();
+ Print document = new RotatePrint(printJob.getDocument());
+ Margins margins = printJob.getMargins().rotate();
+ printJob = new PrintJob(name, document).setMargins(margins)
+ .setOrientation(PaperClips.ORIENTATION_DEFAULT);
+ }
+
+ return printJob;
+ }
+} \ No newline at end of file
diff --git a/plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/PaperClips.java b/plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/PaperClips.java
new file mode 100644
index 0000000000..8bd636e006
--- /dev/null
+++ b/plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/PaperClips.java
@@ -0,0 +1,507 @@
+/*
+ * Copyright (c) 2006 Matthew Hall 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:
+ * Matthew Hall - initial API and implementation
+ */
+package org.eclipse.sirius.table.ui.tools.internal.paperclips;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.eclipse.sirius.table.ui.tools.internal.paperclips.internal.util.PaperClipsUtil;
+import org.eclipse.sirius.table.ui.tools.internal.paperclips.internal.util.Util;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.graphics.GC;
+import org.eclipse.swt.graphics.Point;
+import org.eclipse.swt.graphics.Rectangle;
+import org.eclipse.swt.printing.Printer;
+import org.eclipse.swt.printing.PrinterData;
+
+/**
+ * This class contains static constants and methods for preparing and printing
+ * documents. Methods in this class supersede those in PrintUtil.
+ *
+ * @author Matthew Hall
+ */
+public class PaperClips {
+ private PaperClips() {
+ } // no instances
+
+ static boolean debug = false;
+
+ /**
+ * Indicates that the printer's default page orientation should be used.
+ */
+ public static final int ORIENTATION_DEFAULT = SWT.DEFAULT;
+
+ /**
+ * Indicates portrait page orientation.
+ */
+ public static final int ORIENTATION_PORTRAIT = SWT.VERTICAL;
+
+ /**
+ * Indicates landscape page orientation.
+ */
+ public static final int ORIENTATION_LANDSCAPE = SWT.HORIZONTAL;
+
+ /**
+ * Triggers an appropriate exception based on the passed in error code.
+ *
+ * @param code
+ * the SWT error code.
+ */
+ public static void error(int code) {
+ SWT.error(code, null);
+ }
+
+ /**
+ * Triggers an unspecified exception with the passed in detail.
+ *
+ * @param detail
+ * more information about error.
+ */
+ public static void error(String detail) {
+ SWT.error(SWT.ERROR_UNSPECIFIED, null, detail);
+ }
+
+ /**
+ * Triggers an appropriate exception based on the passed in error code.
+ *
+ * @param code
+ * the SWT error code.
+ * @param detail
+ * more information about error.
+ */
+ public static void error(int code, String detail) {
+ SWT.error(code, null, detail);
+ }
+
+ /**
+ * <b>EXPERIMENTAL</b>: Sets whether debug mode is enabled. This mode may be
+ * used for troubleshooting documents that cannot be laid out for some
+ * reason (e.g. "Cannot layout page x" error occurs).
+ *
+ * <p>
+ * <b>THIS API IS EXPERIMENTAL AND MAY BE REMOVED OR CHANGED IN THE
+ * FUTURE.</b>
+ *
+ * @param debug
+ * true to enable debug mode, false to disable it.
+ */
+ public static void setDebug(boolean debug) {
+ PaperClips.debug = debug;
+ }
+
+ /**
+ * <b>EXPERIMENTAL</b>: Returns whether debug mode is enabled.
+ *
+ * <p>
+ * <b>THIS API IS EXPERIMENTAL AND MAY BE REMOVED OR CHANGED IN THE
+ * FUTURE.</b>
+ *
+ * @return whether debug mode is enabled.
+ */
+ public static boolean getDebug() {
+ return debug;
+ }
+
+ /**
+ * Returns a PrinterData for the system-default printer, or the first
+ * printer if no default printer is configured.
+ *
+ * @return a PrinterData for the system-default printer, or the first
+ * printer if no default printer is configured.
+ */
+ public static PrinterData getDefaultPrinterData() {
+ PrinterData printerData = Printer.getDefaultPrinterData();
+ if (printerData == null) {
+ // Linux may have one or more printers without a default printer
+ PrinterData[] list = Printer.getPrinterList();
+ if (list.length > 0)
+ printerData = list[0];
+ }
+ return printerData;
+ }
+
+ /**
+ * Calls iterator.next(width, height) and returns the result. This method
+ * checks multiple conditions to ensure proper usage and behavior of
+ * PrintIterators.
+ * <p>
+ * This method is intended to be used by PrintIterator classes, as a
+ * results-checking alternative to calling next(int, int) directly on the
+ * target iterator. All PrintIterator classes in the PaperClips library use
+ * this method instead of directly calling the
+ * {@link PrintIterator#next(int, int)} method.
+ *
+ * @param iterator
+ * the PrintIterator
+ * @param width
+ * the available width.
+ * @param height
+ * the available height.
+ * @return the next portion of the Print, or null if the width and height
+ * are not enough to display any of the iterator's contents.
+ */
+ public static PrintPiece next(PrintIterator iterator, int width, int height) {
+ Util.notNull(iterator);
+ if (width < 0 || height < 0)
+ error(SWT.ERROR_INVALID_ARGUMENT,
+ "PrintPiece size " + width + "x" + height + " not possible"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+ if (!iterator.hasNext())
+ error("Iterator " + iterator + " has no more content."); //$NON-NLS-1$ //$NON-NLS-2$
+
+ PrintPiece result = iterator.next(width, height);
+
+ if (result != null) {
+ Point size = result.getSize();
+ if (size.x > width || size.y > height)
+ error("Iterator " + iterator + " produced a " + size.x + "x" + size.y + " piece for a " + width //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
+ + "x" + height + " area."); //$NON-NLS-1$//$NON-NLS-2$
+ } else if (debug) {
+ return new NullPrintPiece();
+ }
+ return result;
+ }
+
+ /**
+ * Prints the print job to the given printer. This method constructs a
+ * Printer, forwards to {@link #print(PrintJob, Printer)}, and disposes the
+ * printer before returning.
+ *
+ * @param printJob
+ * the print job.
+ * @param printerData
+ * the PrinterData of the selected printer.
+ */
+ public static void print(PrintJob printJob, PrinterData printerData) {
+ Printer printer = new Printer(printerData);
+ try {
+ print(printJob, printer);
+ } finally {
+ printer.dispose();
+ }
+ }
+
+ /**
+ * Prints the print job to the given printer.
+ *
+ * @param printJob
+ * the print job.
+ * @param printer
+ * the printer device.
+ */
+ public static void print(PrintJob printJob, Printer printer) {
+ // Bug in SWT on OSX: If Printer.startJob() is not called first, the GC
+ // will be disposed by
+ // default.
+ startJob(printer, printJob.getName());
+
+ boolean completed = false;
+ try {
+ GC gc = createAndConfigureGC(printer);
+ try {
+ print(printJob, printer, gc);
+ } finally {
+ gc.dispose();
+ }
+ printer.endJob();
+ completed = true;
+ } finally {
+ if (!completed)
+ cancelJob(printer);
+ }
+ }
+
+ private static void startJob(Printer printer, String jobName) {
+ if (!printer.startJob(jobName))
+ error("Unable to start print job"); //$NON-NLS-1$
+ }
+
+ private static void cancelJob(Printer printer) {
+ if (isGTK())
+ printer.endJob(); // Printer.cancelJob() not implemented on GTK
+ else
+ printer.cancelJob();
+ }
+
+ private static GC createAndConfigureGC(Printer printer) {
+ GC gc = new GC(printer);
+ gc.setAdvanced(true);
+ return gc;
+ }
+
+ /**
+ * Prints the print job to the specified printer using the GC. This method
+ * does not manage the print job lifecycle (it does not call startJob or
+ * endJob).
+ *
+ * @param printJob
+ * the print job
+ * @param printer
+ * the printer
+ * @param gc
+ * the GC
+ */
+ private static void print(PrintJob printJob, Printer printer, final GC gc) {
+ final PrinterData printerData = printer.getPrinterData();
+
+ PrintPiece[] pages = getPages(printJob, printer, gc);
+
+ int startPage = 0;
+ int endPage = pages.length - 1;
+ if (printerData.scope == PrinterData.PAGE_RANGE) {
+ // Convert from PrinterData's one-based indices to our zero-based
+ // indices
+ startPage = Math.max(startPage, printerData.startPage - 1);
+ endPage = Math.min(endPage, printerData.endPage - 1);
+ }
+
+ final int collatedCopies;
+ final int noncollatedCopies;
+ if (printerData.collate) { // always false if printer driver performs
+ // collation
+ collatedCopies = printerData.copyCount; // always 1 if printer
+ // driver handles copy count
+ noncollatedCopies = 1;
+ } else {
+ noncollatedCopies = printerData.copyCount; // always 1 if printer
+ // driver handles copy
+ // count
+ collatedCopies = 1;
+ }
+
+ printPages(printer, gc, pages, startPage, endPage, collatedCopies,
+ noncollatedCopies);
+ }
+
+ private static void printPages(final Printer printer, final GC gc,
+ final PrintPiece[] pages, final int startPage, final int endPage,
+ final int collatedCopies, final int noncollatedCopies) {
+ disposeUnusedPages(pages, startPage, endPage);
+
+ Rectangle paperBounds = getPaperBounds(printer);
+ final int x = paperBounds.x;
+ final int y = paperBounds.y;
+
+ try {
+ for (int collated = 0; collated < collatedCopies; collated++) {
+ for (int pageIndex = startPage; pageIndex <= endPage; pageIndex++) {
+ for (int noncollated = 0; noncollated < noncollatedCopies; noncollated++) {
+ if (printer.startPage()) {
+ pages[pageIndex].paint(gc, x, y);
+ pages[pageIndex].dispose();
+ printer.endPage();
+ } else {
+ error("Unable to start page " + pageIndex); //$NON-NLS-1$
+ }
+ }
+ }
+ }
+ } finally {
+ PaperClipsUtil.dispose(pages);
+ }
+ }
+
+ private static void disposeUnusedPages(PrintPiece[] pages, int startPage,
+ int endPage) {
+ PaperClipsUtil.dispose(pages, 0, startPage);
+ PaperClipsUtil.dispose(pages, endPage + 1, pages.length);
+ }
+
+ /**
+ * Processes the print job and returns an array of pages for the given
+ * printer device. Each element in the returned array has already had the
+ * page orientation and page margins applied. Therefore, when calling the
+ * paint(GC, int, int) method on each page, the printer's trim should be
+ * provided as the x and y arguments. In other words, the trim is taken as a
+ * minimum margin while applying calculating margins, but the position where
+ * the page's content is drawn is determined solely by the margin, and is
+ * not offset by the trim. This behavior is helpful for screen display, and
+ * is already compensated for in the {@link #print(PrintJob, Printer)}
+ * method.
+ *
+ * @param printer
+ * the printing device.
+ * @param printJob
+ * the print job.
+ * @return an array of all pages of the print job. Each element of the
+ * returned array represents one page in the printed document.
+ */
+ public static PrintPiece[] getPages(PrintJob printJob, Printer printer) {
+ startDummyJob(printer, printJob.getName());
+
+ try {
+ GC gc = createAndConfigureGC(printer);
+ try {
+ return getPages(printJob, printer, gc);
+ } finally {
+ gc.dispose();
+ }
+ } finally {
+ endDummyJob(printer);
+ }
+ }
+
+ /**
+ * Starts a dummy job on the given Printer if the platform requires it.
+ * Dummy jobs allow the various Print components of PaperClips to perform
+ * measurements required for document layout, without actually sending a job
+ * to the printer. Only Mac OS X Carbon and Linux GTK+ are known to require
+ * dummy jobs.
+ *
+ * @param printer
+ * the Printer hosting the dummy print job.
+ * @param name
+ * the name of the dummy print job.
+ */
+ public static void startDummyJob(Printer printer, String name) {
+ // On Mac OS X Carbon and Linux GTK+, created GC is disposed unless
+ // Printer.startJob() is called
+ // first.
+ if (isCarbon() || isGTK())
+ startJob(printer, name);
+ }
+
+ /**
+ * Ends a dummy job on the given Printer if the platform requires a dummy
+ * job.
+ *
+ * @param printer
+ * the Printer hosting the dummy print job.
+ */
+ public static void endDummyJob(Printer printer) {
+ if (isGTK()) { // Linux GTK
+ // Printer.cancelJob() is not implemented in SWT since GTK has no
+ // API for cancelling a print job. For now we must use endJob(),
+ // even though it spits out an empty page.
+
+ // printer.cancelJob(); // Not implemented in SWT on GTK
+ printer.endJob();
+
+ // See also:
+ // http://bugzilla.gnome.org/show_bug.cgi?id=339323
+ // https://bugs.eclipse.org/bugs/show_bug.cgi?id=212594
+ } else if (isCarbon()) // Mac OSX
+ // 2007-04-30: A bug in SWT on Mac OSX prior to 3.3 renders Printer
+ // instances useless after a call to cancelJob().
+ // Therefore on Mac OSX we call endJob() instead of cancelJob().
+ if (SWT.getVersion() < 3346) { // Version 3.3
+ printer.endJob();
+ } else {
+ printer.cancelJob();
+ }
+ }
+
+ private static boolean isCarbon() {
+ return SWT.getPlatform().equals("carbon"); //$NON-NLS-1$
+ }
+
+ private static boolean isGTK() {
+ return SWT.getPlatform().equals("gtk"); //$NON-NLS-1$
+ }
+
+ private static PrintPiece[] getPages(PrintJob printJob, Printer printer,
+ GC gc) {
+ PageEnumeration enumeration = new PageEnumeration(printJob, printer, gc);
+ List pages = new ArrayList();
+ while (enumeration.hasNext()) {
+ PrintPiece page = enumeration.nextPage();
+ if (page == null) {
+ int pageNumber = pages.size() + 1;
+ PaperClipsUtil.dispose(pages);
+ error("Unable to layout page " + pageNumber); //$NON-NLS-1$
+ }
+ pages.add(page);
+ }
+
+ return (PrintPiece[]) pages.toArray(new PrintPiece[pages.size()]);
+ }
+
+ /**
+ * Returns a {@link PageEnumeration} for the passed in PrintJob on the given
+ * Printer, using the given GC. The Printer and GC must not be disposed
+ * while the enumeration is in use.
+ *
+ * @param printJob
+ * the print job
+ * @param printer
+ * the Printer device, which must not be disposed while the
+ * PageEnumeration is in use.
+ * @param gc
+ * the GC, which must not be disposed while the PageEnumeration
+ * is in use.
+ * @return a {@link PageEnumeration} for the passed in PrintJob.
+ */
+ public static PageEnumeration getPageEnumeration(PrintJob printJob,
+ Printer printer, GC gc) {
+ return new PageEnumeration(printJob, printer, gc);
+ }
+
+ /**
+ * Returns the bounding rectangle of the paper, including non-printable
+ * margins.
+ *
+ * @param printer
+ * the printer device.
+ * @return a rectangle whose edges correspond to the edges of the paper.
+ */
+ public static Rectangle getPaperBounds(Printer printer) {
+ Rectangle rect = getPrintableBounds(printer);
+ return printer.computeTrim(rect.x, rect.y, rect.width, rect.height);
+ }
+
+ /**
+ * Returns the bounding rectangle of the printable area on the paper.
+ *
+ * @param printer
+ * the printer device.
+ * @return the bounding rectangle of the printable area on the paper.
+ */
+ public static Rectangle getPrintableBounds(Printer printer) {
+ return printer.getClientArea();
+ }
+
+ /**
+ * Returns the bounding rectangle of the printable area which is inside the
+ * given margins on the paper. The printer's minimum margins are reflected
+ * in the returned rectangle.
+ *
+ * @param printer
+ * the printer device.
+ * @param margins
+ * the desired page margins.
+ * @return the bounding rectangle on the printable area which is within the
+ * margins.
+ */
+ public static Rectangle getMarginBounds(Margins margins, Printer printer) {
+ Rectangle paperBounds = getPaperBounds(printer);
+
+ // Calculate the pixel coordinates for the margins
+ Point dpi = printer.getDPI();
+ int top = paperBounds.y + (margins.top * dpi.y / 72);
+ int left = paperBounds.x + (margins.left * dpi.x / 72);
+ int right = paperBounds.x + paperBounds.width
+ - (margins.right * dpi.x / 72);
+ int bottom = paperBounds.y + paperBounds.height
+ - (margins.bottom * dpi.y / 72);
+
+ // Enforce the printer's minimum margins.
+ Rectangle printableBounds = getPrintableBounds(printer);
+ if (top < printableBounds.y)
+ top = printableBounds.y;
+ if (left < printableBounds.x)
+ left = printableBounds.x;
+ if (right > printableBounds.x + printableBounds.width)
+ right = printableBounds.x + printableBounds.width;
+ if (bottom > printableBounds.y + printableBounds.height)
+ bottom = printableBounds.y + printableBounds.height;
+
+ return new Rectangle(left, top, right - left, bottom - top);
+ }
+} \ No newline at end of file
diff --git a/plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/Print.java b/plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/Print.java
new file mode 100644
index 0000000000..f9d72fda1c
--- /dev/null
+++ b/plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/Print.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (c) 2005 Matthew Hall 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:
+ * Matthew Hall - initial API and implementation
+ */
+package org.eclipse.sirius.table.ui.tools.internal.paperclips;
+
+import org.eclipse.swt.graphics.Device;
+import org.eclipse.swt.graphics.GC;
+
+/**
+ * Interface for printable elements.
+ *
+ * @author Matthew Hall
+ */
+public interface Print {
+ /**
+ * Returns a PrintIterator for laying out the contents of this Print. The
+ * iterator uses a snapshot of the print at the time this method is invoked,
+ * so subsequent changes to the Print will not affect the output of the
+ * iterator.
+ *
+ * @param device
+ * the graphics device this Print will be drawn onto.
+ * @param gc
+ * the graphics context to be used for calculating layout and
+ * drawing the Print's contents.
+ * @return a PrintIterator for laying out the contents of this Print.
+ */
+ public PrintIterator iterator(Device device, GC gc);
+} \ No newline at end of file
diff --git a/plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/PrintIterator.java b/plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/PrintIterator.java
new file mode 100644
index 0000000000..62932840ab
--- /dev/null
+++ b/plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/PrintIterator.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright (c) 2005 Matthew Hall 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:
+ * Matthew Hall - initial API and implementation
+ */
+package org.eclipse.sirius.table.ui.tools.internal.paperclips;
+
+import org.eclipse.swt.graphics.Device;
+import org.eclipse.swt.graphics.GC;
+import org.eclipse.swt.graphics.Point;
+
+/**
+ * Splits a Print into multiple PrintPieces, according to the space available on
+ * the graphics device. PrintIterators are created by
+ * {@link Print#iterator(Device, GC)}, and are initialized with the graphics
+ * device passed to that method.
+ *
+ * @author Matthew Hall
+ */
+public interface PrintIterator {
+ /**
+ * Identifies whether any PrintPieces remain.
+ *
+ * @return whether any PrintPieces remain.
+ */
+ public boolean hasNext();
+
+ /**
+ * Returns the next PrintPiece for the Print.
+ * <p>
+ * If all of the remaining contents of the Print will fit in the given
+ * space, the returned PrintPiece will include all remaining contents, and
+ * subsequent calls to {@link PrintIterator#hasNext() } will return
+ * <code>false</code>.
+ * <p>
+ * If some, but not all of the remaining contents will fit in the given
+ * space, the returned PrintPiece will contain as much of the contents as
+ * possible, and subsequent calls to {@link PrintIterator#hasNext() } will
+ * return <code>true</code>.
+ * <p>
+ * If there is insufficient space for any of the remaining contents in the
+ * given space, <code>null</code> is returned, and subsequent calls to
+ * {@link PrintIterator#hasNext() } will return <code>true</code>.
+ * <p>
+ * If subsequent calls to PrintIterator#hasNext() return <code>true</code>,
+ * this PrintIterator cannot fit any more in the given print area. Future
+ * calls to this method should provide a fresh print area. At the top level,
+ * each returned PrintPiece contains an entire page.
+ * <p>
+ * <b>Note</b>: PrintIterator classes should call
+ * {@link PaperClips#next(PrintIterator, int, int)} instead of calling this
+ * method directly, to gain automatic results checking to ensure all Print
+ * classes are well-behaved.
+ *
+ * @param width
+ * the width available on the graphics device for this iteration.
+ * @param height
+ * the height available on the graphics device for this
+ * iteration.
+ * @return a PrintPiece that paints the next part of the Print, or null if
+ * the print area is too small. The size of the returned PrintPiece
+ * must NOT exceed the width and height indicated.
+ */
+ public PrintPiece next(int width, int height);
+
+ /**
+ * Returns the minimum size PrintPiece that this Print should be broken
+ * into.
+ * <p>
+ * Note that the size calculated by this method is a "preferred minimum," or
+ * the smallest size that the Print should normally be broken into. For a
+ * TextPrint, this is the size of the widest individual word, in pixels.
+ * <p>
+ * This is distinct from the "absolute minimum," which is the smallest size
+ * that a Print could possibly be broken into. For a TextPrint, this is the
+ * size of the widest individual <em>letter</em>, in pixels.
+ *
+ * @return a Point indicating the minimum size PrintPiece this PrintIterator
+ * should be broken into.
+ */
+ public Point minimumSize();
+
+ /**
+ * Returns the smallest size PrintPiece that this Print would be broken into
+ * if print space was unlimited.
+ * <p>
+ * For a TextPrint, this is the size of the widest line (or the whole
+ * TextPrint, if there are no line breaks), in pixels.
+ *
+ * @return a Point indicating the smallest size PrintPiece that this Print
+ * would be broken into if print space was unlimited.
+ */
+ public Point preferredSize();
+
+ /**
+ * Returns a copy of this PrintIterator, with all relevant internal states.
+ * This method allows a containing iterator to "back up" the current state
+ * of its child iterators before invoking <code>next(int, int)</code> on
+ * them. The containing iterator can then safely attempt iterating its
+ * child(ren) in a variety of ways before selecting which way is the most
+ * appropriate.
+ *
+ * @return a deep clone of the target with all relevant internal states.
+ */
+ public PrintIterator copy();
+} \ No newline at end of file
diff --git a/plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/PrintJob.java b/plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/PrintJob.java
new file mode 100644
index 0000000000..f82489249c
--- /dev/null
+++ b/plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/PrintJob.java
@@ -0,0 +1,170 @@
+/*
+ * Copyright (c) 2005 Matthew Hall 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:
+ * Matthew Hall - initial API and implementation
+ */
+package org.eclipse.sirius.table.ui.tools.internal.paperclips;
+
+import org.eclipse.sirius.table.ui.tools.internal.paperclips.internal.util.Util;
+
+/**
+ * Instances of this class represent a prepared print job.
+ *
+ * @author Matthew Hall
+ */
+public class PrintJob {
+ private final String name;
+ private final Print document;
+
+ private Margins margins = new Margins();
+
+ private int orientation = PaperClips.ORIENTATION_DEFAULT;
+
+ /**
+ * Constructs a PrintJob for the given document.
+ *
+ * @param name
+ * the name of the print job, which will appear in the print
+ * queue of the operating system.
+ * @param document
+ * the document to be printed.
+ */
+ public PrintJob(String name, Print document) {
+ Util.notNull(name, document);
+ this.name = name;
+ this.document = document;
+ }
+
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result
+ + ((document == null) ? 0 : document.hashCode());
+ result = prime * result + ((margins == null) ? 0 : margins.hashCode());
+ result = prime * result + ((name == null) ? 0 : name.hashCode());
+ result = prime * result + orientation;
+ return result;
+ }
+
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (obj == null)
+ return false;
+ if (getClass() != obj.getClass())
+ return false;
+ PrintJob other = (PrintJob) obj;
+ if (document == null) {
+ if (other.document != null)
+ return false;
+ } else if (!document.equals(other.document))
+ return false;
+ if (margins == null) {
+ if (other.margins != null)
+ return false;
+ } else if (!margins.equals(other.margins))
+ return false;
+ if (name == null) {
+ if (other.name != null)
+ return false;
+ } else if (!name.equals(other.name))
+ return false;
+ if (orientation != other.orientation)
+ return false;
+ return true;
+ }
+
+ /**
+ * Returns the name of the print job.
+ *
+ * @return the name of the print job.
+ */
+ public String getName() {
+ return name;
+ }
+
+ /**
+ * Returns the document to be printed.
+ *
+ * @return the document to be printed.
+ */
+ public Print getDocument() {
+ return document;
+ }
+
+ /**
+ * Returns the page orientation.
+ *
+ * @return the page orientation.
+ */
+ public int getOrientation() {
+ return orientation;
+ }
+
+ /**
+ * Sets the page orientation.
+ *
+ * @param orientation
+ * the page orientation. Must be one of
+ * {@link PaperClips#ORIENTATION_DEFAULT },
+ * {@link PaperClips#ORIENTATION_PORTRAIT } or
+ * {@link PaperClips#ORIENTATION_LANDSCAPE }. Values other than
+ * these choices will be automatically changed to
+ * {@link PaperClips#ORIENTATION_DEFAULT }.
+ * @return this PrintJob (for chaining method calls)
+ */
+ public PrintJob setOrientation(int orientation) {
+ this.orientation = checkOrientation(orientation);
+ return this;
+ }
+
+ private int checkOrientation(int orientation) {
+ switch (orientation) {
+ case PaperClips.ORIENTATION_LANDSCAPE:
+ case PaperClips.ORIENTATION_PORTRAIT:
+ case PaperClips.ORIENTATION_DEFAULT:
+ return orientation;
+ default:
+ return PaperClips.ORIENTATION_DEFAULT;
+ }
+ }
+
+ /**
+ * Returns the page margins, expressed in points. 72 points = 1".
+ *
+ * @return the page margins, expressed in points. 72 points = 1".
+ */
+ public Margins getMargins() {
+ return margins;
+ }
+
+ /**
+ * Sets the page margins.
+ *
+ * @param margins
+ * the new page margins.
+ * @return this PrintJob (for chaining method calls)
+ */
+ public PrintJob setMargins(Margins margins) {
+ Util.notNull(margins);
+ this.margins = margins;
+ return this;
+ }
+
+ /**
+ * Sets the top, left, right, and bottom margins to the argument.
+ *
+ * @param margins
+ * the margins, in points. 72 points = 1 inch.
+ * @return this PrintJob (for chaining method calls)
+ */
+ public PrintJob setMargins(int margins) {
+ this.margins = new Margins(margins);
+ return this;
+ }
+}
diff --git a/plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/PrintPiece.java b/plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/PrintPiece.java
new file mode 100644
index 0000000000..eaabccc77d
--- /dev/null
+++ b/plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/PrintPiece.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) 2005 Matthew Hall 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:
+ * Matthew Hall - initial API and implementation
+ */
+package org.eclipse.sirius.table.ui.tools.internal.paperclips;
+
+import org.eclipse.swt.graphics.GC;
+import org.eclipse.swt.graphics.Point;
+
+/**
+ * A piece of a Print, which is capable of drawing itself on a graphics device.
+ * PrintPiece objects are created by a PrintIterator.
+ *
+ * @author Matthew
+ */
+public interface PrintPiece {
+ /**
+ * Returns the dimensions of this PrintPiece, in pixels.
+ *
+ * @return the dimensions of this PrintPiece, in pixels.
+ */
+ public Point getSize();
+
+ /**
+ * Draws this PrintPiece on the given graphics device, at the given
+ * coordinates.
+ *
+ * @param gc
+ * a graphics context for the graphics device.
+ * @param x
+ * the x coordinate where this PrintPiece will be drawn.
+ * @param y
+ * the x coordinate where this PrintPiece will be drawn.
+ */
+ public void paint(GC gc, int x, int y);
+
+ /**
+ * Disposes the system resources allocated by this PrintPiece. The dispose
+ * method is <b>not</b> a permanent disposal of a PrintPiece. It is intended
+ * to reclaim system resources, however future calls to paint(GC,int,int)
+ * may require that the resources be allocated again.
+ */
+ public void dispose();
+}
diff --git a/plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/RotatePrint.java b/plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/RotatePrint.java
new file mode 100644
index 0000000000..fe44996912
--- /dev/null
+++ b/plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/RotatePrint.java
@@ -0,0 +1,210 @@
+/*
+ * Copyright (c) 2006 Matthew Hall 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:
+ * Matthew Hall - initial API and implementation
+ */
+package org.eclipse.sirius.table.ui.tools.internal.paperclips;
+
+import org.eclipse.sirius.table.ui.tools.internal.paperclips.internal.RotatePiece;
+import org.eclipse.sirius.table.ui.tools.internal.paperclips.internal.util.Util;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.graphics.Device;
+import org.eclipse.swt.graphics.GC;
+import org.eclipse.swt.graphics.Point;
+
+/**
+ * A decorator print that rotates it's target by increments of 90 degrees.
+ * <p>
+ * <em>Note</em>: On Windows, this class depends on a bugfix available as of
+ * Eclipse build 3.2, release candidate 3 (2006-04-28). Prior to this release,
+ * using RotatePrint triggers the bug, causing the document to scale very large
+ * on paper. This bug only manifests itself on paper, not with on-screen
+ * viewing.
+ * <p>
+ * RotatePrints are horizontally and vertically greedy. Greedy prints take up
+ * all the available space on the page.
+ *
+ * @author Matthew Hall
+ */
+public final class RotatePrint implements Print {
+ private final Print target;
+ private final int angle;
+
+ /**
+ * Constructs a RotatePrint that rotates it's target 90 degrees
+ * counter-clockwise.
+ *
+ * @param target
+ * the print to rotate.
+ */
+ public RotatePrint(Print target) {
+ this(target, 90);
+ }
+
+ /**
+ * Constructs a RotatePrint.
+ *
+ * @param target
+ * the print to rotate.
+ * @param angle
+ * the angle by which the target will be rotated, expressed in
+ * degrees counter-clockwise. Positive values rotate
+ * counter-clockwise, and negative values rotate clockwise. Must
+ * be a multiple of 90.
+ */
+ public RotatePrint(Print target, int angle) {
+ Util.notNull(target);
+ this.target = target;
+ this.angle = checkAngle(angle);
+ }
+
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + angle;
+ result = prime * result + ((target == null) ? 0 : target.hashCode());
+ return result;
+ }
+
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (obj == null)
+ return false;
+ if (getClass() != obj.getClass())
+ return false;
+ RotatePrint other = (RotatePrint) obj;
+ if (angle != other.angle)
+ return false;
+ if (target == null) {
+ if (other.target != null)
+ return false;
+ } else if (!target.equals(other.target))
+ return false;
+ return true;
+ }
+
+ /**
+ * Returns the print to be rotated.
+ *
+ * @return the print to be rotated.
+ */
+ public Print getTarget() {
+ return target;
+ }
+
+ /**
+ * Returns the angle by which the target will be rotated (one of 0, 90, 180,
+ * or 270).
+ *
+ * @return the angle by which the target will be rotated.
+ */
+ public int getAngle() {
+ return angle;
+ }
+
+ private static int checkAngle(int angle) {
+ // Make sure angle is a multiple of 90.
+ if (Math.abs(angle) % 90 != 0)
+ PaperClips.error(SWT.ERROR_INVALID_ARGUMENT,
+ "Angle must be a multiple of 90 degrees"); //$NON-NLS-1$
+
+ // Bring angle within the range [0, 360)
+ if (angle < 0)
+ angle = 360 - (-angle % 360);
+ if (angle >= 360)
+ angle = angle % 360;
+
+ return angle;
+ }
+
+ public PrintIterator iterator(Device device, GC gc) {
+ if (angle == 0)
+ return target.iterator(device, gc);
+ return new RotateIterator(target, angle, device, gc);
+ }
+}
+
+final class RotateIterator implements PrintIterator {
+ private final Device device;
+ private final PrintIterator target;
+ private final int angle;
+
+ private final Point minimumSize;
+ private final Point preferredSize;
+
+ RotateIterator(Print target, int angle, Device device, GC gc) {
+ Util.notNull(target, device, gc);
+
+ this.device = device;
+ this.target = target.iterator(device, gc);
+ this.angle = checkAngle(angle); // returns 90, 180, or 270 only
+
+ Point min = this.target.minimumSize();
+ Point pref = this.target.preferredSize();
+
+ if (this.angle == 180) {
+ this.minimumSize = new Point(min.x, min.y);
+ this.preferredSize = new Point(pref.x, pref.y);
+ } else { // flip x and y sizes if rotating by 90 or 270 degrees
+ this.minimumSize = new Point(min.y, min.x);
+ this.preferredSize = new Point(pref.y, pref.x);
+ }
+ }
+
+ private RotateIterator(RotateIterator that) {
+ this.device = that.device;
+ this.target = that.target.copy();
+ this.angle = that.angle;
+ this.minimumSize = that.minimumSize;
+ this.preferredSize = that.preferredSize;
+ }
+
+ private static int checkAngle(int angle) {
+ switch (angle) {
+ case 90:
+ case 180:
+ case 270:
+ break;
+ default:
+ PaperClips.error(SWT.ERROR_INVALID_ARGUMENT,
+ "Angle must be 90, 180, or 270"); //$NON-NLS-1$
+ }
+ return angle;
+ }
+
+ public Point minimumSize() {
+ return new Point(minimumSize.x, minimumSize.y);
+ }
+
+ public Point preferredSize() {
+ return new Point(preferredSize.x, preferredSize.y);
+ }
+
+ public boolean hasNext() {
+ return target.hasNext();
+ }
+
+ public PrintPiece next(int width, int height) {
+ PrintPiece target;
+ if (angle == 180) // angle may only be init'd to 90, 180, of 270
+ target = PaperClips.next(this.target, width, height);
+ else
+ // flip width and height if rotating by 90 or 270
+ target = PaperClips.next(this.target, height, width);
+
+ if (target == null)
+ return null;
+
+ return new RotatePiece(device, target, angle, new Point(width, height));
+ }
+
+ public PrintIterator copy() {
+ return new RotateIterator(this);
+ }
+}
diff --git a/plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/ScalePrint.java b/plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/ScalePrint.java
new file mode 100644
index 0000000000..6912820c5b
--- /dev/null
+++ b/plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/ScalePrint.java
@@ -0,0 +1,262 @@
+/*
+ * Copyright (c) 2006 Matthew Hall 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:
+ * Matthew Hall - initial API and implementation
+ */
+package org.eclipse.sirius.table.ui.tools.internal.paperclips;
+
+import org.eclipse.sirius.table.ui.tools.internal.paperclips.internal.util.Util;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.graphics.Device;
+import org.eclipse.swt.graphics.GC;
+import org.eclipse.swt.graphics.Point;
+import org.eclipse.swt.graphics.Transform;
+
+/**
+ * A decorator print that scales it's target larger or smaller.
+ * <p>
+ * <em>Note</em>: On Windows, this class depends on a bugfix available as of
+ * Eclipse build 3.2, release candidate 3 (2006-04-28). Prior to this release,
+ * using ScalePrint triggers the bug, causing the document to scale very large
+ * on paper. This bug manifests itself only on paper, not with on-screen
+ * viewing.
+ *
+ * @author Matthew Hall
+ */
+public class ScalePrint implements Print {
+ final Print target;
+ final Double scale;
+
+ /**
+ * Constructs a ScalePrint which scales down it's target to print at it's
+ * preferred size. This constructor is equivalent to calling new
+ * ScalePrint(target, null).
+ *
+ * @param target
+ * the print to scale down.
+ */
+ public ScalePrint(Print target) {
+ this(target, null);
+ }
+
+ /**
+ * Constructs a ScalePrint which scales it's target by the given factor.
+ *
+ * @param target
+ * @param scale
+ * the scale factor (must be >0). A value of 2.0 draws at double
+ * the size, and a value of 0.5 draws at half the size. A null
+ * value automatically scales down so the target is rendered at
+ * it's preferred size.
+ */
+ public ScalePrint(Print target, Double scale) {
+ Util.notNull(target);
+ if (scale != null && !(scale.doubleValue() > 0))
+ PaperClips.error(SWT.ERROR_INVALID_ARGUMENT,
+ "Scale " + scale + " must be > 0"); //$NON-NLS-1$ //$NON-NLS-2$
+
+ this.target = target;
+ this.scale = scale;
+ }
+
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + ((scale == null) ? 0 : scale.hashCode());
+ result = prime * result + ((target == null) ? 0 : target.hashCode());
+ return result;
+ }
+
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (obj == null)
+ return false;
+ if (getClass() != obj.getClass())
+ return false;
+ ScalePrint other = (ScalePrint) obj;
+ if (scale == null) {
+ if (other.scale != null)
+ return false;
+ } else if (!scale.equals(other.scale))
+ return false;
+ if (target == null) {
+ if (other.target != null)
+ return false;
+ } else if (!target.equals(other.target))
+ return false;
+ return true;
+ }
+
+ /**
+ * Returns the print being scaled.
+ *
+ * @return the print being scaled.
+ */
+ public Print getTarget() {
+ return target;
+ }
+
+ /**
+ * Returns the scale by which the target will be scaled, or null (indicating
+ * automatic scale down to fit).
+ *
+ * @return the scale by which the target will be scaled, or null (indicating
+ * automatic scale down to fit).
+ */
+ public Double getScale() {
+ return scale;
+ }
+
+ public PrintIterator iterator(Device device, GC gc) {
+ return new ScaleIterator(this, device, gc);
+ }
+}
+
+class ScaleIterator implements PrintIterator {
+ private final Device device;
+ private final PrintIterator target;
+ private final Double scale;
+
+ private final Point minimumSize;
+ private final Point preferredSize;
+
+ ScaleIterator(ScalePrint print, Device device, GC gc) {
+ Util.notNull(print, device, gc);
+
+ this.device = device;
+ this.target = print.target.iterator(device, gc);
+ this.scale = print.scale;
+
+ Point min = target.minimumSize();
+ Point pref = target.preferredSize();
+ if (scale == null) { // auto-scale
+ minimumSize = new Point(1, 1);
+ preferredSize = pref;
+ } else { // specific scale
+ double s = scale.doubleValue();
+ minimumSize = new Point((int) Math.ceil(min.x * s), (int) Math
+ .ceil(min.y * s));
+ preferredSize = new Point((int) Math.ceil(pref.x * s), (int) Math
+ .ceil(pref.y * s));
+ }
+ }
+
+ private ScaleIterator(ScaleIterator that) {
+ this.device = that.device;
+ this.target = that.target.copy();
+ this.scale = that.scale;
+
+ this.minimumSize = that.minimumSize;
+ this.preferredSize = that.preferredSize;
+ }
+
+ public Point minimumSize() {
+ return minimumSize;
+ }
+
+ public Point preferredSize() {
+ return preferredSize;
+ }
+
+ public boolean hasNext() {
+ return target.hasNext();
+ }
+
+ public PrintPiece next(int width, int height) {
+ // Find out what scale we're going to iterate at.
+ double scale;
+ Point pref = target.preferredSize();
+ if (this.scale == null)
+ scale = Math.min(Math.min((double) width / (double) pref.x,
+ (double) height / (double) pref.y), 1.0);
+ else
+ scale = this.scale.doubleValue();
+
+ // Calculate the width and height to be passed to the target.
+ final int scaledWidth = (int) Math.ceil(width / scale);
+ final int scaledHeight = (int) Math.ceil(height / scale);
+
+ PrintPiece target = PaperClips.next(this.target, scaledWidth,
+ scaledHeight);
+
+ if (target == null)
+ return null;
+
+ return new ScalePiece(device, target, scale, width, height);
+ }
+
+ public PrintIterator copy() {
+ return new ScaleIterator(this);
+ }
+}
+
+final class ScalePiece implements PrintPiece {
+ private final Device device;
+ private final PrintPiece target;
+ private final double scale;
+ private final Point size;
+
+ private Transform oldTransform;
+ private Transform transform;
+
+ ScalePiece(Device device, PrintPiece target, double scale, int maxWidth,
+ int maxHeight) {
+ Util.notNull(device, target);
+ this.device = device;
+ this.target = target;
+ this.scale = scale;
+ Point targetSize = target.getSize();
+ this.size = new Point(Math.min((int) Math.ceil(targetSize.x * scale),
+ maxWidth), Math.min((int) Math.ceil(targetSize.y * scale),
+ maxHeight));
+ }
+
+ public Point getSize() {
+ return new Point(size.x, size.y);
+ }
+
+ private Transform getOldTransform() {
+ if (oldTransform == null)
+ oldTransform = new Transform(device);
+ return oldTransform;
+ }
+
+ private Transform getTransform() {
+ if (transform == null)
+ transform = new Transform(device);
+ return transform;
+ }
+
+ public void paint(GC gc, int x, int y) {
+ Transform oldTransform = getOldTransform();
+ gc.getTransform(oldTransform);
+
+ Transform transform = getTransform();
+ gc.getTransform(transform);
+ transform.translate(x, y);
+ transform.scale((float) scale, (float) scale);
+ gc.setTransform(transform);
+
+ target.paint(gc, 0, 0);
+
+ gc.setTransform(oldTransform);
+ }
+
+ public void dispose() {
+ if (oldTransform != null) {
+ oldTransform.dispose();
+ oldTransform = null;
+ }
+ if (transform != null) {
+ transform.dispose();
+ transform = null;
+ }
+ target.dispose();
+ }
+} \ No newline at end of file
diff --git a/plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/SeriesPrint.java b/plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/SeriesPrint.java
new file mode 100644
index 0000000000..ff09ea57e7
--- /dev/null
+++ b/plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/SeriesPrint.java
@@ -0,0 +1,164 @@
+/*
+ * Copyright (c) 2005 Matthew Hall 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:
+ * Matthew Hall - initial API and implementation
+ */
+package org.eclipse.sirius.table.ui.tools.internal.paperclips;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.eclipse.sirius.table.ui.tools.internal.paperclips.internal.util.PrintSizeStrategy;
+import org.eclipse.sirius.table.ui.tools.internal.paperclips.internal.util.Util;
+import org.eclipse.swt.graphics.Device;
+import org.eclipse.swt.graphics.GC;
+import org.eclipse.swt.graphics.Point;
+
+/**
+ * A Print which displays its child prints in series. Each element in the series
+ * is displayed one at a time (no more than one child per page, although one
+ * Print may span several pages).
+ * <p>
+ * Use this class as the top-level Print when several distinct Prints should be
+ * batched into one print job, but printed on separate pages.
+ *
+ * @author Matthew Hall
+ */
+public class SeriesPrint implements Print {
+ final List items = new ArrayList();
+
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + ((items == null) ? 0 : items.hashCode());
+ return result;
+ }
+
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (obj == null)
+ return false;
+ if (getClass() != obj.getClass())
+ return false;
+ SeriesPrint other = (SeriesPrint) obj;
+ if (items == null) {
+ if (other.items != null)
+ return false;
+ } else if (!items.equals(other.items))
+ return false;
+ return true;
+ }
+
+ /**
+ * Adds the given prints to this SeriesPrint.
+ *
+ * @param items
+ * the Prints to add
+ */
+ public void add(Print[] items) {
+ Util.noNulls(items);
+ for (int i = 0; i < items.length; i++)
+ this.items.add(items[i]);
+ }
+
+ /**
+ * Adds the given print to this SeriesPrint.
+ *
+ * @param item
+ * the Print to add
+ */
+ public void add(Print item) {
+ Util.notNull(item);
+ items.add(item);
+ }
+
+ /**
+ * Returns the number of Prints that have been added to this SeriesPrint.
+ *
+ * @return the number of Prints that have been added to this SeriesPrint.
+ */
+ public int size() {
+ return items.size();
+ }
+
+ /**
+ * Returns an array of items in the series.
+ *
+ * @return an array of items in the series.
+ */
+ public Print[] getItems() {
+ return (Print[]) items.toArray(new Print[items.size()]);
+ }
+
+ public PrintIterator iterator(Device device, GC gc) {
+ return new SeriesIterator(this, device, gc);
+ }
+}
+
+class SeriesIterator implements PrintIterator {
+ final PrintIterator[] iters;
+ int index;
+
+ SeriesIterator(SeriesPrint print, Device device, GC gc) {
+ this.iters = new PrintIterator[print.items.size()];
+ for (int i = 0; i < iters.length; i++)
+ iters[i] = ((Print) print.items.get(i)).iterator(device, gc);
+
+ this.index = 0;
+ }
+
+ SeriesIterator(SeriesIterator that) {
+ this.iters = (PrintIterator[]) that.iters.clone();
+ for (int i = index; i < iters.length; i++)
+ this.iters[i] = that.iters[i].copy();
+
+ this.index = that.index;
+ }
+
+ public boolean hasNext() {
+ return index < iters.length;
+ }
+
+ private Point computeSize(PrintSizeStrategy strategy) {
+ int width = 0;
+ int height = 0;
+ for (int i = 0; i < iters.length; i++) {
+ PrintIterator iter = iters[i];
+ Point printSize = strategy.computeSize(iter);
+ width = Math.max(width, printSize.x);
+ height = Math.max(height, printSize.y);
+ }
+ return new Point(width, height);
+ }
+
+ public Point minimumSize() {
+ return computeSize(PrintSizeStrategy.MINIMUM);
+ }
+
+ public Point preferredSize() {
+ return computeSize(PrintSizeStrategy.PREFERRED);
+ }
+
+ public PrintPiece next(int width, int height) {
+ if (!hasNext())
+ PaperClips.error("No more content"); //$NON-NLS-1$
+
+ PrintIterator iter = iters[index];
+ PrintPiece printPiece = PaperClips.next(iter, width, height);
+
+ if (printPiece != null && !iter.hasNext())
+ index++;
+
+ return printPiece;
+ }
+
+ public PrintIterator copy() {
+ return new SeriesIterator(this);
+ }
+} \ No newline at end of file
diff --git a/plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/SidewaysPrint.java b/plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/SidewaysPrint.java
new file mode 100644
index 0000000000..2bdf5956d4
--- /dev/null
+++ b/plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/SidewaysPrint.java
@@ -0,0 +1,214 @@
+/*
+ * Copyright (c) 2006 Matthew Hall 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:
+ * Matthew Hall - initial API and implementation
+ */
+package org.eclipse.sirius.table.ui.tools.internal.paperclips;
+
+import org.eclipse.sirius.table.ui.tools.internal.paperclips.internal.RotatePiece;
+import org.eclipse.sirius.table.ui.tools.internal.paperclips.internal.util.Util;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.graphics.Device;
+import org.eclipse.swt.graphics.GC;
+import org.eclipse.swt.graphics.Point;
+
+/**
+ * A decorator print that rotates it's target by increments of 90 degrees.
+ * <p>
+ * <em>Note</em>: On Windows, this class depends on a bugfix available as of
+ * Eclipse build 3.2, release candidate 3 (2006-04-28). Prior to this release,
+ * using SidewaysPrint triggers the bug, causing the document to scale very
+ * large on paper. This bug only manifests itself on paper, not with on-screen
+ * viewing.
+ * <p>
+ * SidewaysPrint, unlike RotatePrint, is neither horizontally nor vertically
+ * greedy. Greedy prints take up all the available space on the page.
+ *
+ * @author Matthew Hall
+ */
+public final class SidewaysPrint implements Print {
+ private final Print target;
+ private final int angle;
+
+ /**
+ * Constructs a SidewaysPrint that rotates it's target 90 degrees
+ * counter-clockwise.
+ *
+ * @param target
+ * the print to rotate.
+ */
+ public SidewaysPrint(Print target) {
+ this(target, 90);
+ }
+
+ /**
+ * Constructs a SidewaysPrint.
+ *
+ * @param target
+ * the print to rotate.
+ * @param angle
+ * the angle by which the target will be rotated, expressed in
+ * degrees counter-clockwise. Positive values rotate
+ * counter-clockwise, and negative values rotate clockwise. Must
+ * be a multiple of 90.
+ */
+ public SidewaysPrint(Print target, int angle) {
+ Util.notNull(target);
+ this.target = target;
+ this.angle = checkAngle(angle);
+ }
+
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + angle;
+ result = prime * result + ((target == null) ? 0 : target.hashCode());
+ return result;
+ }
+
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (obj == null)
+ return false;
+ if (getClass() != obj.getClass())
+ return false;
+ SidewaysPrint other = (SidewaysPrint) obj;
+ if (angle != other.angle)
+ return false;
+ if (target == null) {
+ if (other.target != null)
+ return false;
+ } else if (!target.equals(other.target))
+ return false;
+ return true;
+ }
+
+ private static int checkAngle(int angle) {
+ // Make sure angle is a multiple of 90.
+ if (Math.abs(angle) % 90 != 0)
+ PaperClips.error(SWT.ERROR_INVALID_ARGUMENT,
+ "Angle must be a multiple of 90 degrees"); //$NON-NLS-1$
+
+ // Bring angle within the range [0, 360)
+ if (angle < 0)
+ angle = 360 - (-angle % 360);
+ if (angle >= 360)
+ angle = angle % 360;
+
+ return angle;
+ }
+
+ /**
+ * Returns the print to be rotated.
+ *
+ * @return the print to be rotated.
+ */
+ public Print getTarget() {
+ return target;
+ }
+
+ /**
+ * Returns the angle by which the target will be rotated (one of 0, 90, 180,
+ * or 270).
+ *
+ * @return the angle by which the target will be rotated.
+ */
+ public int getAngle() {
+ return angle;
+ }
+
+ public PrintIterator iterator(Device device, GC gc) {
+ if (angle == 0)
+ return target.iterator(device, gc);
+ return new SidewaysIterator(target, angle, device, gc);
+ }
+}
+
+final class SidewaysIterator implements PrintIterator {
+ private final Device device;
+ private final PrintIterator target;
+ private final int angle;
+
+ private final Point minimumSize;
+ private final Point preferredSize;
+
+ SidewaysIterator(Print target, int angle, Device device, GC gc) {
+ Util.notNull(target, device, gc);
+
+ this.device = device;
+ this.target = target.iterator(device, gc);
+ this.angle = checkAngle(angle); // returns 90, 180, or 270 only
+
+ Point min = this.target.minimumSize();
+ Point pref = this.target.preferredSize();
+
+ if (this.angle == 180) {
+ this.minimumSize = new Point(min.x, min.y);
+ this.preferredSize = new Point(pref.x, pref.y);
+ } else { // flip x and y sizes if rotating by 90 or 270 degrees
+ this.minimumSize = new Point(min.y, min.x);
+ this.preferredSize = new Point(pref.y, pref.x);
+ }
+ }
+
+ private SidewaysIterator(SidewaysIterator that) {
+ this.device = that.device;
+ this.target = that.target.copy();
+ this.angle = that.angle;
+ this.minimumSize = that.minimumSize;
+ this.preferredSize = that.preferredSize;
+ }
+
+ private static int checkAngle(int angle) {
+ switch (angle) {
+ case 90:
+ case 180:
+ case 270:
+ break;
+ default:
+ PaperClips.error(SWT.ERROR_INVALID_ARGUMENT,
+ "Angle must be 90, 180, or 270"); //$NON-NLS-1$
+ }
+ return angle;
+ }
+
+ public Point minimumSize() {
+ return new Point(minimumSize.x, minimumSize.y);
+ }
+
+ public Point preferredSize() {
+ return new Point(preferredSize.x, preferredSize.y);
+ }
+
+ public boolean hasNext() {
+ return target.hasNext();
+ }
+
+ public PrintPiece next(int width, int height) {
+ PrintPiece target;
+ if (angle == 180)
+ target = PaperClips.next(this.target, width, height);
+ else
+ // flip width and height if rotating by 90 or 270
+ target = PaperClips.next(this.target, height, width);
+
+ if (target == null)
+ return null;
+
+ Point size = target.getSize();
+ if (angle == 90 || angle == 270)
+ size = new Point(size.y, size.x);
+
+ return new RotatePiece(device, target, angle, size);
+ }
+
+ public PrintIterator copy() {
+ return new SidewaysIterator(this);
+ }
+} \ No newline at end of file
diff --git a/plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/border/AbstractBorderPainter.java b/plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/border/AbstractBorderPainter.java
new file mode 100644
index 0000000000..87d0bf309d
--- /dev/null
+++ b/plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/border/AbstractBorderPainter.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (c) 2005 Matthew Hall 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:
+ * Matthew Hall - initial API and implementation
+ */
+package org.eclipse.sirius.table.ui.tools.internal.paperclips.border;
+
+import org.eclipse.swt.graphics.GC;
+
+/**
+ * Abstract implementation of BorderPainter providing implementation of helper
+ * methods.
+ *
+ * @author Matthew Hall
+ */
+public abstract class AbstractBorderPainter implements BorderPainter {
+ /**
+ * Paints a border around the specified region. Depending on the type of
+ * border, the top and bottom of may be painted differently depending on the
+ * values of <code>topOpen</code> and <code>bottomOpen</code>.
+ */
+ public abstract void paint(GC gc, int x, int y, int width, int height,
+ boolean topOpen, boolean bottomOpen);
+
+ /**
+ * Returns the border inset, in pixels, from the left.
+ */
+ public abstract int getLeft();
+
+ /**
+ * Returns the border inset, in pixels, from the right.
+ */
+ public abstract int getRight();
+
+ /**
+ * Returns the sum of the left and right border insets.
+ */
+ public final int getWidth() {
+ return getLeft() + getRight();
+ }
+
+ /**
+ * Returns the border inset, in pixels, from the top.
+ */
+ public abstract int getTop(boolean open);
+
+ /**
+ * Returns the border inset, in pixels, from the bottom.
+ */
+ public abstract int getBottom(boolean open);
+
+ /**
+ * Returns the sum of the top and bottom border insets.
+ */
+ public final int getHeight(boolean topOpen, boolean bottomOpen) {
+ return getTop(topOpen) + getBottom(bottomOpen);
+ }
+
+ /**
+ * Returns the sum of the maximum top and bottom border insets.
+ */
+ public final int getMaxHeight() {
+ return Math.max(getTop(false), getTop(true))
+ + Math.max(getBottom(false), getBottom(true));
+ }
+}
diff --git a/plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/border/Border.java b/plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/border/Border.java
new file mode 100644
index 0000000000..6671e470df
--- /dev/null
+++ b/plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/border/Border.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (c) 2005 Matthew Hall 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:
+ * Matthew Hall - initial API and implementation
+ */
+package org.eclipse.sirius.table.ui.tools.internal.paperclips.border;
+
+import org.eclipse.swt.graphics.Device;
+import org.eclipse.swt.graphics.GC;
+
+/**
+ * Interface for drawing borders, used by BorderPaint and GridPrint for drawing
+ * borders a child print and grid cells, respectively.
+ *
+ * @author Matthew Hall
+ */
+public interface Border {
+ /**
+ * Creates a BorderPainter which uses the given Device and GC.
+ *
+ * @param device
+ * the print device.
+ * @param gc
+ * a GC for drawing to the print device.
+ * @return a BorderPainter for painting the border on the given Device and
+ * GC.
+ */
+ public BorderPainter createPainter(Device device, GC gc);
+}
diff --git a/plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/border/BorderPainter.java b/plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/border/BorderPainter.java
new file mode 100644
index 0000000000..ab9ba3ab97
--- /dev/null
+++ b/plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/border/BorderPainter.java
@@ -0,0 +1,133 @@
+/*
+ * Copyright (c) 2005 Matthew Hall 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:
+ * Matthew Hall - initial API and implementation
+ */
+package org.eclipse.sirius.table.ui.tools.internal.paperclips.border;
+
+import org.eclipse.sirius.table.ui.tools.internal.paperclips.grid.GridPrint;
+import org.eclipse.swt.graphics.GC;
+import org.eclipse.swt.graphics.Point;
+
+/**
+ * Interface for calculating and drawing borders in a BorderPrint.
+ *
+ * @author Matthew Hall
+ */
+public interface BorderPainter {
+ /**
+ * Returns the border inset, in pixels, from the left.
+ *
+ * @return the border inset, in pixels, from the left.
+ */
+ public int getLeft();
+
+ /**
+ * Returns the border inset, in pixels, from the right.
+ *
+ * @return the border inset, in pixels, from the right.
+ */
+ public int getRight();
+
+ /**
+ * Returns the sum of the left and right border insets.
+ *
+ * @return the sum of the left and right border insets.
+ */
+ public int getWidth();
+
+ /**
+ * Returns the border inset, in pixels, from the top.
+ *
+ * @param open
+ * If true, the inset of an open border will be returned. If
+ * false, the inset of a closed border will be returned.
+ * @return the border inset, in pixels, from the top.
+ */
+ public int getTop(boolean open);
+
+ /**
+ * Returns the border inset, in pixels, from the bottom.
+ *
+ * @param open
+ * If true, the inset of an open border will be returned. If
+ * false, the inset of a closed border will be returned.
+ * @return the border inset, in pixels, from the bottom.
+ */
+ public int getBottom(boolean open);
+
+ /**
+ * Returns the sum of the top and bottom border insets.
+ *
+ * @param topOpen
+ * If true, the inset of an open border will be returned. If
+ * false, the inset of a closed border will be returned.
+ * @param bottomOpen
+ * If true, the inset of an open border will be returned. If
+ * false, the inset of a closed border will be returned.
+ * @return the sum of the top and bottom border insets.
+ */
+ public int getHeight(boolean topOpen, boolean bottomOpen);
+
+ /**
+ * Returns the sum of the maximum top and bottom border insets.
+ *
+ * @return the sum of the maximum top and bottom border insets.
+ */
+ public int getMaxHeight();
+
+ /**
+ * Returns the x and y distance that two of the same BorderPainters would
+ * overlap to create the appearance of a single border between the two. This
+ * method is used by GridPrint whenever the horizontal and/or vertical
+ * spacing fields are set to {@link GridPrint#BORDER_OVERLAP }.
+ *
+ * @return the distance that this border painter would overlap an adjacent
+ * one.
+ */
+ public Point getOverlap();
+
+ /**
+ * Paints a border around the specified region. Depending on the type of
+ * border, the top and bottom of may be painted differently depending on the
+ * values of <code>topOpen</code> and <code>bottomOpen</code>.
+ *
+ * @param gc
+ * The graphics context to paint on.
+ * @param x
+ * The x coordinate of the top left corner of the border.
+ * @param y
+ * The y coordinate of the top left corner of the border.
+ * @param width
+ * The width of the border to paint
+ * @param height
+ * The height of the border to paint
+ * @param topOpen
+ * If true, the top border should be drawn "open," to indicate
+ * that this is the continuation of a border in a previous
+ * iteration. If false, the border should be drawn "closed" to
+ * indicate that this is the first iteration on the BorderPrint's
+ * target.
+ * @param bottomOpen
+ * If true, the bottom border should be drawn "open," to indicate
+ * that the BorderPrint's target was not consumed in this
+ * iteration. If false, the bottom border should be drawn
+ * "closed," to indicate that the BorderPrint's target completed
+ * during this iteration.
+ */
+ public void paint(GC gc, int x, int y, int width, int height,
+ boolean topOpen, boolean bottomOpen);
+
+ /**
+ * Disposes the system resources allocated by this BorderPainter. The
+ * dispose method is <b>not</b> a permanent disposal of a BorderPainter. It
+ * is intended to reclaim system resources, however future calls to
+ * paint(GC,int,int) may require that the resources be allocated again.
+ */
+ public void dispose();
+} \ No newline at end of file
diff --git a/plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/border/BorderPrint.java b/plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/border/BorderPrint.java
new file mode 100644
index 0000000000..2583c025a7
--- /dev/null
+++ b/plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/border/BorderPrint.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (c) 2005 Matthew Hall 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:
+ * Matthew Hall - initial API and implementation
+ */
+package org.eclipse.sirius.table.ui.tools.internal.paperclips.border;
+
+import org.eclipse.sirius.table.ui.tools.internal.paperclips.Print;
+import org.eclipse.sirius.table.ui.tools.internal.paperclips.PrintIterator;
+import org.eclipse.sirius.table.ui.tools.internal.paperclips.border.internal.BorderIterator;
+import org.eclipse.sirius.table.ui.tools.internal.paperclips.internal.util.Util;
+import org.eclipse.swt.graphics.Device;
+import org.eclipse.swt.graphics.GC;
+
+/**
+ * A decorator that draws a border around the target print.
+ *
+ * @author Matthew Hall
+ */
+public class BorderPrint implements Print {
+ final Print target;
+ final Border border;
+
+ /**
+ * Constructs a BorderPrint with the given target and border.
+ *
+ * @param target
+ * the print to decorate with a border.
+ * @param border
+ * the border which will be drawn around the target.
+ */
+ public BorderPrint(Print target, Border border) {
+ Util.notNull(target, border);
+ this.target = target;
+ this.border = border;
+ }
+
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + ((border == null) ? 0 : border.hashCode());
+ result = prime * result + ((target == null) ? 0 : target.hashCode());
+ return result;
+ }
+
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (obj == null)
+ return false;
+ if (getClass() != obj.getClass())
+ return false;
+ BorderPrint other = (BorderPrint) obj;
+ if (border == null) {
+ if (other.border != null)
+ return false;
+ } else if (!border.equals(other.border))
+ return false;
+ if (target == null) {
+ if (other.target != null)
+ return false;
+ } else if (!target.equals(other.target))
+ return false;
+ return true;
+ }
+
+ /**
+ * Returns the wrapped print to which the border is being applied.
+ *
+ * @return the wrapped print to which the border is being applied.
+ */
+ public Print getTarget() {
+ return target;
+ }
+
+ /**
+ * Returns the border being applied to the target.
+ *
+ * @return the border being applied to the target.
+ */
+ public Border getBorder() {
+ return border;
+ }
+
+ public PrintIterator iterator(Device device, GC gc) {
+ return new BorderIterator(this, device, gc);
+ }
+}
diff --git a/plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/border/GapBorder.java b/plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/border/GapBorder.java
new file mode 100644
index 0000000000..8e50249765
--- /dev/null
+++ b/plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/border/GapBorder.java
@@ -0,0 +1,178 @@
+/*
+ * Copyright (c) 2005 Matthew Hall 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:
+ * Matthew Hall - initial API and implementation
+ */
+package org.eclipse.sirius.table.ui.tools.internal.paperclips.border;
+
+import org.eclipse.sirius.table.ui.tools.internal.paperclips.PaperClips;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.graphics.Device;
+import org.eclipse.swt.graphics.GC;
+import org.eclipse.swt.graphics.Point;
+
+/**
+ * A border which leaves a gap around the target Print.
+ *
+ * @author Matthew Hall
+ */
+public class GapBorder implements Border {
+ /** The top gap of a closed border, expressed in points. */
+ public int top = 0;
+
+ /** The bottom gap of a closed border, expressed in points. */
+ public int bottom = 0;
+
+ /** The left side gap, expressed in points. */
+ public int left = 0;
+
+ /** The right side gap, expressed in points. */
+ public int right = 0;
+
+ /** The top gap of an open border, expressed in points. */
+ public int openTop = 0;
+
+ /** The bottom gap of an open border, expressed in points. */
+ public int openBottom = 0;
+
+ /**
+ * Constructs a GapBorder with 0 gap around all sides.
+ */
+ public GapBorder() {
+ this(0);
+ }
+
+ /**
+ * Constructs a GapBorder with the given gap around all sides.
+ *
+ * @param gap
+ * the gap, expressed in points.
+ */
+ public GapBorder(int gap) {
+ setGap(gap);
+ }
+
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + bottom;
+ result = prime * result + left;
+ result = prime * result + openBottom;
+ result = prime * result + openTop;
+ result = prime * result + right;
+ result = prime * result + top;
+ return result;
+ }
+
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (obj == null)
+ return false;
+ if (getClass() != obj.getClass())
+ return false;
+ GapBorder other = (GapBorder) obj;
+ if (bottom != other.bottom)
+ return false;
+ if (left != other.left)
+ return false;
+ if (openBottom != other.openBottom)
+ return false;
+ if (openTop != other.openTop)
+ return false;
+ if (right != other.right)
+ return false;
+ if (top != other.top)
+ return false;
+ return true;
+ }
+
+ /**
+ * Sets the left, right, closed top and closed bottom gaps to he argument.
+ *
+ * @param gap
+ * the gap, expressed in points.
+ */
+ public void setGap(int gap) {
+ top = left = bottom = right = checkGap(gap);
+ }
+
+ int checkGap(int gap) {
+ if (gap < 0)
+ PaperClips.error(SWT.ERROR_INVALID_ARGUMENT, "Gap must be >= 0"); //$NON-NLS-1$
+ return gap;
+ }
+
+ public BorderPainter createPainter(Device device, GC gc) {
+ return new GapBorderPainter(this, device);
+ }
+}
+
+class GapBorderPainter extends AbstractBorderPainter {
+ final int top;
+ final int openTop;
+ final int bottom;
+ final int openBottom;
+ final int left;
+ final int right;
+
+ GapBorderPainter(GapBorder target, Device device) {
+ Point dpi = device.getDPI();
+
+ this.top = toPixels(target.top, dpi.y);
+ this.bottom = toPixels(target.bottom, dpi.y);
+ this.openTop = toPixels(target.openTop, dpi.y);
+ this.openBottom = toPixels(target.openBottom, dpi.y);
+
+ this.left = toPixels(target.left, dpi.x);
+ this.right = toPixels(target.right, dpi.x);
+ }
+
+ GapBorderPainter(GapBorderPainter that) {
+ this.top = that.top;
+ this.bottom = that.bottom;
+ this.left = that.left;
+ this.right = that.right;
+
+ this.openTop = that.openTop;
+ this.openBottom = that.openBottom;
+ }
+
+ static int toPixels(int points, int dpi) {
+ return Math.max(0, points) * dpi / 72;
+ }
+
+ public int getBottom(boolean open) {
+ return open ? openBottom : bottom;
+ }
+
+ public int getLeft() {
+ return left;
+ }
+
+ public int getRight() {
+ return right;
+ }
+
+ public int getTop(boolean open) {
+ return open ? openTop : top;
+ }
+
+ public Point getOverlap() {
+ return new Point(Math.min(left, right), Math.max(top, bottom));
+ }
+
+ public void paint(GC gc, int x, int y, int width, int height,
+ boolean topOpen, boolean bottomOpen) {
+ // Nothing to paint.
+ }
+
+ public void dispose() {
+ // Nothing to dispose
+ }
+} \ No newline at end of file
diff --git a/plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/border/LineBorder.java b/plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/border/LineBorder.java
new file mode 100644
index 0000000000..b6fadba654
--- /dev/null
+++ b/plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/border/LineBorder.java
@@ -0,0 +1,212 @@
+/*
+ * Copyright (c) 2005 Matthew Hall 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:
+ * Matthew Hall - initial API and implementation
+ */
+package org.eclipse.sirius.table.ui.tools.internal.paperclips.border;
+
+import org.eclipse.sirius.table.ui.tools.internal.paperclips.internal.util.ResourcePool;
+import org.eclipse.sirius.table.ui.tools.internal.paperclips.internal.util.Util;
+import org.eclipse.swt.graphics.Color;
+import org.eclipse.swt.graphics.Device;
+import org.eclipse.swt.graphics.GC;
+import org.eclipse.swt.graphics.Point;
+import org.eclipse.swt.graphics.RGB;
+
+/**
+ * A border that draws a rectangle around a print.
+ *
+ * @author Matthew Hall
+ */
+public class LineBorder implements Border {
+ RGB rgb;
+ int lineWidth = 1; // in points
+ int gapSize = 5; // in points
+
+ /**
+ * Constructs a LineBorder with a black border and 5-pt insets. (72 pts =
+ * 1")
+ */
+ public LineBorder() {
+ this(new RGB(0, 0, 0)); // black
+ }
+
+ /**
+ * Constructs a LineBorder with 5-pt insets. (72 pts = 1")
+ *
+ * @param rgb
+ * the color to use for the border.
+ */
+ public LineBorder(RGB rgb) {
+ setRGB(rgb);
+ }
+
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + gapSize;
+ result = prime * result + lineWidth;
+ result = prime * result + ((rgb == null) ? 0 : rgb.hashCode());
+ return result;
+ }
+
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (obj == null)
+ return false;
+ if (getClass() != obj.getClass())
+ return false;
+ LineBorder other = (LineBorder) obj;
+ if (gapSize != other.gapSize)
+ return false;
+ if (lineWidth != other.lineWidth)
+ return false;
+ if (rgb == null) {
+ if (other.rgb != null)
+ return false;
+ } else if (!rgb.equals(other.rgb))
+ return false;
+ return true;
+ }
+
+ /**
+ * Sets the border color to the argument.
+ *
+ * @param rgb
+ * the new border color.
+ */
+ public void setRGB(RGB rgb) {
+ this.rgb = new RGB(rgb.red, rgb.green, rgb.blue);
+ }
+
+ /**
+ * Returns the border color.
+ *
+ * @return the border color.
+ */
+ public RGB getRGB() {
+ return new RGB(rgb.red, rgb.green, rgb.blue);
+ }
+
+ /**
+ * Sets the line width to the argument.
+ *
+ * @param points
+ * the line width, in points.
+ */
+ public void setLineWidth(int points) {
+ if (points < 1)
+ points = 1;
+
+ this.lineWidth = points;
+ }
+
+ /**
+ * Returns the line width of the border, expressed in points.
+ *
+ * @return the line width of the border, expressed in points.
+ */
+ public int getLineWidth() {
+ return lineWidth;
+ }
+
+ /**
+ * Sets the size of the gap between the line border and the target print.
+ *
+ * @param points
+ * the gap size, expressed in points.
+ */
+ public void setGapSize(int points) {
+ if (points < 1)
+ points = 1;
+
+ this.gapSize = points;
+ }
+
+ /**
+ * Returns the size of the gap between the line border and the target print,
+ * expressed in points.
+ *
+ * @return the gap size between the line border and the target print.
+ */
+ public int getGapSize() {
+ return Math.max(lineWidth, gapSize);
+ }
+
+ public BorderPainter createPainter(Device device, GC gc) {
+ return new LineBorderPainter(this, device, gc);
+ }
+}
+
+class LineBorderPainter extends AbstractBorderPainter {
+ private final Device device;
+ private final RGB rgb;
+ private final Point lineWidth;
+ private final Point borderWidth;
+
+ LineBorderPainter(LineBorder border, Device device, GC gc) {
+ Util.notNull(border, device, gc);
+ this.rgb = border.rgb;
+ this.device = device;
+
+ int lineWidthPoints = border.getLineWidth();
+ int borderWidthPoints = border.getGapSize();
+
+ Point dpi = device.getDPI();
+ lineWidth = new Point(Math.round(lineWidthPoints * dpi.x / 72f), Math
+ .round(lineWidthPoints * dpi.y / 72f));
+ borderWidth = new Point(Math.round(borderWidthPoints * dpi.x / 72f),
+ Math.round(borderWidthPoints * dpi.y / 72f));
+ }
+
+ public int getLeft() {
+ return borderWidth.x;
+ }
+
+ public int getRight() {
+ return borderWidth.x;
+ }
+
+ public int getTop(boolean open) {
+ return open ? 0 : borderWidth.y;
+ }
+
+ public int getBottom(boolean open) {
+ return open ? 0 : borderWidth.y;
+ }
+
+ public void paint(GC gc, int x, int y, int width, int height,
+ boolean topOpen, boolean bottomOpen) {
+ Color oldColor = gc.getBackground();
+
+ try {
+ gc.setBackground(ResourcePool.forDevice(device).getColor(rgb));
+
+ // Left & right
+ gc.fillRectangle(x, y, lineWidth.x, height);
+ gc.fillRectangle(x + width - lineWidth.x, y, lineWidth.x, height);
+
+ // Top & bottom
+ if (!topOpen)
+ gc.fillRectangle(x, y, width, lineWidth.y);
+ if (!bottomOpen)
+ gc.fillRectangle(x, y + height - lineWidth.y, width,
+ lineWidth.y);
+ } finally {
+ gc.setBackground(oldColor);
+ }
+ }
+
+ public Point getOverlap() {
+ return new Point(lineWidth.x, lineWidth.y);
+ }
+
+ public void dispose() {
+ } // Shared resources -- nothing to dispose
+}
diff --git a/plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/border/internal/BorderIterator.java b/plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/border/internal/BorderIterator.java
new file mode 100644
index 0000000000..388973a6a5
--- /dev/null
+++ b/plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/border/internal/BorderIterator.java
@@ -0,0 +1,149 @@
+/*
+ * Copyright (c) 2007 Matthew Hall 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:
+ * Matthew Hall - initial API and implementation
+ */
+
+package org.eclipse.sirius.table.ui.tools.internal.paperclips.border.internal;
+
+import org.eclipse.sirius.table.ui.tools.internal.paperclips.PaperClips;
+import org.eclipse.sirius.table.ui.tools.internal.paperclips.PrintIterator;
+import org.eclipse.sirius.table.ui.tools.internal.paperclips.PrintPiece;
+import org.eclipse.sirius.table.ui.tools.internal.paperclips.border.BorderPainter;
+import org.eclipse.sirius.table.ui.tools.internal.paperclips.border.BorderPrint;
+import org.eclipse.swt.graphics.Device;
+import org.eclipse.swt.graphics.GC;
+import org.eclipse.swt.graphics.Point;
+
+public class BorderIterator implements PrintIterator {
+ private final BorderPainter border;
+
+ private PrintIterator target;
+ private boolean opened;
+
+ public BorderIterator(BorderPrint print, Device device, GC gc) {
+ this.border = print.getBorder().createPainter(device, gc);
+
+ this.target = print.getTarget().iterator(device, gc);
+ this.opened = false;
+ }
+
+ public BorderIterator(BorderIterator that) {
+ this.border = that.border;
+
+ this.target = that.target.copy();
+ this.opened = that.opened;
+ }
+
+ public boolean hasNext() {
+ return target.hasNext();
+ }
+
+ public Point minimumSize() {
+ return addBorderMargin(target.minimumSize());
+ }
+
+ public Point preferredSize() {
+ return addBorderMargin(target.preferredSize());
+ }
+
+ private Point addBorderMargin(Point targetSize) {
+ return new Point(targetSize.x + border.getWidth(), targetSize.y
+ + border.getMaxHeight());
+ }
+
+ public PrintPiece next(int width, int height) {
+ if (!hasNext())
+ PaperClips.error("No more content"); //$NON-NLS-1$
+
+ PrintPiece piece = next(width, height, false /* closed bottom border */);
+
+ if (piece == null)
+ piece = next(width, height, true /* open bottom border */);
+
+ if (piece != null)
+ opened = true;
+
+ return piece;
+ }
+
+ private PrintPiece next(int width, int height, boolean bottomBorderOpen) {
+ // Adjust iteration area for border dimensions.
+ width -= border.getWidth();
+ height -= border.getHeight(opened, bottomBorderOpen);
+ if (width < 0 || height < 0)
+ return null;
+
+ PrintIterator iter = target.copy();
+ PrintPiece piece = PaperClips.next(iter, width, height);
+ if (piece == null)
+ return null;
+
+ if (bottomBorderOpen && !iter.hasNext()) {
+ // The target content was consumed, but the bottom border is open
+ // (suggesting that there is more
+ // content): find the largest piece that *doesn't* consume all the
+ // target's content, and show it with
+ // an open bottom border.
+ piece.dispose();
+ piece = getTallestPieceNotCompletelyConsumingTarget(width, height);
+ if (piece == null)
+ return null;
+ } else if (!bottomBorderOpen && iter.hasNext()) {
+ // Bottom border is closed but the target has more content: fail so
+ // calling method can try again with
+ // an open bottom border.
+ piece.dispose();
+ return null;
+ } else {
+ this.target = iter;
+ }
+
+ // Decorate the target print piece with border
+ piece = new BorderPiece(piece, border, opened, bottomBorderOpen);
+
+ return piece;
+ }
+
+ private PrintPiece getTallestPieceNotCompletelyConsumingTarget(
+ final int width, final int height) {
+ int low = 0;
+ int high = height - 1;
+
+ PrintIterator bestIterator = null;
+ PrintPiece bestPiece = null;
+ while (low + 1 < high) {
+ int testHeight = (low + high + 1) / 2;
+
+ PrintIterator testIterator = target.copy();
+ PrintPiece testPiece = PaperClips.next(testIterator, width,
+ testHeight);
+
+ if (testPiece == null) {
+ low = testHeight + 1;
+ } else if (testIterator.hasNext()) {
+ low = testHeight;
+
+ if (bestPiece != null)
+ bestPiece.dispose();
+ bestIterator = testIterator;
+ bestPiece = testPiece;
+ } else { // !testIterator.hasNext()
+ high = testPiece.getSize().y - 1;
+ }
+ }
+
+ if (bestPiece != null)
+ this.target = bestIterator;
+ return bestPiece;
+ }
+
+ public PrintIterator copy() {
+ return new BorderIterator(this);
+ }
+} \ No newline at end of file
diff --git a/plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/border/internal/BorderPiece.java b/plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/border/internal/BorderPiece.java
new file mode 100644
index 0000000000..a244d52880
--- /dev/null
+++ b/plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/border/internal/BorderPiece.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (c) 2007 Matthew Hall 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:
+ * Matthew Hall - initial API and implementation
+ */
+
+package org.eclipse.sirius.table.ui.tools.internal.paperclips.border.internal;
+
+import org.eclipse.sirius.table.ui.tools.internal.paperclips.PrintPiece;
+import org.eclipse.sirius.table.ui.tools.internal.paperclips.border.BorderPainter;
+import org.eclipse.sirius.table.ui.tools.internal.paperclips.internal.util.Util;
+import org.eclipse.swt.graphics.GC;
+import org.eclipse.swt.graphics.Point;
+
+public class BorderPiece implements PrintPiece {
+ private final PrintPiece target;
+
+ private final BorderPainter border;
+
+ private final boolean topOpen;
+
+ private final boolean bottomOpen;
+
+ private final Point size;
+
+ public BorderPiece(PrintPiece target, BorderPainter border,
+ boolean topOpen, boolean bottomOpen) {
+ Util.notNull(target, border);
+ this.target = target;
+ this.border = border;
+
+ this.topOpen = topOpen;
+ this.bottomOpen = bottomOpen;
+
+ Point targetSize = target.getSize();
+ this.size = new Point(targetSize.x + border.getWidth(), targetSize.y
+ + border.getHeight(topOpen, bottomOpen));
+ }
+
+ public Point getSize() {
+ return new Point(size.x, size.y);
+ }
+
+ public void paint(GC gc, int x, int y) {
+ border.paint(gc, x, y, size.x, size.y, topOpen, bottomOpen);
+ target.paint(gc, x + border.getLeft(), y + border.getTop(topOpen));
+ }
+
+ public void dispose() {
+ border.dispose();
+ target.dispose();
+ }
+} \ No newline at end of file
diff --git a/plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/grid/BasicGridLookPainter.java b/plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/grid/BasicGridLookPainter.java
new file mode 100644
index 0000000000..0ec191d15a
--- /dev/null
+++ b/plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/grid/BasicGridLookPainter.java
@@ -0,0 +1,250 @@
+/*
+ * Copyright (c) 2007 Matthew Hall 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:
+ * Matthew Hall - initial API and implementation
+ */
+package org.eclipse.sirius.table.ui.tools.internal.paperclips.grid;
+
+import org.eclipse.sirius.table.ui.tools.internal.paperclips.internal.util.Util;
+import org.eclipse.swt.graphics.Device;
+import org.eclipse.swt.graphics.GC;
+import org.eclipse.swt.graphics.Rectangle;
+
+/**
+ * A abstract GridLookPainter which simplifies implementation of custom
+ * GridLooks.
+ * <p>
+ * Subclasses must have the following methods implemented:
+ * <ul>
+ * <li>getMargins() - these margins are referenced by GridPrint for determining
+ * proper layout of the cells, as well as by the paint() method.
+ * <li>paintHeaderCell() - will be called by the paint() method for each header
+ * cell.
+ * <li>paintBodyCell() - will be called by the paint() method for each body
+ * cell.
+ * <li>paintFooterCell() - will be called by the paint() method for each footer
+ * cell.
+ * <li>dispose() - must dispose any SWT resources created by the subclass.
+ * </ul>
+ *
+ * @author Matthew Hall
+ */
+public abstract class BasicGridLookPainter implements GridLookPainter {
+ /**
+ * The printer device on which the look is being painted. This is the device
+ * that was passed as an argument to the constructor.
+ */
+ protected final Device device;
+
+ /**
+ * Constructs a BasicGridLook painter.
+ *
+ * @param device
+ * the printer device (may not be null). This argument will be
+ * saved in the protected {@link #device} field.
+ */
+ public BasicGridLookPainter(Device device) {
+ Util.notNull(device);
+ this.device = device;
+ }
+
+ public void paint(GC gc, int x, int y, int[] columns, int[] headerRows,
+ int[][] headerColSpans, int firstRowIndex, boolean topOpen,
+ int[] bodyRows, int[][] bodyColSpans, boolean bottomOpen,
+ int[] footerRows, int[][] footerColSpans) {
+ GridMargins margins = getMargins();
+
+ final boolean headerPresent = headerRows.length > 0;
+ final boolean footerPresent = footerRows.length > 0;
+
+ x += margins.getLeft();
+
+ if (headerPresent)
+ y = paintHeader(gc, x, y, columns, headerRows, headerColSpans);
+
+ y += margins.getBodyTop(headerPresent, topOpen);
+ y = paintBody(gc, x, y, columns, bodyRows, bodyColSpans, firstRowIndex,
+ topOpen, bottomOpen);
+ y += margins.getBodyBottom(footerPresent, bottomOpen);
+
+ if (footerPresent)
+ paintFooter(gc, x, y, columns, footerRows, footerColSpans);
+ }
+
+ private int paintHeader(GC gc, int x, int y, int[] columns, int[] rows,
+ int[][] colSpans) {
+ GridMargins margins = getMargins();
+
+ y += margins.getHeaderTop();
+
+ for (int i = 0; i < rows.length; i++) {
+ int h = rows[i];
+
+ paintHeaderRow(gc, x, y, columns, h, i, colSpans[i]);
+
+ y += h;
+ if (i < rows.length - 1)
+ y += margins.getHeaderVerticalSpacing();
+ }
+
+ return y;
+ }
+
+ private int paintBody(GC gc, int x, int y, int[] columns, int[] rows,
+ int[][] colSpans, int firstRowIndex, boolean topOpen,
+ boolean bottomOpen) {
+ GridMargins margins = getMargins();
+
+ for (int i = 0; i < rows.length; i++) {
+ final int h = rows[i];
+
+ paintBodyRow(gc, x, y, columns, h, colSpans[i], firstRowIndex + i,
+ i == 0 && topOpen, i == rows.length - 1 && bottomOpen);
+
+ y += h;
+ if (i < rows.length - 1)
+ y += margins.getBodyVerticalSpacing();
+ }
+ return y;
+ }
+
+ private void paintFooter(GC gc, final int x, int y, int[] columns,
+ int[] rows, int[][] colSpans) {
+ GridMargins margins = getMargins();
+
+ for (int i = 0; i < rows.length; i++) {
+ final int h = rows[i];
+
+ paintFooterRow(gc, x, y, columns, h, i, colSpans[i]);
+
+ y += h;
+ y += margins.getFooterVerticalSpacing();
+ }
+ }
+
+ private void paintHeaderRow(GC gc, int x, int y, int[] columns,
+ final int h, int rowIndex, int[] colSpans) {
+ GridMargins margins = getMargins();
+
+ int col = 0;
+ for (int i = 0; i < colSpans.length; i++) {
+ final int colSpan = colSpans[i];
+ final int w = sum(columns, col, colSpan) + (colSpan - 1)
+ * margins.getHorizontalSpacing();
+
+ paintHeaderCell(gc, new Rectangle(x, y, w, h), rowIndex, col,
+ colSpan);
+
+ col += colSpan;
+ x += w + margins.getHorizontalSpacing();
+ }
+ }
+
+ private void paintBodyRow(GC gc, int x, int y, int[] columns, final int h,
+ int[] colSpans, int rowIndex, final boolean topOpen,
+ final boolean bottomOpen) {
+ GridMargins margins = getMargins();
+
+ int col = 0;
+ for (int i = 0; i < colSpans.length; i++) {
+ final int colSpan = colSpans[i];
+ final int w = sum(columns, col, colSpan) + (colSpan - 1)
+ * margins.getHorizontalSpacing();
+
+ paintBodyCell(gc, new Rectangle(x, y, w, h), rowIndex, col,
+ colSpan, topOpen, bottomOpen);
+
+ col += colSpan;
+ x += w + margins.getHorizontalSpacing();
+ }
+ }
+
+ private void paintFooterRow(GC gc, int x, int y, int[] columns,
+ final int h, int rowIndex, int[] colSpans) {
+ GridMargins margins = getMargins();
+
+ int col = 0;
+ for (int i = 0; i < colSpans.length; i++) {
+ final int colSpan = colSpans[i];
+ final int w = sum(columns, col, colSpan) + (colSpan - 1)
+ * margins.getHorizontalSpacing();
+
+ paintFooterCell(gc, new Rectangle(x, y, w, h), rowIndex, col,
+ colSpan);
+
+ col += colSpan;
+ x += w + margins.getHorizontalSpacing();
+ }
+ }
+
+ private int sum(int[] elements, int start, int length) {
+ int sum = 0;
+ for (int j = 0; j < length; j++)
+ sum += elements[start + j];
+ return sum;
+ }
+
+ /**
+ * Paint the decorations for the described header cell.
+ *
+ * @param gc
+ * the graphics context to use for painting.
+ * @param bounds
+ * the bounds of the cell, excluding margins.
+ * @param row
+ * the row offset of the cell within the header.
+ * @param col
+ * the column offset of the cell within the header.
+ * @param colspan
+ * the number of columns that this cell spans.
+ */
+ protected abstract void paintHeaderCell(GC gc, Rectangle bounds, int row,
+ int col, int colspan);
+
+ /**
+ * Paint the decorations for the described body cell.
+ *
+ * @param gc
+ * the graphics context to use for painting.
+ * @param bounds
+ * the bounds of the cell, excluding margins.
+ * @param row
+ * the row offset of the cell within the header.
+ * @param col
+ * the column offset of the cell within the header.
+ * @param colspan
+ * the number of columns that this cell spans.
+ * @param topOpen
+ * whether the cell should be drawn with the top edge of the cell
+ * border "open." An open top border is a visual cue that the
+ * cell is being continued from the previous page.
+ * @param bottomOpen
+ * whether the cell should be drawn with the bottom edge of the
+ * cell border "open." An open bottom border is a visual cue that
+ * the cell will be continued on the next page.
+ */
+ protected abstract void paintBodyCell(GC gc, Rectangle bounds, int row,
+ int col, int colspan, boolean topOpen, boolean bottomOpen);
+
+ /**
+ * Paint the decorations for the described footer cell.
+ *
+ * @param gc
+ * the graphics context to use for painting.
+ * @param bounds
+ * the bounds of the cell, excluding margins.
+ * @param row
+ * the row offset of the cell within the header.
+ * @param col
+ * the column offset of the cell within the header.
+ * @param colspan
+ * the number of columns that this cell spans.
+ */
+ protected abstract void paintFooterCell(GC gc, Rectangle bounds, int row,
+ int col, int colspan);
+} \ No newline at end of file
diff --git a/plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/grid/CellBackgroundProvider.java b/plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/grid/CellBackgroundProvider.java
new file mode 100644
index 0000000000..46af700b66
--- /dev/null
+++ b/plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/grid/CellBackgroundProvider.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (c) 2006 Matthew Hall 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:
+ * Matthew Hall - initial API and implementation
+ */
+package org.eclipse.sirius.table.ui.tools.internal.paperclips.grid;
+
+import org.eclipse.swt.graphics.RGB;
+
+/**
+ * Instances of this interface provide background colors to be drawn behind
+ * cells in a grid. This interface is used by DefaultGridLook to provide
+ * pluggable cell background behavior.
+ *
+ * @author Matthew Hall
+ */
+public interface CellBackgroundProvider {
+ /**
+ * Returns the background color to display for the given grid cell.
+ *
+ * @param row
+ * the row index (zero-based)
+ * @param column
+ * the column index (zero-based). This is the grid column index,
+ * not the cell index within the row.
+ * @param colspan
+ * the number of grid columns that the cell occupies.
+ * @return the background color to display for the given header cell.
+ */
+ public RGB getCellBackground(int row, int column, int colspan);
+}
diff --git a/plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/grid/DefaultCellBackgroundProvider.java b/plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/grid/DefaultCellBackgroundProvider.java
new file mode 100644
index 0000000000..2ab1e3cd23
--- /dev/null
+++ b/plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/grid/DefaultCellBackgroundProvider.java
@@ -0,0 +1,109 @@
+/*
+ * Copyright (c) 2006 Matthew Hall 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:
+ * Matthew Hall - initial API and implementation
+ */
+package org.eclipse.sirius.table.ui.tools.internal.paperclips.grid;
+
+import org.eclipse.swt.graphics.RGB;
+
+/**
+ * Default implementation of the CellBackgroundProvider interface.
+ *
+ * @author Matthew Hall
+ */
+public class DefaultCellBackgroundProvider implements CellBackgroundProvider {
+ private final CellBackgroundProvider chain;
+
+ private RGB background;
+
+ /**
+ * Constructs a DefaultGridBackgroundProvider with a null background.
+ */
+ public DefaultCellBackgroundProvider() {
+ this.chain = null;
+
+ this.background = null;
+ }
+
+ /**
+ * Constructs a DefaultGridBackgroundProvider which chains to the argument
+ * if this instance has a null background color. (DefaultGridLook uses this
+ * constructor to cause header and footer background colors to default to
+ * the body background color.)
+ *
+ * @param chain
+ * the provider to chain a getCellBackground(...) call to if this
+ * instance would return null. Ignored if null.
+ */
+ public DefaultCellBackgroundProvider(CellBackgroundProvider chain) {
+ this.chain = chain;
+ this.background = null;
+ }
+
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result
+ + ((background == null) ? 0 : background.hashCode());
+ result = prime * result + ((chain == null) ? 0 : chain.hashCode());
+ return result;
+ }
+
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (obj == null)
+ return false;
+ if (getClass() != obj.getClass())
+ return false;
+ DefaultCellBackgroundProvider other = (DefaultCellBackgroundProvider) obj;
+ if (background == null) {
+ if (other.background != null)
+ return false;
+ } else if (!background.equals(other.background))
+ return false;
+ if (chain == null) {
+ if (other.chain != null)
+ return false;
+ } else if (!chain.equals(other.chain))
+ return false;
+ return true;
+ }
+
+ /**
+ * Returns the value in the background property. If the background property
+ * is null, the chained provider will be consulted to obtain a background
+ * color.
+ */
+ public RGB getCellBackground(int row, int column, int colspan) {
+ RGB result = getBackground();
+ if (result == null && chain != null)
+ result = chain.getCellBackground(row, column, colspan);
+ return result;
+ }
+
+ /**
+ * Returns the background color.
+ *
+ * @return the background color.
+ */
+ public RGB getBackground() {
+ return background;
+ }
+
+ /**
+ * Sets the background color to the argument.
+ *
+ * @param background
+ * the new background color.
+ */
+ public void setBackground(RGB background) {
+ this.background = background;
+ }
+}
diff --git a/plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/grid/DefaultGridLook.java b/plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/grid/DefaultGridLook.java
new file mode 100644
index 0000000000..b6dec9424d
--- /dev/null
+++ b/plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/grid/DefaultGridLook.java
@@ -0,0 +1,454 @@
+/*
+ * Copyright (c) 2006 Matthew Hall 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:
+ * Matthew Hall - initial API and implementation
+ */
+package org.eclipse.sirius.table.ui.tools.internal.paperclips.grid;
+
+import org.eclipse.sirius.table.ui.tools.internal.paperclips.border.Border;
+import org.eclipse.sirius.table.ui.tools.internal.paperclips.border.GapBorder;
+import org.eclipse.sirius.table.ui.tools.internal.paperclips.grid.internal.DefaultGridLookPainter;
+import org.eclipse.swt.graphics.Device;
+import org.eclipse.swt.graphics.GC;
+import org.eclipse.swt.graphics.Point;
+import org.eclipse.swt.graphics.RGB;
+import org.eclipse.swt.graphics.Rectangle;
+
+/**
+ * A GridLook which draws a border around grid cells, with configurable
+ * background colors for body, header, and footer cells.
+ *
+ * @author Matthew Hall
+ */
+public class DefaultGridLook implements GridLook {
+ /**
+ * Constant cell spacing value indicating that the borders of adjacent cells
+ * should overlap so the appear continuous.
+ */
+ public static final int BORDER_OVERLAP = -1;
+
+ Point cellSpacing = new Point(BORDER_OVERLAP, BORDER_OVERLAP);
+ Rectangle cellPadding = new Rectangle(0, 0, 0, 0);
+ int headerGap = BORDER_OVERLAP;
+ int footerGap = BORDER_OVERLAP;
+
+ Border cellBorder = new GapBorder();
+
+ DefaultCellBackgroundProvider defaultBodyBackgroundProvider;
+ DefaultCellBackgroundProvider defaultHeaderBackgroundProvider;
+ DefaultCellBackgroundProvider defaultFooterBackgroundProvider;
+
+ CellBackgroundProvider bodyBackgroundProvider;
+ CellBackgroundProvider headerBackgroundProvider;
+ CellBackgroundProvider footerBackgroundProvider;
+
+ /**
+ * Constructs a DefaultGridLook with no border, no cell spacing, and no
+ * background colors.
+ */
+ public DefaultGridLook() {
+ this.bodyBackgroundProvider = defaultBodyBackgroundProvider = new DefaultCellBackgroundProvider();
+ this.headerBackgroundProvider = defaultHeaderBackgroundProvider = new DefaultCellBackgroundProvider(
+ bodyBackgroundProvider);
+ this.footerBackgroundProvider = defaultFooterBackgroundProvider = new DefaultCellBackgroundProvider(
+ bodyBackgroundProvider);
+ }
+
+ /**
+ * Constructs a DefaultGridLook with the given cell spacing, and no border
+ * or background colors.
+ *
+ * @param horizontalSpacing
+ * the horizontal cell spacing.
+ * @param verticalSpacing
+ * the vertical cell spacing.
+ */
+ public DefaultGridLook(int horizontalSpacing, int verticalSpacing) {
+ this();
+ setCellSpacing(horizontalSpacing, verticalSpacing);
+ }
+
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime
+ * result
+ + ((bodyBackgroundProvider == null) ? 0
+ : bodyBackgroundProvider.hashCode());
+ result = prime * result
+ + ((cellBorder == null) ? 0 : cellBorder.hashCode());
+ result = prime * result
+ + ((cellPadding == null) ? 0 : cellPadding.hashCode());
+ result = prime * result
+ + ((cellSpacing == null) ? 0 : cellSpacing.hashCode());
+ result = prime
+ * result
+ + ((footerBackgroundProvider == null) ? 0
+ : footerBackgroundProvider.hashCode());
+ result = prime * result + footerGap;
+ result = prime
+ * result
+ + ((headerBackgroundProvider == null) ? 0
+ : headerBackgroundProvider.hashCode());
+ result = prime * result + headerGap;
+ return result;
+ }
+
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (obj == null)
+ return false;
+ if (getClass() != obj.getClass())
+ return false;
+ DefaultGridLook other = (DefaultGridLook) obj;
+ if (bodyBackgroundProvider == null) {
+ if (other.bodyBackgroundProvider != null)
+ return false;
+ } else if (!bodyBackgroundProvider.equals(other.bodyBackgroundProvider))
+ return false;
+ if (cellBorder == null) {
+ if (other.cellBorder != null)
+ return false;
+ } else if (!cellBorder.equals(other.cellBorder))
+ return false;
+ if (cellPadding == null) {
+ if (other.cellPadding != null)
+ return false;
+ } else if (!cellPadding.equals(other.cellPadding))
+ return false;
+ if (cellSpacing == null) {
+ if (other.cellSpacing != null)
+ return false;
+ } else if (!cellSpacing.equals(other.cellSpacing))
+ return false;
+ if (footerBackgroundProvider == null) {
+ if (other.footerBackgroundProvider != null)
+ return false;
+ } else if (!footerBackgroundProvider
+ .equals(other.footerBackgroundProvider))
+ return false;
+ if (footerGap != other.footerGap)
+ return false;
+ if (headerBackgroundProvider == null) {
+ if (other.headerBackgroundProvider != null)
+ return false;
+ } else if (!headerBackgroundProvider
+ .equals(other.headerBackgroundProvider))
+ return false;
+ if (headerGap != other.headerGap)
+ return false;
+ return true;
+ }
+
+ /**
+ * Returns the cell border. Default is an empty border with no margins.
+ *
+ * @return the cell border.
+ */
+ public Border getCellBorder() {
+ return cellBorder;
+ }
+
+ /**
+ * Sets the cell border.
+ *
+ * @param border
+ * the cell border.
+ */
+ public void setCellBorder(Border border) {
+ this.cellBorder = border;
+ }
+
+ /**
+ * Returns the border spacing, in points, between adjacent grid cells.
+ * Default is (x=BORDER_OVERLAP, y=BORDER_OVERLAP).
+ *
+ * @return the border spacing, in points, between adjacent grid cells.
+ */
+ public Point getCellSpacing() {
+ return new Point(cellSpacing.x, cellSpacing.y);
+ }
+
+ /**
+ * Sets the border spacing, in points, between adjacent grid cells. A value
+ * of {@link #BORDER_OVERLAP} causes the borders to overlap, making the
+ * border appear continuous throughout the grid. A value of 0 or more causes
+ * the cell borders to be spaced that many points apart. 72 points = 1".
+ *
+ * @param cellSpacing
+ * a point whose x and y elements indicate the horizontal and
+ * vertical spacing between grid cells.
+ */
+ public void setCellSpacing(Point cellSpacing) {
+ setCellSpacing(cellSpacing.x, cellSpacing.y);
+ }
+
+ /**
+ * Sets the border spacing, in points, between adjacent grid cells. A value
+ * of {@link #BORDER_OVERLAP} causes the borders to overlap, making the
+ * border appear continuous throughout the grid. A value of 0 or more causes
+ * the cell borders to be spaced that many points apart. 72 points = 1".
+ *
+ * @param horizontal
+ * the horizontal cell spacing.
+ * @param vertical
+ * the vertical cell spacing.
+ */
+ public void setCellSpacing(int horizontal, int vertical) {
+ if (horizontal == BORDER_OVERLAP || horizontal >= 0)
+ this.cellSpacing.x = horizontal;
+ if (vertical == BORDER_OVERLAP || vertical >= 0)
+ this.cellSpacing.y = vertical;
+ }
+
+ /**
+ * Returns a rectangle whose public fields denote the left (x), top (y),
+ * right (width) and bottom (height) cell padding, expressed in points. 72
+ * points = 1" = 2.54cm.
+ *
+ * @return a rectangle whose public fields denote the cell padding at each
+ * edge.
+ */
+ public Rectangle getCellPadding() {
+ return new Rectangle(cellPadding.x, cellPadding.y, cellPadding.width,
+ cellPadding.height);
+ }
+
+ /**
+ * Sets the cell padding to the values in the public fields of the argument.
+ *
+ * @param cellPadding
+ * the new cell padding.
+ */
+ public void setCellPadding(Rectangle cellPadding) {
+ setCellPadding(cellPadding.x, cellPadding.y, cellPadding.width,
+ cellPadding.height);
+ }
+
+ /**
+ * Sets the cell padding to the given horizontal and vertical values. This
+ * is equivalent to calling setCellPadding(horizontalPadding,
+ * verticalPadding, horizontalPadding, verticalPadding).
+ *
+ * @param horizontalPadding
+ * the amount of padding to add to the left and right of each
+ * cell, in points.
+ * @param verticalPadding
+ * the amount padding to add to the top and bottom each cell, in
+ * points.
+ */
+ public void setCellPadding(int horizontalPadding, int verticalPadding) {
+ setCellPadding(horizontalPadding, verticalPadding, horizontalPadding,
+ verticalPadding);
+ }
+
+ /**
+ * Sets the cell padding to the specified values.
+ *
+ * @param left
+ * the left cell padding, in points.
+ * @param top
+ * the top cell padding, in points.
+ * @param right
+ * the right cell padding, in points.
+ * @param bottom
+ * the bottom cell padding, in points.
+ */
+ public void setCellPadding(int left, int top, int right, int bottom) {
+ cellPadding.x = left;
+ cellPadding.y = top;
+ cellPadding.width = right;
+ cellPadding.height = bottom;
+ }
+
+ /**
+ * Returns the header background color. If null, the body background color
+ * is used. Default is null.
+ *
+ * @return the header background color.
+ */
+ public RGB getHeaderBackground() {
+ return defaultHeaderBackgroundProvider.getBackground();
+ }
+
+ /**
+ * Sets the header background color. Calls to this method override any
+ * previous calls to setHeaderBackgroundProvider(...).
+ *
+ * @param headerBackground
+ * the new background color. If null, the body background color
+ * will be used.
+ */
+ public void setHeaderBackground(RGB headerBackground) {
+ defaultHeaderBackgroundProvider.setBackground(headerBackground);
+ this.headerBackgroundProvider = defaultHeaderBackgroundProvider;
+ }
+
+ /**
+ * Returns the header background color provider.
+ *
+ * @return the header background color provider.
+ */
+ public CellBackgroundProvider getHeaderBackgroundProvider() {
+ return headerBackgroundProvider;
+ }
+
+ /**
+ * Sets the header background color provider. Calls to this method override
+ * any previous calls to setHeaderBackground(RGB). Setting this property to
+ * null restores the default background provider.
+ *
+ * @param headerBackgroundProvider
+ * the new background color provider.
+ */
+ public void setHeaderBackgroundProvider(
+ CellBackgroundProvider headerBackgroundProvider) {
+ this.headerBackgroundProvider = headerBackgroundProvider == null ? defaultHeaderBackgroundProvider
+ : headerBackgroundProvider;
+ }
+
+ /**
+ * Returns the vertical gap between the header and body cells. Default is
+ * BORDER_OVERLAP.
+ *
+ * @return the vertical gap between the header and body cells.
+ */
+ public int getHeaderGap() {
+ return headerGap;
+ }
+
+ /**
+ * Sets the vertical gap between the header and body cells. A value of
+ * {@link #BORDER_OVERLAP} causes the borders to overlap, making the border
+ * appear continuous in the transition from the header cells to the body
+ * cells.
+ *
+ * @param headerGap
+ * the new header gap.
+ */
+ public void setHeaderGap(int headerGap) {
+ this.headerGap = headerGap;
+ }
+
+ /**
+ * Returns the body background color. Default is null (no background color).
+ *
+ * @return the body background color.
+ */
+ public RGB getBodyBackground() {
+ return defaultBodyBackgroundProvider.getBackground();
+ }
+
+ /**
+ * Sets the body background color. Calls to this method override any
+ * previous calls to setBodyBackgroundProvider(...).
+ *
+ * @param bodyBackground
+ * the new background color.
+ */
+ public void setBodyBackground(RGB bodyBackground) {
+ defaultBodyBackgroundProvider.setBackground(bodyBackground);
+ this.bodyBackgroundProvider = defaultBodyBackgroundProvider;
+ }
+
+ /**
+ * Returns the body background color provider.
+ *
+ * @return the body background color provider.
+ */
+ public CellBackgroundProvider getBodyBackgroundProvider() {
+ return bodyBackgroundProvider;
+ }
+
+ /**
+ * Sets the body background color provider. Calls to this method override
+ * any previous calls to setBodyBackground(RGB). Setting this property to
+ * null restores the default background provider.
+ *
+ * @param bodyBackgroundProvider
+ * the new background color provider.
+ */
+ public void setBodyBackgroundProvider(
+ CellBackgroundProvider bodyBackgroundProvider) {
+ this.bodyBackgroundProvider = bodyBackgroundProvider == null ? defaultBodyBackgroundProvider
+ : bodyBackgroundProvider;
+ }
+
+ /**
+ * Returns the vertical gap between the body and footer cells. Default is
+ * BORDER_OVERLAP.
+ *
+ * @return the vertical gap between the header and body cells.
+ */
+ public int getFooterGap() {
+ return footerGap;
+ }
+
+ /**
+ * Sets the vertical gap between the header and body cells. A value of
+ * {@link #BORDER_OVERLAP} causes the borders to overlap, making the border
+ * appear continuous in the transition from the body cells to the footer
+ * cells.
+ *
+ * @param footerGap
+ */
+ public void setFooterGap(int footerGap) {
+ this.footerGap = footerGap;
+ }
+
+ /**
+ * Returns the footer background color. If null, the body background color
+ * is used. Default is null.
+ *
+ * @return the footer background color.
+ */
+ public RGB getFooterBackground() {
+ return defaultFooterBackgroundProvider.getBackground();
+ }
+
+ /**
+ * Sets the footer background color. Calls to this method override any
+ * previous calls to setFooterBackgroundProvider(...).
+ *
+ * @param footerBackground
+ * the new background color. If null, the body background color
+ * will be used.
+ */
+ public void setFooterBackground(RGB footerBackground) {
+ defaultFooterBackgroundProvider.setBackground(footerBackground);
+ this.footerBackgroundProvider = defaultFooterBackgroundProvider;
+ }
+
+ /**
+ * Returns the footer background color provider.
+ *
+ * @return the footer background color provider.
+ */
+ public CellBackgroundProvider getFooterBackgroundProvider() {
+ return footerBackgroundProvider;
+ }
+
+ /**
+ * Sets the footer background color provider. Calls to this method override
+ * any previous calls to setFooterBackground(RGB). Setting this property to
+ * null restores the default background provider.
+ *
+ * @param footerBackgroundProvider
+ * the new background color provider.
+ */
+ public void setFooterBackgroundProvider(
+ CellBackgroundProvider footerBackgroundProvider) {
+ this.footerBackgroundProvider = footerBackgroundProvider == null ? defaultFooterBackgroundProvider
+ : footerBackgroundProvider;
+ }
+
+ public GridLookPainter getPainter(Device device, GC gc) {
+ return new DefaultGridLookPainter(this, device, gc);
+ }
+}
diff --git a/plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/grid/GridCell.java b/plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/grid/GridCell.java
new file mode 100644
index 0000000000..59c0d54eaa
--- /dev/null
+++ b/plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/grid/GridCell.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (c) 2006 Matthew Hall 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:
+ * Matthew Hall - initial API and implementation
+ */
+package org.eclipse.sirius.table.ui.tools.internal.paperclips.grid;
+
+import org.eclipse.sirius.table.ui.tools.internal.paperclips.Print;
+import org.eclipse.swt.graphics.Point;
+
+/**
+ * Instances of this interface represent a single cell in a GridPrint.
+ *
+ * @author Matthew Hall
+ */
+public interface GridCell {
+
+ /**
+ * Returns a Point representing the horizontal and vertical alignment
+ * applied to the cell's content.
+ *
+ * @return a Point representing the horizontal and vertical alignment
+ * applied to the cell's content.
+ */
+ Point getAlignment();
+
+ /**
+ * Returns the horizontal alignment applied to the cell content.
+ *
+ * @return the horizontal alignment applied to the cell content.
+ */
+ int getHorizontalAlignment();
+
+ /**
+ * Returns the vertical alignment applied to the cell content.
+ *
+ * @return the vertical alignment applied to the cell content.
+ */
+ int getVerticalAlignment();
+
+ /**
+ * Returns the content print of the cell.
+ *
+ * @return the content print of the cell.
+ */
+ Print getContent();
+
+ /**
+ * Returns the number of columns this cell spans across.
+ *
+ * @return the number of columns this cell spans across.
+ */
+ int getColSpan();
+
+} \ No newline at end of file
diff --git a/plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/grid/GridColumn.java b/plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/grid/GridColumn.java
new file mode 100644
index 0000000000..8cf03dfc5f
--- /dev/null
+++ b/plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/grid/GridColumn.java
@@ -0,0 +1,331 @@
+/*
+ * Copyright (c) 2005 Matthew Hall 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:
+ * Matthew Hall - initial API and implementation
+ */
+package org.eclipse.sirius.table.ui.tools.internal.paperclips.grid;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.eclipse.sirius.table.ui.tools.internal.paperclips.PaperClips;
+import org.eclipse.sirius.table.ui.tools.internal.paperclips.internal.util.PaperClipsUtil;
+import org.eclipse.sirius.table.ui.tools.internal.paperclips.internal.util.Util;
+import org.eclipse.swt.SWT;
+
+/**
+ * Describes the properties of a column in a GridPrint.
+ *
+ * @author Matthew Hall
+ */
+public class GridColumn {
+ /**
+ * The default alignment used when alignment is not specified. Value is
+ * SWT.LEFT.
+ */
+ public static final int DEFAULT_ALIGN = SWT.LEFT;
+
+ /**
+ * The default size used when size is not specified. Value is SWT.DEFAULT.
+ */
+ public static final int DEFAULT_SIZE = SWT.DEFAULT;
+
+ /**
+ * The default weight used when weight is not specified. Value is 0.
+ */
+ public static final int DEFAULT_WEIGHT = 0;
+
+ /**
+ * The size property for this GridColumn. Possible values:
+ * <ul>
+ * <li>GridPrint.PREFERRED - indicates that the column should be as wide as
+ * the preferred width of its widest element.
+ * <li>SWT.DEFAULT - Similar to GridPrint.PREFERRED, except that the column
+ * may shrink down to its minimum width if space is scarce.
+ * <li>A value > 0 indicates that the column should be <code>size</code>
+ * points wide (72pts = 1").
+ * </ul>
+ */
+ public final int size;
+
+ /**
+ * The default alignment for Prints in this column. Possible values are
+ * SWT.LEFT, SWT.CENTER, SWT.RIGHT, or SWT.DEFAULT. Note that alignment
+ * affects the placement of PrintPieces within the grid's cell--the
+ * alignment elements of the PrintPiece themselves are not affected. Thus,
+ * in order to achieve the desired effect, a Print having an alignment
+ * property should be set to the same alignment as the grid cell it is added
+ * to. For example, a TextPrint in a right-aligned grid cell should be set
+ * to right alignment as well.
+ * <p>
+ * Cells that span multiple columns use the alignment of the left-most cell
+ * in the cell span.
+ */
+ public final int align;
+
+ /**
+ * The weight of this column. If the available print space is wider than the
+ * grid's preferred width, this field determines how much of that extra
+ * space should be given to this column. A larger weight causes the column
+ * to receive more of the extra width. A value of 0 indicates that the
+ * column should not be given any excess width.
+ */
+ public final int weight;
+
+ /**
+ * Constructs a GridColumn.
+ *
+ * @param align
+ * The default alignment for Prints in this column.
+ * @param size
+ * The size this column should be given.
+ * @param weight
+ * The weight this column should be given.
+ */
+ public GridColumn(int align, int size, int weight) {
+ this.align = checkAlign(align);
+ this.size = checkSize(size);
+ this.weight = checkWeight(weight);
+ }
+
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + align;
+ result = prime * result + size;
+ result = prime * result + weight;
+ return result;
+ }
+
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (obj == null)
+ return false;
+ if (getClass() != obj.getClass())
+ return false;
+ GridColumn other = (GridColumn) obj;
+ if (align != other.align)
+ return false;
+ if (size != other.size)
+ return false;
+ if (weight != other.weight)
+ return false;
+ return true;
+ }
+
+ private static int checkAlign(int align) {
+ align = PaperClipsUtil.firstMatch(align, new int[] { SWT.LEFT,
+ SWT.CENTER, SWT.RIGHT, SWT.DEFAULT }, 0);
+ if (align == 0)
+ PaperClips
+ .error(
+ SWT.ERROR_INVALID_ARGUMENT,
+ "Alignment argument must be one of SWT.LEFT, SWT.CENTER, SWT.RIGHT, or SWT.DEFAULT"); //$NON-NLS-1$
+ if (align == SWT.DEFAULT)
+ return DEFAULT_ALIGN;
+ return align;
+ }
+
+ private static int checkSize(int size) {
+ if (size != SWT.DEFAULT && size != GridPrint.PREFERRED && size <= 0)
+ PaperClips
+ .error(SWT.ERROR_INVALID_ARGUMENT,
+ "Size argument must be SWT.DEFAULT, GridPrint.PREFERRED, or > 0"); //$NON-NLS-1$
+ return size;
+ }
+
+ private static int checkWeight(int grow) {
+ if (grow < 0)
+ PaperClips.error(SWT.ERROR_INVALID_ARGUMENT,
+ "Weight argument must be >= 0"); //$NON-NLS-1$
+ return grow;
+ }
+
+ /**
+ * Parses the given column spec and returns a GridColumn matching that spec.
+ * <p>
+ * Format:
+ *
+ * <pre>
+ * [align:]size[:grow]
+ *
+ * align = L | LEFT |
+ * C | CENTER |
+ * R | RIGHT
+ * size = P | PREF | PREFERRED |
+ * D | DEF | DEFAULT |
+ * (Positive number)[PT|IN|INCH|CM|MM]
+ * weight = N | NONE |
+ * G | GROW | G(#) | GROW(#)
+ * </pre>
+ *
+ * The default alignment is LEFT. The
+ *
+ * <code>weight</code> argument expresses the weight property: NONE
+ * indicates a weight of 0; GROW indicates a weight of 1; and GROW(3)
+ * indicates a weight of 3. The default weight (if <code>weight</code> is
+ * omitted) is 0.
+ * <p>
+ * Examples:
+ *
+ * <pre>
+ * LEFT:DEFAULT:GROW // left-aligned, default size, weight=1
+ * R:72PT:N // light-aligned, 72 points (1&quot;) wide, weight=0
+ * right:72 // identical to previous line
+ * c:pref:none // center-aligned, preferred size, weight=0
+ * p // left-aligned (default), preferred size, weight=0
+ * r:2inch // right-aligned, 2 inches (50.8mm)
+ * r:50.8mm // right-aligned, 50.8 mm (2&quot;)
+ * </pre>
+ *
+ * @param spec
+ * the column spec that will be parsed.
+ * @return a GridColumn matching the column spec.
+ * @see #align
+ * @see #size
+ * @see #weight
+ */
+ public static GridColumn parse(String spec) {
+ Util.notNull(spec);
+
+ String[] matches = spec.split("\\s*:\\s*"); //$NON-NLS-1$
+ if (matches.length == 0)
+ PaperClips.error(SWT.ERROR_INVALID_ARGUMENT, "Missing column spec"); //$NON-NLS-1$
+
+ int align = DEFAULT_ALIGN;
+ int size = DEFAULT_SIZE;
+ int grow = DEFAULT_WEIGHT;
+
+ if (matches.length == 1) {
+ // One option: must be size
+ size = parseSize(matches[0]);
+ } else if (matches.length == 2) {
+ // Two possible scenarios:
+ // 1. align:size
+ // 2. size:weight
+ if (isAlign(matches[0])) {
+ align = parseAlign(matches[0]);
+ size = parseSize(matches[1]);
+ } else {
+ size = parseSize(matches[0]);
+ grow = parseWeight(matches[1]);
+ }
+ } else if (matches.length == 3) {
+ align = parseAlign(matches[0]);
+ size = parseSize(matches[1]);
+ grow = parseWeight(matches[2]);
+ }
+
+ return new GridColumn(align, size, grow);
+ }
+
+ // Alignment patterns
+ private static final Pattern LEFT_ALIGN_PATTERN = Pattern.compile(
+ "^l(eft)?$", //$NON-NLS-1$
+ Pattern.CASE_INSENSITIVE);
+
+ private static final Pattern CENTER_ALIGN_PATTERN = Pattern.compile(
+ "^c(enter)?$", //$NON-NLS-1$
+ Pattern.CASE_INSENSITIVE);
+
+ private static final Pattern RIGHT_ALIGN_PATTERN = Pattern.compile(
+ "^r(ight)?$", //$NON-NLS-1$
+ Pattern.CASE_INSENSITIVE);
+
+ private static final Pattern ANY_ALIGN_PATTERN = Pattern.compile(
+ "^l(eft)?|c(enter)?|r(ight)?$", //$NON-NLS-1$
+ Pattern.CASE_INSENSITIVE);
+
+ private static boolean isAlign(String alignmentString) {
+ return ANY_ALIGN_PATTERN.matcher(alignmentString).matches();
+ }
+
+ private static int parseAlign(String alignmentString) {
+ if (LEFT_ALIGN_PATTERN.matcher(alignmentString).matches())
+ return SWT.LEFT;
+ else if (CENTER_ALIGN_PATTERN.matcher(alignmentString).matches())
+ return SWT.CENTER;
+ else if (RIGHT_ALIGN_PATTERN.matcher(alignmentString).matches())
+ return SWT.RIGHT;
+ PaperClips.error(SWT.ERROR_INVALID_ARGUMENT,
+ "Unknown alignment \"" + alignmentString + "\""); //$NON-NLS-1$//$NON-NLS-2$
+ return 0; // unreachable
+ }
+
+ // Size patterns.
+ private static final Pattern DEFAULT_SIZE_PATTERN = Pattern.compile(
+ "^d(ef(ault)?)?$", //$NON-NLS-1$
+ Pattern.CASE_INSENSITIVE);
+
+ private static final Pattern PREFERRED_SIZE_PATTERN = Pattern.compile(
+ "^p(ref(erred)?)?", //$NON-NLS-1$
+ Pattern.CASE_INSENSITIVE);
+
+ private static final Pattern EXPLICIT_SIZE_PATTERN = Pattern.compile(
+ "^(\\d+(\\.\\d+)?)\\s*(pt|in(ch)?|mm|cm)?$", //$NON-NLS-1$
+ Pattern.CASE_INSENSITIVE);
+
+ private static int parseSize(String sizeString) {
+ Matcher matcher;
+ if (DEFAULT_SIZE_PATTERN.matcher(sizeString).matches())
+ return SWT.DEFAULT;
+ else if (PREFERRED_SIZE_PATTERN.matcher(sizeString).matches())
+ return GridPrint.PREFERRED;
+ else if ((matcher = EXPLICIT_SIZE_PATTERN.matcher(sizeString))
+ .matches()) {
+ return (int) Math.ceil(convertToPoints(Double.parseDouble(matcher
+ .group(1)), matcher.group(3)));
+ } else {
+ PaperClips.error(SWT.ERROR_INVALID_ARGUMENT,
+ "Unknown size pattern: \"" + sizeString + "\""); //$NON-NLS-1$ //$NON-NLS-2$
+ return 0; // unreachable
+ }
+ }
+
+ private static double convertToPoints(double value, String unit) {
+ if (unit == null || unit.length() == 0 || unit.equalsIgnoreCase("pt")) //$NON-NLS-1$
+ return value;
+ else if (unit.equalsIgnoreCase("in") || unit.equalsIgnoreCase("inch")) //$NON-NLS-1$ //$NON-NLS-2$
+ return 72 * value;
+ else if (unit.equalsIgnoreCase("cm")) //$NON-NLS-1$
+ return 72 * value / 2.54;
+ else if (unit.equalsIgnoreCase("mm")) //$NON-NLS-1$
+ return 72 * value / 25.4;
+ PaperClips.error(SWT.ERROR_INVALID_ARGUMENT,
+ "Unknown unit \"" + unit + "\"."); //$NON-NLS-1$ //$NON-NLS-2$
+ return 0;
+ }
+
+ private static final Pattern WEIGHTLESS_PATTERN = Pattern.compile(
+ "n(one)?", //$NON-NLS-1$
+ Pattern.CASE_INSENSITIVE);
+
+ private static final Pattern WEIGHTED_PATTERN = Pattern.compile(
+ "(g(row)?)(\\((\\d+)\\))?", // yikes //$NON-NLS-1$
+ Pattern.CASE_INSENSITIVE);
+
+ private static int parseWeight(String weightString) {
+ Matcher matcher;
+ if (WEIGHTLESS_PATTERN.matcher(weightString).matches())
+ return 0;
+ else if ((matcher = WEIGHTED_PATTERN.matcher(weightString)).matches()) {
+ String weight = matcher.group(4);
+ return (weight == null) ? 1 : Integer.parseInt(weight);
+ } else {
+ PaperClips.error(SWT.ERROR_INVALID_ARGUMENT,
+ "Illegal grow pattern: \"" + weightString //$NON-NLS-1$
+ + "\""); //$NON-NLS-1$
+ return 0; // unreachable
+ }
+ }
+
+ GridColumn copy() {
+ return new GridColumn(align, size, weight);
+ }
+} \ No newline at end of file
diff --git a/plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/grid/GridLook.java b/plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/grid/GridLook.java
new file mode 100644
index 0000000000..dbd7df95b8
--- /dev/null
+++ b/plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/grid/GridLook.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (c) 2006 Matthew Hall 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:
+ * Matthew Hall - initial API and implementation
+ */
+package org.eclipse.sirius.table.ui.tools.internal.paperclips.grid;
+
+import org.eclipse.swt.graphics.Device;
+import org.eclipse.swt.graphics.GC;
+
+/**
+ * A pluggable "look" for a GridPrint.
+ *
+ * @author Matthew Hall
+ */
+public interface GridLook {
+ /**
+ * Returns a GridLookPainter for painting the GridLook.
+ *
+ * @param device
+ * the device to paint on.
+ * @param gc
+ * the graphics context for painting.
+ * @return a GridLookPainter for painting the GridLook.
+ */
+ public GridLookPainter getPainter(Device device, GC gc);
+} \ No newline at end of file
diff --git a/plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/grid/GridLookPainter.java b/plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/grid/GridLookPainter.java
new file mode 100644
index 0000000000..009f692cde
--- /dev/null
+++ b/plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/grid/GridLookPainter.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright (c) 2006 Matthew Hall 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:
+ * Matthew Hall - initial API and implementation
+ */
+package org.eclipse.sirius.table.ui.tools.internal.paperclips.grid;
+
+import org.eclipse.swt.graphics.GC;
+
+/**
+ * Interface for drawing a GridLook.
+ *
+ * @author Matthew Hall
+ */
+public interface GridLookPainter {
+ /**
+ * Returns the grid margins used for the GridLook.
+ *
+ * @return the grid margins used for the GridLook.
+ * @see GridMargins
+ */
+ public GridMargins getMargins();
+
+ /**
+ * Paints the grid look onto the GC.
+ *
+ * @param gc
+ * the graphics context to paint on.
+ * @param x
+ * the x coordinate of the top-left of the grid.
+ * @param y
+ * the y coordinate of the top-left of the grid.
+ * @param columns
+ * the column widths. The left and right margins of each cell are
+ * included in the column widths.
+ * @param headerRows
+ * the header row heights.
+ * @param headerColSpans
+ * a two-dimensional array of cell spans in the header. Each
+ * element in the outer array is a header row. Each element of an
+ * inner array is a cell, where the element value indicates how
+ * many columns the cell spans.
+ * @param firstRowIndex
+ * the zero-based index of the first row displayed on the page.
+ * @param topOpen
+ * whether the top body row should be drawn with the top edge of
+ * the cell border "open." An open top border is a visual
+ * indication that the top row is being continued from the
+ * previous page.
+ * @param bodyRows
+ * the body row heights.
+ * @param bodyColSpans
+ * a two-dimensional array of cell spans in the body. Each
+ * element in the outer array is a body row. Each element of an
+ * inner array is a cell, where the element value indicates how
+ * many columns the cell spans.
+ * @param bottomOpen
+ * whether the bottom body row should be drawn with the bottom
+ * edge of the cell border "open." An open bottom border is a
+ * visual indication that the bottom row will be continued on the
+ * next page.
+ * @param footerRows
+ * the footer row heights.
+ * @param footerColSpans
+ * a two-dimensional array of cell spans in the footer. Each
+ * element in the outer array is a footer row. Each element of an
+ * inner array is a cell, where the element value indicates how
+ * many columns the cell spans.
+ */
+ public void paint(final GC gc, final int x, final int y,
+ final int[] columns, final int[] headerRows,
+ final int[][] headerColSpans, final int firstRowIndex,
+ final boolean topOpen, final int[] bodyRows,
+ final int[][] bodyColSpans, final boolean bottomOpen,
+ final int[] footerRows, final int[][] footerColSpans);
+
+ /**
+ * Disposes the system resources allocated by this GridLookPainter. The
+ * dispose method is <b>not</b> a permanent disposal of a GridLookPainter.
+ * It is intended to reclaim system resources, however future calls to
+ * paint(GC,int,int) may require that the resources be allocated again.
+ */
+ public void dispose();
+}
diff --git a/plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/grid/GridMargins.java b/plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/grid/GridMargins.java
new file mode 100644
index 0000000000..0ec5dccae7
--- /dev/null
+++ b/plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/grid/GridMargins.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright (c) 2006 Matthew Hall 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:
+ * Matthew Hall - initial API and implementation
+ */
+package org.eclipse.sirius.table.ui.tools.internal.paperclips.grid;
+
+/**
+ * An interface for informing a GridPrint what cell margins to use for the
+ * GridLook.
+ *
+ * @author Matthew Hall
+ */
+public interface GridMargins {
+ /**
+ * Returns the margin, in pixels, at the left side of the grid.
+ *
+ * @return the margin, in pixels, at the left side of the grid.
+ */
+ public int getLeft();
+
+ /**
+ * Returns the horizontal spacing, in pixels, between grid cells.
+ *
+ * @return the horizontal spacing, in pixels, between grid cells.
+ */
+ public int getHorizontalSpacing();
+
+ /**
+ * Returns the margin, in pixels, at the right side of the grid.
+ *
+ * @return the margin, in pixels, at the right side of the grid.
+ */
+ public int getRight();
+
+ /**
+ * Returns the margin, in pixels, at the top of the header cells. If a grid
+ * has no header cells, this value is ignored.
+ *
+ * @return the margin, in pixels, at the top of the header cells.
+ */
+ public int getHeaderTop();
+
+ /**
+ * Returns the vertical spacing, in pixels, between rows in the header.
+ *
+ * @return the vertical spacing, in pixels, between rows in the header.
+ */
+ public int getHeaderVerticalSpacing();
+
+ /**
+ * Returns the margin, in pixels, at the top of the body cells. If a header
+ * is present, this is the spacing, in pixels, between the last header row
+ * and the first body row. If a header is not present, this is the margin,
+ * in pixels, at the top of the grid.
+ *
+ * @param headerPresent
+ * whether a header is present.
+ * @param open
+ * whether the top row of body cells are "open." That is, whether
+ * the top row was started on a previous page and is continuing
+ * on this page. A GridLook may choose to show a visual
+ * indication for cells that were "opened" on previous pages.
+ * @return the margin, in pixels, at the top of the body cells.
+ */
+ public int getBodyTop(boolean headerPresent, boolean open);
+
+ /**
+ * Returns the vertical spacing, in pixels, between rows in the body.
+ *
+ * @return the vertical spacing, in pixels, between rows in the body.
+ */
+ public int getBodyVerticalSpacing();
+
+ /**
+ * Returns the margin, in pixels, at the bottom of the body cells. If a
+ * footer is present, this is the spacing, in pixels, between the last body
+ * row and the first footer row. If a header is not present, this is the
+ * margin, in pixels, at the bottom of the grid.
+ *
+ * @param footerPresent
+ * whether a footer is present.
+ * @param open
+ * whether the bottom row of body cells are "open." That is,
+ * whether the bottom row still has more content to display on
+ * the next page. A GridLook may choose to show a visual
+ * indication for cells that will be "continued" on the next
+ * page.
+ * @return the margin, in pixels, at the bottom of the body cells.
+ */
+ public int getBodyBottom(boolean footerPresent, boolean open);
+
+ /**
+ * Returns the vertical spacing, in pixels, between rows in the footer.
+ *
+ * @return the vertical spacing, in pixels, between rows in the footer.
+ */
+ public int getFooterVerticalSpacing();
+
+ /**
+ * Returns the margin, in pixels, at the bottom of the footer cells. If a
+ * grid has no footer cells, this value is ignored.
+ *
+ * @return the margin, in pixels, at the bottom of the footer cells.
+ */
+ public int getFooterBottom();
+}
diff --git a/plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/grid/GridPrint.java b/plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/grid/GridPrint.java
new file mode 100644
index 0000000000..f76ab8126a
--- /dev/null
+++ b/plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/grid/GridPrint.java
@@ -0,0 +1,1033 @@
+/*
+ * Copyright (c) 2005 Matthew Hall 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:
+ * Matthew Hall - initial API and implementation
+ */
+package org.eclipse.sirius.table.ui.tools.internal.paperclips.grid;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import org.eclipse.sirius.table.ui.tools.internal.paperclips.PaperClips;
+import org.eclipse.sirius.table.ui.tools.internal.paperclips.Print;
+import org.eclipse.sirius.table.ui.tools.internal.paperclips.PrintIterator;
+import org.eclipse.sirius.table.ui.tools.internal.paperclips.grid.internal.GridCellImpl;
+import org.eclipse.sirius.table.ui.tools.internal.paperclips.grid.internal.GridIterator;
+import org.eclipse.sirius.table.ui.tools.internal.paperclips.internal.util.PaperClipsUtil;
+import org.eclipse.sirius.table.ui.tools.internal.paperclips.internal.util.Util;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.graphics.Device;
+import org.eclipse.swt.graphics.GC;
+
+/**
+ * A Print which arranges child prints into a grid. A grid is initialized with a
+ * series of GridColumns, and child prints are laid out into those columns by
+ * invoking the add(...) methods.
+ * <p>
+ * GridPrint uses a column sizing algorithm based on the <a
+ * href=http://www.w3.org/TR/html4/appendix/notes.html#h-B.5.2.2>W3C
+ * recommendation</a> for automatic layout of tables. GridPrint deviates from
+ * the recommendation on one important point: if there is less width available
+ * on the print device than the calculated "minimum" size of the grid, the
+ * columns will be scaled down to <em>less</em> than their calculated minimum
+ * widths. Only when one of the columns goes below its "absolute minimum" will
+ * the grid fail to print ( {@link PrintIterator#next(int, int)} returns null).
+ * <p>
+ * GridPrint offers three basic methods of specifying column size.
+ * <ol>
+ * <li>Default size. The column will be somewhere between it's minimum and
+ * preferred width. GridPrint will determine the optimum widths for all default
+ * size columns, using the modified W3C recommendation described above. This is
+ * the recommended option for most cases.
+ * <li>Preferred size. The column will be sized to it's preferred width. This
+ * option is sometimes appropriate, for example when certain portions of text
+ * should not be allowed to line-wrap. In cases where only a few cells in a
+ * column need to be prevented from line wrapping, consider wrapping them in a
+ * NoBreakPrint instead.
+ * <li>Explicit size. The column will be the size you specify, expressed in
+ * points. 72 points = 1".
+ * </ol>
+ * Example: GridPrint grid = new GridPrint("d, p, 72pts");
+ * <p>
+ * In addition, any column can be given a grow attribute. In the event a grid is
+ * not as wide as the page, those columns with the grow attribute set will be
+ * widened to fill the extra space.
+ * <p>
+ * Because GridPrint scales columns according to their minimum sizes in the
+ * worst-case scenario, the absolute minimum size of a GridPrint is dependant on
+ * its child prints and is not clearly defined.
+ * <p>
+ * If a grid has one of more columns with the grow attribute set, the grid is
+ * horizontally greedy. Greedy prints take up all the available space on the
+ * page.
+ *
+ * @author Matthew Hall
+ * @see GridColumn
+ * @see PrintIterator#minimumSize()
+ * @see PrintIterator#preferredSize()
+ */
+public final class GridPrint implements Print {
+ /**
+ * Constant colspan value indicating that all remaining columns in the row
+ * should be used.
+ */
+ public static final int REMAINDER = -1;
+
+ /**
+ * Constant column size value indicating that the column should be given its
+ * preferred size. (In the context of W3C's autolayout recommendation, this
+ * has the effect of setting the columns minimum width to its preferred
+ * width. This value is used in the GridColumn constructor.
+ */
+ public static final int PREFERRED = 0;
+
+ /**
+ * Constant cell spacing value indicating that the borders of adjacent cells
+ * should overlap.
+ */
+ public static final int BORDER_OVERLAP = -1;
+
+ private GridLook look;
+
+ /** The columns for this grid. */
+ final List columns; // List<GridColumn>
+
+ /** Array of column groups. */
+ int[][] columnGroups = new int[0][];
+
+ /**
+ * Two-dimension list of all header cells. Each element of this list
+ * represents a row in the header. Each element of a row represents a
+ * cellspan in that row.
+ */
+ final List header = new ArrayList(); // List <List <GridCell>>
+
+ /** Column cursor - the column that the next added header cell will go into. */
+ private int headerCol = 0;
+
+ /**
+ * Two-dimensional list of all body cells. Each element of this list
+ * represents a row in the body. Each element of a row represents a cellspan
+ * in that row.
+ */
+
+ final List body = new ArrayList(); // List <List <GridCell>>
+
+ /** Column cursor - the column that the next added print will go into. */
+ private int bodyCol = 0;
+
+ boolean cellClippingEnabled = true;
+
+ /**
+ * Two-dimension list of all footer cells. Each element of this list
+ * represents a row in the footer. Each element of a row represents a
+ * cellspan in that row.
+ */
+ // List <List <GridCell>>
+ final List footer = new ArrayList();
+
+ /** Column cursor - the column that the next added footer cell will go into. */
+ private int footerCol = 0;
+
+ /**
+ * Constructs a GridPrint with no columns and a default look.
+ */
+ public GridPrint() {
+ this(new GridColumn[0]);
+ }
+
+ /**
+ * Constructs a GridPrint with no columns and the given look.
+ *
+ * @param look
+ * the look to apply to the constructed grid.
+ */
+ public GridPrint(GridLook look) {
+ this(new GridColumn[0], look);
+ }
+
+ /**
+ * Constructs a GridPrint with the given columns and a default look.
+ *
+ * @param columns
+ * a comma-separated list of parseable column specs.
+ * @see GridColumn#parse(String)
+ */
+ public GridPrint(String columns) {
+ this(parseColumns(columns));
+ }
+
+ /**
+ * Constructs a GridPrint with the given columns and look.
+ *
+ * @param columns
+ * a comma-separated list of parseable column specs.
+ * @param look
+ * the look to apply to the constructed grid.
+ * @see GridColumn#parse(String)
+ */
+ public GridPrint(String columns, GridLook look) {
+ this(parseColumns(columns), look);
+ }
+
+ /**
+ * Constructs a GridPrint with the given columns and a default look.
+ *
+ * @param columns
+ * the columns for the new grid.
+ */
+ public GridPrint(GridColumn[] columns) {
+ Util.noNulls(columns);
+
+ this.columns = new ArrayList();
+ for (int i = 0; i < columns.length; i++)
+ this.columns.add(columns[i]);
+ this.look = new DefaultGridLook();
+ }
+
+ /**
+ * Constructs a GridPrint with the given columns and look.
+ *
+ * @param columns
+ * the columns for the new grid.
+ * @param look
+ * the look to apply to the constructed grid.
+ */
+ public GridPrint(GridColumn[] columns, GridLook look) {
+ this(columns);
+ setLook(look);
+ }
+
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + ((body == null) ? 0 : body.hashCode());
+ result = prime * result + bodyCol;
+ result = prime * result + (cellClippingEnabled ? 1231 : 1237);
+ result = prime * result + GridPrint.hashCode(columnGroups);
+ result = prime * result + ((columns == null) ? 0 : columns.hashCode());
+ result = prime * result + ((footer == null) ? 0 : footer.hashCode());
+ result = prime * result + footerCol;
+ result = prime * result + ((header == null) ? 0 : header.hashCode());
+ result = prime * result + headerCol;
+ result = prime * result + ((look == null) ? 0 : look.hashCode());
+ return result;
+ }
+
+ private static int hashCode(int[][] array) {
+ int prime = 31;
+ if (array == null)
+ return 0;
+ int result = 1;
+ for (int index = 0; index < array.length; index++) {
+ result = prime * result
+ + (array[index] == null ? 0 : hashCode(array[index]));
+ }
+ return result;
+ }
+
+ private static int hashCode(int[] array) {
+ int prime = 31;
+ if (array == null)
+ return 0;
+ int result = 1;
+ for (int index = 0; index < array.length; index++) {
+ result = prime * result + array[index];
+ }
+ return result;
+ }
+
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (obj == null)
+ return false;
+ if (getClass() != obj.getClass())
+ return false;
+ GridPrint other = (GridPrint) obj;
+ if (body == null) {
+ if (other.body != null)
+ return false;
+ } else if (!body.equals(other.body))
+ return false;
+ if (bodyCol != other.bodyCol)
+ return false;
+ if (cellClippingEnabled != other.cellClippingEnabled)
+ return false;
+ if (!Util.equal(columnGroups, other.columnGroups))
+ return false;
+ if (columns == null) {
+ if (other.columns != null)
+ return false;
+ } else if (!columns.equals(other.columns))
+ return false;
+ if (footer == null) {
+ if (other.footer != null)
+ return false;
+ } else if (!footer.equals(other.footer))
+ return false;
+ if (footerCol != other.footerCol)
+ return false;
+ if (header == null) {
+ if (other.header != null)
+ return false;
+ } else if (!header.equals(other.header))
+ return false;
+ if (headerCol != other.headerCol)
+ return false;
+ if (look == null) {
+ if (other.look != null)
+ return false;
+ } else if (!look.equals(other.look))
+ return false;
+ return true;
+ }
+
+ /**
+ * Adds the column on the right edge of the grid. Any cells which have been
+ * added to the grid prior to adding the column will be adjusted as follows:
+ * the right-hand cell of each completed row will have it's colspan expanded
+ * to fill the added column.
+ *
+ * @param column
+ * the column to add to the grid.
+ * @see GridColumn#parse(String)
+ */
+ public void addColumn(String column) {
+ addColumn(columns.size(), GridColumn.parse(column));
+ }
+
+ /**
+ * Adds the column on the right edge of the grid. Any cells which have been
+ * added to the grid prior to adding the column will be adjusted as follows:
+ * the right-hand cell of each completed row will have it's colspan expanded
+ * to fill the added column.
+ *
+ * @param column
+ * the column to add to the grid.
+ */
+ public void addColumn(GridColumn column) {
+ addColumn(columns.size(), column);
+ }
+
+ /**
+ * Inserts the column at the specified position in the grid. Any cells which
+ * have been added to the grid prior to adding the column will be adjusted
+ * as follows: on each row, the cell which overlaps or whose right edge
+ * touches the insert position will be expanded to fill the added column.
+ *
+ * @param index
+ * the insert position.
+ * @param column
+ * the column to be inserted.
+ * @see GridColumn#parse(String)
+ */
+ public void addColumn(int index, String column) {
+ addColumn(index, GridColumn.parse(column));
+ }
+
+ /**
+ * Inserts the column at the specified position in the grid. Any cells which
+ * have been added to the grid prior to adding the column will be adjusted
+ * as follows: on each row, the cell which overlaps or whose right edge
+ * touches the insert position will be expanded to fill the added column.
+ *
+ * @param index
+ * the insert position.
+ * @param column
+ * the column to be inserted.
+ */
+ public void addColumn(int index, GridColumn column) {
+ checkColumnInsert(index);
+ Util.notNull(column);
+
+ this.columns.add(index, column);
+ adjustForColumnInsert(index, 1);
+ }
+
+ /**
+ * Adds the columns on the right edge of the grid. Any cells which have been
+ * added to the grid prior to adding the columns will be adjusted as
+ * follows: the right-hand cell of each completed row will have it's colspan
+ * expanded to fill the added columns.
+ *
+ * @param columns
+ * the columns to add to the grid.
+ * @see GridColumn#parse(String)
+ */
+ public void addColumns(String columns) {
+ addColumns(this.columns.size(), parseColumns(columns));
+ }
+
+ /**
+ * Adds the columns on the right edge of the grid. Any cells which have been
+ * added to the grid prior to adding the columns will be adjusted as
+ * follows: the right-hand cell of each completed row will have it's colspan
+ * expanded to fill the added columns.
+ *
+ * @param columns
+ * the columns to add to the grid.
+ */
+ public void addColumns(GridColumn[] columns) {
+ addColumns(this.columns.size(), columns);
+ }
+
+ /**
+ * Inserts the columns at the specified position in the grid. Any cells
+ * which have been added to the grid prior to adding the columns will be
+ * adjusted as follows: on each row, the cell which overlaps or whose right
+ * edge touches the insert position will be expanded to fill the added
+ * columns.
+ *
+ * @param index
+ * the insert position.
+ * @param columns
+ * the columns to be inserted.
+ * @see GridColumn#parse(String)
+ */
+ public void addColumns(int index, String columns) {
+ addColumns(index, parseColumns(columns));
+ }
+
+ /**
+ * Inserts the columns at the specified position in the grid. Any cells
+ * which have been added to the grid prior to adding the columns will be
+ * adjusted as follows: on each row, the cell which overlaps or whose right
+ * edge touches the insert position will be expanded to fill the added
+ * columns.
+ *
+ * @param index
+ * the insert position.
+ * @param columns
+ * the columns to be inserted.
+ * @see GridColumn#parse(String)
+ */
+ public void addColumns(int index, GridColumn[] columns) {
+ checkColumnInsert(index);
+ Util.noNulls(columns);
+
+ this.columns.addAll(index, Arrays.asList(columns));
+
+ adjustForColumnInsert(index, columns.length);
+ }
+
+ private void checkColumnInsert(int index) {
+ if (index < 0 || index > this.columns.size())
+ PaperClips.error(SWT.ERROR_INVALID_RANGE,
+ "index = " + index + ", size = " + this.columns.size()); //$NON-NLS-1$ //$NON-NLS-2$
+ }
+
+ private void adjustForColumnInsert(int index, int count) {
+ adjustCellsForColumnInsert(header, index, count);
+ adjustCellsForColumnInsert(body, index, count);
+ adjustCellsForColumnInsert(footer, index, count);
+
+ adjustColumnGroupsForColumnInsert(index, count);
+
+ if (bodyCol > index)
+ bodyCol += count;
+ if (headerCol > index)
+ headerCol += count;
+ if (footerCol > index)
+ footerCol += count;
+ }
+
+ private void adjustCellsForColumnInsert(List rows, int index, int count) {
+ for (int rowI = 0; rowI < rows.size(); rowI++) {
+ List row = (List) rows.get(rowI);
+ int col = 0;
+ for (int cellI = 0; cellI < row.size(); cellI++) {
+ GridCell cell = (GridCell) row.get(cellI);
+ col += cell.getColSpan();
+
+ // Adjust the cell which extends through the insert point, or
+ // whose right side touches the insert
+ // point. Except on the last row, don't adjust the final cell if
+ // it only touches the insert point
+ // (the user may be adding columns right before s/he adds column
+ // headers).
+ if ( // cell overlaps insert point, or
+ (col > index) ||
+ // right side touches insert point but is not the final cell.
+ (col == index && (rowI + 1 < rows.size() || cellI + 1 < row
+ .size()))) {
+ row.set(cellI,
+ new GridCellImpl(cell.getHorizontalAlignment(),
+ cell.getVerticalAlignment(), cell
+ .getContent(), cell.getColSpan()
+ + count));
+ break;
+ }
+ }
+ }
+ }
+
+ private void adjustColumnGroupsForColumnInsert(int index, int count) {
+ for (int groupI = 0; groupI < columnGroups.length; groupI++) {
+ int[] group = columnGroups[groupI];
+ for (int i = 0; i < group.length; i++)
+ if (group[i] >= index)
+ group[i] += count;
+ }
+ }
+
+ /**
+ * Separates the comma-separated argument and parses each piece to obtain an
+ * array of GridColumns.
+ *
+ * @param columns
+ * the comma-separated list of column specs.
+ * @return GridColumn array with the requested columns.
+ */
+ private static GridColumn[] parseColumns(String columns) {
+ Util.notNull(columns);
+ String[] cols = columns.split("\\s*,\\s*"); //$NON-NLS-1$
+
+ GridColumn[] result = new GridColumn[cols.length];
+ for (int i = 0; i < cols.length; i++)
+ result[i] = GridColumn.parse(cols[i]);
+
+ return result;
+ }
+
+ /**
+ * Returns an array of <code>GridColumn</code>s which are the columns in the
+ * receiver.
+ *
+ * @return an array of <code>GridColumn</code>s which are the columns in the
+ * receiver.
+ */
+ public GridColumn[] getColumns() {
+ return (GridColumn[]) columns.toArray(new GridColumn[columns.size()]);
+ }
+
+ /**
+ * Adds the Print to the grid header, with default alignment and a colspan
+ * of 1.
+ *
+ * @param cell
+ * the print to add.
+ */
+ public void addHeader(Print cell) {
+ headerCol = add(header, headerCol, SWT.DEFAULT, SWT.DEFAULT, cell, 1);
+ }
+
+ /**
+ * Adds the Print to the grid header, using the given alignment.
+ *
+ * @param hAlignment
+ * the horizontal alignment of the print within the grid cell.
+ * One of {@link SWT#DEFAULT} , {@link SWT#LEFT},
+ * {@link SWT#CENTER} or {@link SWT#RIGHT}.
+ * @param cell
+ * the print to add.
+ */
+ public void addHeader(int hAlignment, Print cell) {
+ headerCol = add(header, headerCol, hAlignment, SWT.DEFAULT, cell, 1);
+ }
+
+ /**
+ * Adds the Print to the grid header, using the given alignment.
+ *
+ * @param hAlignment
+ * the horizontal alignment of the print within the grid cell.
+ * One of {@link SWT#DEFAULT} , {@link SWT#LEFT},
+ * {@link SWT#CENTER} or {@link SWT#RIGHT}.
+ * @param vAlignment
+ * the vertical alignment of the print within the grid cell. One
+ * of {@link SWT#DEFAULT}, {@link SWT#TOP}, {@link SWT#CENTER},
+ * {@link SWT#BOTTOM}, or {@link SWT#FILL}. A value of FILL
+ * indicates that the cell is vertically greedy, so GridPrint
+ * will limit the cell's height to the tallest non-FILL cell in
+ * the row.
+ * @param cell
+ * the print to add.
+ */
+ public void addHeader(int hAlignment, int vAlignment, Print cell) {
+ headerCol = add(header, headerCol, hAlignment, vAlignment, cell, 1);
+ }
+
+ /**
+ * Adds the Print to the grid header, with the given colspan and the default
+ * alignment.
+ *
+ * @param cell
+ * the print to add.
+ * @param colspan
+ * the number of columns to span, or {@link GridPrint#REMAINDER}
+ * to span the rest of the row.
+ */
+ public void addHeader(Print cell, int colspan) {
+ headerCol = add(header, headerCol, SWT.DEFAULT, SWT.DEFAULT, cell,
+ colspan);
+ }
+
+ /**
+ * Adds the Print to the grid header, using the given colspan and alignment.
+ *
+ * @param cell
+ * the print to add.
+ * @param colspan
+ * the number of columns to span, or {@link GridPrint#REMAINDER}
+ * to span the rest of the row.
+ * @param hAlignment
+ * the horizontal alignment of the print within the grid cell.
+ * One of {@link SWT#DEFAULT} , {@link SWT#LEFT},
+ * {@link SWT#CENTER} or {@link SWT#RIGHT}.
+ */
+ public void addHeader(int hAlignment, Print cell, int colspan) {
+ headerCol = add(header, headerCol, hAlignment, SWT.DEFAULT, cell,
+ colspan);
+ }
+
+ /**
+ * Adds the Print to the grid header, using the given colspan and alignment.
+ *
+ * @param hAlignment
+ * the horizontal alignment of the print within the grid cell.
+ * One of {@link SWT#DEFAULT} , {@link SWT#LEFT},
+ * {@link SWT#CENTER} or {@link SWT#RIGHT}.
+ * @param vAlignment
+ * the vertical alignment of the print within the grid cell. One
+ * of {@link SWT#DEFAULT}, {@link SWT#TOP}, {@link SWT#CENTER},
+ * {@link SWT#BOTTOM}, or {@link SWT#FILL}. A value of FILL
+ * indicates that the cell is vertically greedy, so GridPrint
+ * will limit the cell's height to the tallest non-FILL cell in
+ * the row.
+ * @param cell
+ * the print to add.
+ * @param colspan
+ * the number of columns to span, or {@link GridPrint#REMAINDER}
+ * to span the rest of the row.
+ */
+ public void addHeader(int hAlignment, int vAlignment, Print cell,
+ int colspan) {
+ headerCol = add(header, headerCol, hAlignment, vAlignment, cell,
+ colspan);
+ }
+
+ /**
+ * Returns an array containing the header cells in this grid. Each inner
+ * array represents one row in the header.
+ *
+ * @return an array containing the header cells in this grid.
+ */
+ public GridCell[][] getHeaderCells() {
+ return getGridCellArray(header);
+ }
+
+ /**
+ * Returns an array containing the body cells in the grid. Each inner array
+ * represents one row in the body.
+ *
+ * @return an array containing the body cells in the grid.
+ */
+ public GridCell[][] getBodyCells() {
+ return getGridCellArray(body);
+ }
+
+ /**
+ * Returns an array containing the footer cells in the grid. Each inner
+ * array represents one row in the footer.
+ *
+ * @return an array containing the footer cells in the grid.
+ */
+ public GridCell[][] getFooterCells() {
+ return getGridCellArray(footer);
+ }
+
+ private static GridCell[][] getGridCellArray(List list) {
+ GridCell[][] cells = new GridCell[list.size()][];
+ for (int rowIndex = 0; rowIndex < cells.length; rowIndex++) {
+ List row = (List) list.get(rowIndex);
+ GridCell[] rowCells = new GridCell[row.size()];
+ for (int cellIndex = 0; cellIndex < rowCells.length; cellIndex++)
+ rowCells[cellIndex] = (GridCell) row.get(cellIndex);
+ cells[rowIndex] = rowCells;
+ }
+ return cells;
+ }
+
+ /**
+ * Adds the Print to the grid body, with the default alignment and a colspan
+ * of 1.
+ *
+ * @param cell
+ * the print to add.
+ */
+ public void add(Print cell) {
+ bodyCol = add(body, bodyCol, SWT.DEFAULT, SWT.DEFAULT, cell, 1);
+ }
+
+ /**
+ * Adds the Print to the grid body, using the given colspan and alignment.
+ *
+ * @param hAlignment
+ * the horizontal alignment of the print within the grid cell.
+ * One of {@link SWT#DEFAULT} , {@link SWT#LEFT},
+ * {@link SWT#CENTER} or {@link SWT#RIGHT}.
+ * @param cell
+ * the print to add.
+ */
+ public void add(int hAlignment, Print cell) {
+ bodyCol = add(body, bodyCol, hAlignment, SWT.DEFAULT, cell, 1);
+ }
+
+ /**
+ * Adds the Print to the grid body, using the given colspan and alignment.
+ *
+ * @param hAlignment
+ * the horizontal alignment of the print within the grid cell.
+ * One of {@link SWT#DEFAULT} , {@link SWT#LEFT},
+ * {@link SWT#CENTER} or {@link SWT#RIGHT}.
+ * @param vAlignment
+ * the vertical alignment of the print within the grid cell. One
+ * of {@link SWT#DEFAULT}, {@link SWT#TOP}, {@link SWT#CENTER},
+ * {@link SWT#BOTTOM}, or {@link SWT#FILL}. A value of FILL
+ * indicates that the cell is vertically greedy, so GridPrint
+ * will limit the cell's height to the tallest non-FILL cell in
+ * the row.
+ * @param cell
+ * the print to add.
+ */
+ public void add(int hAlignment, int vAlignment, Print cell) {
+ bodyCol = add(body, bodyCol, hAlignment, vAlignment, cell, 1);
+ }
+
+ /**
+ * Adds the Print to the grid body, with the given colspan and the default
+ * alignment.
+ *
+ * @param cell
+ * the print to add.
+ * @param colspan
+ * the number of columns to span, or {@link GridPrint#REMAINDER}
+ * to span the rest of the row.
+ */
+ public void add(Print cell, int colspan) {
+ bodyCol = add(body, bodyCol, SWT.DEFAULT, SWT.DEFAULT, cell, colspan);
+ }
+
+ /**
+ * Adds the Print to the grid body, using the given colspan and alignment.
+ *
+ * @param hAlignment
+ * the horizontal alignment of the print within the grid cell.
+ * One of {@link SWT#DEFAULT} , {@link SWT#LEFT},
+ * {@link SWT#CENTER} or {@link SWT#RIGHT}.
+ * @param cell
+ * the print to add.
+ * @param colspan
+ * the number of columns to span, or {@link GridPrint#REMAINDER}
+ * to span the rest of the row.
+ */
+ public void add(int hAlignment, Print cell, int colspan) {
+ bodyCol = add(body, bodyCol, hAlignment, SWT.DEFAULT, cell, colspan);
+ }
+
+ /**
+ * Adds the Print to the grid body, using the given colspan and alignment.
+ *
+ * @param hAlignment
+ * the horizontal alignment of the print within the grid cell.
+ * One of {@link SWT#DEFAULT} , {@link SWT#LEFT},
+ * {@link SWT#CENTER} or {@link SWT#RIGHT}.
+ * @param vAlignment
+ * the vertical alignment of the print within the grid cell. One
+ * of {@link SWT#DEFAULT}, {@link SWT#TOP}, {@link SWT#CENTER},
+ * {@link SWT#BOTTOM}, or {@link SWT#FILL}. A value of FILL
+ * indicates that the cell is vertically greedy, so GridPrint
+ * will limit the cell's height to the tallest non-FILL cell in
+ * the row.
+ * @param cell
+ * the print to add.
+ * @param colspan
+ * the number of columns to span, or {@link GridPrint#REMAINDER}
+ * to span the rest of the row.
+ */
+ public void add(int hAlignment, int vAlignment, Print cell, int colspan) {
+ bodyCol = add(body, bodyCol, hAlignment, vAlignment, cell, colspan);
+ }
+
+ /**
+ * Returns whether individual body cells in the grid may be broken across
+ * pages. Defaults to true.
+ *
+ * @return whether individual body cells in the grid may be broken across
+ * pages.
+ */
+ public boolean isCellClippingEnabled() {
+ return cellClippingEnabled;
+ }
+
+ /**
+ * Sets whether individual body cells in the grid may be broken across
+ * pages.
+ *
+ * @param cellClippingEnabled
+ * whether to enabled cell clipping.
+ */
+ public void setCellClippingEnabled(boolean cellClippingEnabled) {
+ this.cellClippingEnabled = cellClippingEnabled;
+ }
+
+ /**
+ * Adds the Print to the grid footer, with the default alignment and a
+ * colspan of 1.
+ *
+ * @param cell
+ * the print to add.
+ */
+ public void addFooter(Print cell) {
+ footerCol = add(footer, footerCol, SWT.DEFAULT, SWT.DEFAULT, cell, 1);
+ }
+
+ /**
+ * Adds the Print to the grid footer, using the given colspan and alignment.
+ *
+ * @param hAlignment
+ * the horizontal alignment of the print within the grid cell.
+ * One of {@link SWT#DEFAULT} , {@link SWT#LEFT},
+ * {@link SWT#CENTER} or {@link SWT#RIGHT}.
+ * @param cell
+ * the print to add.
+ */
+ public void addFooter(int hAlignment, Print cell) {
+ footerCol = add(footer, footerCol, hAlignment, SWT.DEFAULT, cell, 1);
+ }
+
+ /**
+ * Adds the Print to the grid footer, using the given colspan and alignment.
+ *
+ * @param hAlignment
+ * the horizontal alignment of the print within the grid cell.
+ * One of {@link SWT#DEFAULT} , {@link SWT#LEFT},
+ * {@link SWT#CENTER} or {@link SWT#RIGHT}.
+ * @param vAlignment
+ * the vertical alignment of the print within the grid cell. One
+ * of {@link SWT#DEFAULT}, {@link SWT#TOP}, {@link SWT#CENTER},
+ * {@link SWT#BOTTOM}, or {@link SWT#FILL}. A value of FILL
+ * indicates that the cell is vertically greedy, so GridPrint
+ * will limit the cell's height to the tallest non-FILL cell in
+ * the row.
+ * @param cell
+ * the print to add.
+ */
+ public void addFooter(int hAlignment, int vAlignment, Print cell) {
+ footerCol = add(footer, footerCol, hAlignment, vAlignment, cell, 1);
+ }
+
+ /**
+ * Adds the Print to the grid footer, with the given colspan and the default
+ * alignment.
+ *
+ * @param cell
+ * the print to add.
+ * @param colspan
+ * the number of columns to span, or {@link GridPrint#REMAINDER}
+ * to span the rest of the row.
+ */
+ public void addFooter(Print cell, int colspan) {
+ footerCol = add(footer, footerCol, SWT.DEFAULT, SWT.DEFAULT, cell,
+ colspan);
+ }
+
+ /**
+ * Adds the Print to the grid footer, using the given colspan and alignment.
+ *
+ * @param hAlignment
+ * the horizontal alignment of the print within the grid cell.
+ * One of {@link SWT#DEFAULT} , {@link SWT#LEFT},
+ * {@link SWT#CENTER} or {@link SWT#RIGHT}.
+ * @param cell
+ * the print to add.
+ * @param colspan
+ * the number of columns to span, or {@link GridPrint#REMAINDER}
+ * to span the rest of the row.
+ */
+ public void addFooter(int hAlignment, Print cell, int colspan) {
+ footerCol = add(footer, footerCol, hAlignment, SWT.DEFAULT, cell,
+ colspan);
+ }
+
+ /**
+ * Adds the Print to the grid footer, using the given colspan and alignment.
+ *
+ * @param hAlignment
+ * the horizontal alignment of the print within the grid cell.
+ * One of {@link SWT#DEFAULT} , {@link SWT#LEFT},
+ * {@link SWT#CENTER} or {@link SWT#RIGHT}.
+ * @param vAlignment
+ * the vertical alignment of the print within the grid cell. One
+ * of {@link SWT#DEFAULT}, {@link SWT#TOP}, {@link SWT#CENTER},
+ * {@link SWT#BOTTOM}, or {@link SWT#FILL}. A value of FILL
+ * indicates that the cell is vertically greedy, so GridPrint
+ * will limit the cell's height to the tallest non-FILL cell in
+ * the row.
+ * @param cell
+ * the print to add.
+ * @param colspan
+ * the number of columns to span, or {@link GridPrint#REMAINDER}
+ * to span the rest of the row.
+ */
+ public void addFooter(int hAlignment, int vAlignment, Print cell,
+ int colspan) {
+ footerCol = add(footer, footerCol, hAlignment, vAlignment, cell,
+ colspan);
+ }
+
+ /*
+ * Returns the column number that we've advanced to, after adding the new
+ * cell.
+ */
+ private int add(
+ List rows, // List of List of GridCell
+ int startColumn, int hAlignment, int vAlignment,
+ Print cellContents, int colspan) {
+ startColumn = startNewRowIfCurrentRowFull(startColumn);
+ checkColumnSpan(startColumn, colspan);
+ List row = getOpenRow(rows, startColumn);
+ colspan = convertRemainderToExplicitColSpan(startColumn, colspan);
+
+ GridCell cell = new GridCellImpl(hAlignment, vAlignment, cellContents,
+ colspan);
+ row.add(cell);
+ startColumn += colspan;
+
+ // Make sure column number is valid.
+ if (startColumn > columns.size()) {
+ // THIS SHOULD NOT HAPPEN--ABOVE LOGIC SHOULD PREVENT THIS CASE
+ // ..but just in case.
+
+ row.remove(row.size() - 1);
+ if (row.size() == 0)
+ rows.remove(row);
+
+ PaperClips.error(SWT.ERROR_INVALID_ARGUMENT, "Colspan " + colspan //$NON-NLS-1$
+ + " too wide at column " + startColumn + " (" //$NON-NLS-1$ //$NON-NLS-2$
+ + columns.size() + " columns total)"); //$NON-NLS-1$
+ }
+
+ return startColumn;
+ }
+
+ private int convertRemainderToExplicitColSpan(int startColumn, int colspan) {
+ if (colspan == REMAINDER)
+ colspan = columns.size() - startColumn;
+ return colspan;
+ }
+
+ private int startNewRowIfCurrentRowFull(int startColumn) {
+ // If we're at the end of a row, start a new row.
+ if (startColumn == columns.size())
+ startColumn = 0;
+ return startColumn;
+ }
+
+ private void checkColumnSpan(int startColumn, int colspan) {
+ if (startColumn + colspan > columns.size())
+ PaperClips.error(SWT.ERROR_INVALID_ARGUMENT, "Colspan " + colspan //$NON-NLS-1$
+ + " too wide at column " + startColumn + " (" //$NON-NLS-1$ //$NON-NLS-2$
+ + columns.size() + " columns total)"); //$NON-NLS-1$
+ }
+
+ private List getOpenRow(List rows, int startColumn) {
+ List row; // the row we will add the cell to.
+ if (startColumn == 0)
+ // Start a new row if back at column 0.
+ rows.add(row = new ArrayList(columns.size()));
+ else
+ // Get the incomplete row.
+ row = (List) rows.get(rows.size() - 1); // List of GridCell
+ return row;
+ }
+
+ /**
+ * Returns current column groups. The returned array may be modified without
+ * affecting this GridPrint.
+ *
+ * @return the column groups.
+ */
+ public int[][] getColumnGroups() {
+ return PaperClipsUtil.copy(columnGroups);
+ }
+
+ /**
+ * Sets the column groups to the given two-dimension array. Each int[] array
+ * is a group. Columns in a group will be the same size when laid out on the
+ * print device.
+ * <p>
+ * The following statement causes columns 0 and 2 to be the same size, and
+ * columns 1 and 3 to be the same size.
+ *
+ * <pre>
+ * grid.setColumnGroups(new int[][] { { 0, 2 }, { 1, 3 } });
+ * </pre>
+ *
+ * <p>
+ * The behavior of this property is undefined when a column belongs to more
+ * than one group.
+ * <p>
+ * <b>Note:</b> Column grouping is enforced <i>before</i> column weights.
+ * Therefore, columns in the same group should be given the same weight to
+ * ensure they are laid out at the same width.
+ *
+ * @param columnGroups
+ * the new column groups.
+ */
+ public void setColumnGroups(int[][] columnGroups) {
+ checkColumnGroups(columnGroups);
+ this.columnGroups = PaperClipsUtil.copy(columnGroups);
+ }
+
+ private void checkColumnGroups(int[][] columnGroups) {
+ Util.notNull(columnGroups);
+ for (int groupIndex = 0; groupIndex < columnGroups.length; groupIndex++)
+ checkColumnGroup(columnGroups[groupIndex]);
+ }
+
+ private void checkColumnGroup(int[] columnGroup) {
+ Util.notNull(columnGroup);
+ for (int columnInGroupIndex = 0; columnInGroupIndex < columnGroup.length; columnInGroupIndex++)
+ checkColumnIndex(columnGroup[columnInGroupIndex]);
+ }
+
+ private void checkColumnIndex(int columnIndex) {
+ if (columnIndex < 0 || columnIndex >= columns.size())
+ PaperClips.error(SWT.ERROR_INVALID_RANGE,
+ "Column index in column group must be " + "0 <= " //$NON-NLS-1$ //$NON-NLS-2$
+ + columnIndex + " < " + columns.size()); //$NON-NLS-1$
+ }
+
+ /**
+ * Returns the grid's look. A GridLook determines what decorations will
+ * appear around the grid's contents. Default is a DefaultGridLook with no
+ * cell spacing, no cell borders, and no background colors.
+ *
+ * @return the look of this grid.
+ */
+ public GridLook getLook() {
+ return look;
+ }
+
+ /**
+ * Sets the grid's look.
+ *
+ * @param look
+ * the new look.
+ */
+ public void setLook(GridLook look) {
+ Util.notNull(look);
+ this.look = look;
+ }
+
+ public PrintIterator iterator(Device device, GC gc) {
+ return new GridIterator(this, device, gc);
+ }
+}
diff --git a/plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/grid/internal/DefaultGridLookPainter.java b/plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/grid/internal/DefaultGridLookPainter.java
new file mode 100644
index 0000000000..ca4f548fcf
--- /dev/null
+++ b/plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/grid/internal/DefaultGridLookPainter.java
@@ -0,0 +1,141 @@
+/*
+ * Copyright (c) 2007 Matthew Hall 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:
+ * Matthew Hall - initial API and implementation
+ */
+
+package org.eclipse.sirius.table.ui.tools.internal.paperclips.grid.internal;
+
+import org.eclipse.sirius.table.ui.tools.internal.paperclips.border.BorderPainter;
+import org.eclipse.sirius.table.ui.tools.internal.paperclips.grid.BasicGridLookPainter;
+import org.eclipse.sirius.table.ui.tools.internal.paperclips.grid.CellBackgroundProvider;
+import org.eclipse.sirius.table.ui.tools.internal.paperclips.grid.DefaultGridLook;
+import org.eclipse.sirius.table.ui.tools.internal.paperclips.grid.GridMargins;
+import org.eclipse.sirius.table.ui.tools.internal.paperclips.internal.util.ResourcePool;
+import org.eclipse.swt.graphics.Color;
+import org.eclipse.swt.graphics.Device;
+import org.eclipse.swt.graphics.GC;
+import org.eclipse.swt.graphics.Point;
+import org.eclipse.swt.graphics.RGB;
+import org.eclipse.swt.graphics.Rectangle;
+
+public class DefaultGridLookPainter extends BasicGridLookPainter {
+ private final Rectangle cellPadding;
+
+ private final BorderPainter border;
+
+ private final CellBackgroundProvider headerBackground;
+ private final CellBackgroundProvider bodyBackground;
+ private final CellBackgroundProvider footerBackground;
+
+ private final GridMargins margins;
+
+ private final ResourcePool resources;
+
+ public DefaultGridLookPainter(DefaultGridLook look, Device device, GC gc) {
+ super(device);
+
+ Point dpi = device.getDPI();
+
+ this.border = look.getCellBorder().createPainter(device, gc);
+ this.cellPadding = calculateCellPadding(look, dpi);
+ this.margins = calculateGridMargins(look, dpi);
+
+ this.bodyBackground = look.getBodyBackgroundProvider();
+ this.headerBackground = look.getHeaderBackgroundProvider();
+ this.footerBackground = look.getFooterBackgroundProvider();
+
+ this.resources = ResourcePool.forDevice(device);
+ }
+
+ private Rectangle calculateCellPadding(DefaultGridLook look, Point dpi) {
+ Rectangle cellPadding = new Rectangle(look.getCellPadding().x * dpi.x
+ / 72, look.getCellPadding().y * dpi.y / 72,
+ look.getCellPadding().width * dpi.x / 72,
+ look.getCellPadding().height * dpi.y / 72);
+ return cellPadding;
+ }
+
+ private GridMargins calculateGridMargins(DefaultGridLook look, Point dpi) {
+ final Point cellSpacing = new Point(
+ border.getWidth()
+ + (look.getCellSpacing().x == DefaultGridLook.BORDER_OVERLAP ? -border.getOverlap().x
+ : dpi.x * look.getCellSpacing().x / 72),
+ border.getHeight(false, false)
+ + (look.getCellSpacing().y == DefaultGridLook.BORDER_OVERLAP ? -border
+ .getOverlap().y : dpi.y
+ * look.getCellSpacing().y / 72));
+
+ final int headerClosedSpacing = border.getHeight(false, false)
+ + (look.getHeaderGap() == DefaultGridLook.BORDER_OVERLAP ? -border
+ .getOverlap().y : dpi.y * look.getHeaderGap() / 72);
+ final int headerOpenSpacing = border.getHeight(true, false)
+ + (look.getHeaderGap() == DefaultGridLook.BORDER_OVERLAP ? dpi.y / 72
+ : dpi.y * look.getHeaderGap() / 72);
+ final int footerClosedSpacing = border.getHeight(false, false)
+ + (look.getFooterGap() == DefaultGridLook.BORDER_OVERLAP ? -border
+ .getOverlap().y : dpi.y * look.getFooterGap() / 72);
+ final int footerOpenSpacing = border.getHeight(false, true)
+ + (look.getFooterGap() == DefaultGridLook.BORDER_OVERLAP ? dpi.y / 72
+ : dpi.y * look.getFooterGap() / 72);
+
+ return new DefaultGridMargins(border, cellSpacing, cellPadding,
+ headerClosedSpacing, headerOpenSpacing, footerClosedSpacing,
+ footerOpenSpacing);
+ }
+
+ public GridMargins getMargins() {
+ return margins;
+ }
+
+ protected void paintHeaderCell(GC gc, Rectangle bounds, int row, int col,
+ int colspan) {
+ RGB background = headerBackground.getCellBackground(row, col, colspan);
+ paintCell(gc, background, bounds, false, false);
+ }
+
+ protected void paintBodyCell(GC gc, Rectangle bounds, int row, int col,
+ int colspan, boolean topOpen, boolean bottomOpen) {
+ RGB background = bodyBackground.getCellBackground(row, col, colspan);
+ paintCell(gc, background, bounds, topOpen, bottomOpen);
+ }
+
+ protected void paintFooterCell(GC gc, Rectangle bounds, int row, int col,
+ int colspan) {
+ RGB background = footerBackground.getCellBackground(row, col, colspan);
+ paintCell(gc, background, bounds, false, false);
+ }
+
+ private void paintCell(GC gc, RGB background, Rectangle bounds,
+ boolean topOpen, boolean bottomOpen) {
+ // Compute effective cell rectangle
+ int x = bounds.x - border.getLeft() - cellPadding.x;
+ int y = bounds.y - border.getTop(topOpen)
+ - (topOpen ? 0 : cellPadding.y);
+ int width = bounds.width + border.getWidth() + cellPadding.x
+ + cellPadding.width;
+ int height = bounds.height + border.getHeight(topOpen, bottomOpen)
+ + (bottomOpen ? 0 : cellPadding.y + cellPadding.height);
+
+ // Paint background
+ Color backgroundColor = resources.getColor(background);
+ if (backgroundColor != null) {
+ Color oldBackground = gc.getBackground();
+ gc.setBackground(backgroundColor);
+ gc.fillRectangle(x, y, width, height);
+ gc.setBackground(oldBackground);
+ }
+
+ // Paint border
+ border.paint(gc, x, y, width, height, topOpen, bottomOpen);
+ }
+
+ public void dispose() {
+ border.dispose();
+ }
+} \ No newline at end of file
diff --git a/plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/grid/internal/DefaultGridMargins.java b/plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/grid/internal/DefaultGridMargins.java
new file mode 100644
index 0000000000..24a263437f
--- /dev/null
+++ b/plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/grid/internal/DefaultGridMargins.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (c) 2007 Matthew Hall 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:
+ * Matthew Hall - initial API and implementation
+ */
+
+package org.eclipse.sirius.table.ui.tools.internal.paperclips.grid.internal;
+
+import org.eclipse.sirius.table.ui.tools.internal.paperclips.border.BorderPainter;
+import org.eclipse.sirius.table.ui.tools.internal.paperclips.grid.GridMargins;
+import org.eclipse.swt.graphics.Point;
+import org.eclipse.swt.graphics.Rectangle;
+
+class DefaultGridMargins implements GridMargins {
+ private final BorderPainter border;
+ private final Point cellSpacing;
+ private final Rectangle cellPadding;
+ private final int headerClosedSpacing;
+ private final int headerOpenSpacing;
+ private final int footerClosedSpacing;
+ private final int footerOpenSpacing;
+
+ DefaultGridMargins(BorderPainter border, Point cellSpacing,
+ Rectangle cellPadding, int headerClosedSpacing,
+ int headerOpenSpacing, int footerClosedSpacing,
+ int footerOpenSpacing) {
+ this.border = border;
+ this.cellSpacing = cellSpacing;
+ this.cellPadding = cellPadding;
+ this.headerClosedSpacing = headerClosedSpacing;
+ this.headerOpenSpacing = headerOpenSpacing;
+ this.footerClosedSpacing = footerClosedSpacing;
+ this.footerOpenSpacing = footerOpenSpacing;
+ }
+
+ public int getLeft() {
+ return border.getLeft() + cellPadding.x;
+ }
+
+ public int getHorizontalSpacing() {
+ return cellSpacing.x + cellPadding.x + cellPadding.width;
+ }
+
+ public int getRight() {
+ return border.getRight() + cellPadding.width;
+ }
+
+ public int getHeaderTop() {
+ return border.getTop(false) + cellPadding.y;
+ }
+
+ public int getHeaderVerticalSpacing() {
+ return cellSpacing.y + cellPadding.y + cellPadding.height;
+ }
+
+ public int getBodyTop(boolean headerPresent, boolean open) {
+ return headerPresent ? open ? headerOpenSpacing : headerClosedSpacing
+ + cellPadding.y : open ? border.getTop(true) : border
+ .getTop(false)
+ + cellPadding.y;
+ }
+
+ public int getBodyVerticalSpacing() {
+ return cellSpacing.y + cellPadding.y + cellPadding.height;
+ }
+
+ public int getBodyBottom(boolean footerPresent, boolean open) {
+ return footerPresent ? open ? footerOpenSpacing : footerClosedSpacing
+ + cellPadding.height : open ? border.getBottom(true) : border
+ .getBottom(false)
+ + cellPadding.height;
+ }
+
+ public int getFooterVerticalSpacing() {
+ return cellSpacing.y + cellPadding.y + cellPadding.height;
+ }
+
+ public int getFooterBottom() {
+ return border.getBottom(false) + cellPadding.height;
+ }
+} \ No newline at end of file
diff --git a/plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/grid/internal/GridCellImpl.java b/plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/grid/internal/GridCellImpl.java
new file mode 100644
index 0000000000..0c011c7253
--- /dev/null
+++ b/plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/grid/internal/GridCellImpl.java
@@ -0,0 +1,159 @@
+package org.eclipse.sirius.table.ui.tools.internal.paperclips.grid.internal;
+
+import org.eclipse.sirius.table.ui.tools.internal.paperclips.PaperClips;
+import org.eclipse.sirius.table.ui.tools.internal.paperclips.Print;
+import org.eclipse.sirius.table.ui.tools.internal.paperclips.grid.GridCell;
+import org.eclipse.sirius.table.ui.tools.internal.paperclips.grid.GridPrint;
+import org.eclipse.sirius.table.ui.tools.internal.paperclips.internal.util.PaperClipsUtil;
+import org.eclipse.sirius.table.ui.tools.internal.paperclips.internal.util.Util;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.graphics.Device;
+import org.eclipse.swt.graphics.GC;
+import org.eclipse.swt.graphics.Point;
+
+/**
+ * Instances of this class represent a single cell in a GridPrint.
+ *
+ * @author Matthew Hall
+ */
+public class GridCellImpl implements GridCell {
+
+ private final int hAlignment;
+ private final int vAlignment;
+ private final Print target;
+ private final int colspan;
+
+ /**
+ * This constructor is only here for compatibility reasons and is not
+ * intented to be used by clients.
+ *
+ * @param hAlignment
+ * the horizontal alignment.
+ * @param vAlignment
+ * the vertical alignment.
+ * @param target
+ * the target of the cell.
+ * @param colspan
+ * the number of columns this cell spans across.
+ */
+
+ public GridCellImpl(int hAlignment, int vAlignment, Print target,
+ int colspan) {
+ Util.notNull(target);
+ this.hAlignment = checkHorizontalAlignment(hAlignment);
+ this.vAlignment = checkVerticalAlignment(vAlignment);
+ this.target = target;
+ this.colspan = checkColspan(colspan);
+ }
+
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + colspan;
+ result = prime * result + hAlignment;
+ result = prime * result + ((target == null) ? 0 : target.hashCode());
+ result = prime * result + vAlignment;
+ return result;
+ }
+
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (obj == null)
+ return false;
+ if (getClass() != obj.getClass())
+ return false;
+ GridCell other = (GridCell) obj;
+ if (colspan != other.getColSpan())
+ return false;
+ if (hAlignment != other.getHorizontalAlignment())
+ return false;
+ if (target == null) {
+ if (other.getContent() != null)
+ return false;
+ } else if (!target.equals(other.getContent()))
+ return false;
+ if (vAlignment != other.getVerticalAlignment())
+ return false;
+ return true;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.eclipse.sirius.ext.paperclips.core.grid.GridCell#getAlignment()
+ */
+ public Point getAlignment() {
+ return new Point(hAlignment, vAlignment);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.eclipse.sirius.ext.paperclips.core.grid.GridCell#getHorizontalAlignment()
+ */
+ public int getHorizontalAlignment() {
+ return hAlignment;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.eclipse.sirius.ext.paperclips.core.grid.GridCell#getVerticalAlignment()
+ */
+ public int getVerticalAlignment() {
+ return vAlignment;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.eclipse.sirius.ext.paperclips.core.grid.GridCell#getContent()
+ */
+ public Print getContent() {
+ return target;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.eclipse.sirius.ext.paperclips.core.grid.GridCell#getColSpan()
+ */
+ public int getColSpan() {
+ return colspan;
+ }
+
+ private static int checkHorizontalAlignment(int hAlignment) {
+ hAlignment = PaperClipsUtil.firstMatch(hAlignment, new int[] {
+ SWT.DEFAULT, SWT.LEFT, SWT.CENTER, SWT.RIGHT }, 0);
+ if (hAlignment == 0)
+ PaperClips
+ .error(SWT.ERROR_INVALID_ARGUMENT,
+ "Alignment argument must be one of SWT.LEFT, SWT.CENTER, SWT.RIGHT, or SWT.DEFAULT"); //$NON-NLS-1$
+ return hAlignment;
+ }
+
+ private static int checkVerticalAlignment(int vAlignment) {
+ vAlignment = PaperClipsUtil.firstMatch(vAlignment, new int[] {
+ SWT.DEFAULT, SWT.TOP, SWT.CENTER, SWT.BOTTOM, SWT.FILL }, 0);
+ if (vAlignment == 0)
+ PaperClips
+ .error(SWT.ERROR_INVALID_ARGUMENT,
+ "Alignment argument must be one of SWT.TOP, SWT.CENTER, SWT.BOTTOM, SWT.DEFAULT, or SWT.FILL"); //$NON-NLS-1$
+ return vAlignment;
+ }
+
+ private int checkColspan(int colspan) {
+ if (colspan <= 0 && colspan != GridPrint.REMAINDER)
+ PaperClips.error(SWT.ERROR_INVALID_ARGUMENT,
+ "colspan must be a positive number or GridPrint.REMAINDER"); //$NON-NLS-1$
+ return colspan;
+ }
+
+ public GridCellIterator iterator(Device device, GC gc) {
+ return new GridCellIterator(this, device, gc);
+ }
+
+} \ No newline at end of file
diff --git a/plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/grid/internal/GridCellIterator.java b/plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/grid/internal/GridCellIterator.java
new file mode 100644
index 0000000000..6bf10a6a5c
--- /dev/null
+++ b/plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/grid/internal/GridCellIterator.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (c) 2007 Matthew Hall 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:
+ * Matthew Hall - initial API and implementation
+ */
+package org.eclipse.sirius.table.ui.tools.internal.paperclips.grid.internal;
+
+import org.eclipse.sirius.table.ui.tools.internal.paperclips.PrintIterator;
+import org.eclipse.sirius.table.ui.tools.internal.paperclips.grid.GridCell;
+import org.eclipse.swt.graphics.Device;
+import org.eclipse.swt.graphics.GC;
+
+public class GridCellIterator {
+ final int hAlignment;
+ final int vAlignment;
+ final PrintIterator target;
+ final int colspan;
+
+ public GridCellIterator(GridCell cell, Device device, GC gc) {
+ this.hAlignment = cell.getHorizontalAlignment();
+ this.vAlignment = cell.getVerticalAlignment();
+ this.target = cell.getContent().iterator(device, gc);
+ this.colspan = cell.getColSpan();
+ }
+
+ private GridCellIterator(GridCellIterator that) {
+ this.hAlignment = that.hAlignment;
+ this.vAlignment = that.vAlignment;
+ this.target = that.target.copy();
+ this.colspan = that.colspan;
+ }
+
+ public int getHorizontalAlignment() {
+ return hAlignment;
+ }
+
+ public int getVerticalAlignment() {
+ return vAlignment;
+ }
+
+ public PrintIterator getTarget() {
+ return target;
+ }
+
+ public int getColspan() {
+ return colspan;
+ }
+
+ public GridCellIterator copy() {
+ return new GridCellIterator(this);
+ }
+} \ No newline at end of file
diff --git a/plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/grid/internal/GridIterator.java b/plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/grid/internal/GridIterator.java
new file mode 100644
index 0000000000..c916d37a8a
--- /dev/null
+++ b/plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/grid/internal/GridIterator.java
@@ -0,0 +1,1040 @@
+/*
+ * Copyright (c) 2007 Matthew Hall 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:
+ * Matthew Hall - initial API and implementation
+ */
+package org.eclipse.sirius.table.ui.tools.internal.paperclips.grid.internal;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+import org.eclipse.sirius.table.ui.tools.internal.paperclips.CompositeEntry;
+import org.eclipse.sirius.table.ui.tools.internal.paperclips.CompositePiece;
+import org.eclipse.sirius.table.ui.tools.internal.paperclips.PaperClips;
+import org.eclipse.sirius.table.ui.tools.internal.paperclips.PrintIterator;
+import org.eclipse.sirius.table.ui.tools.internal.paperclips.PrintPiece;
+import org.eclipse.sirius.table.ui.tools.internal.paperclips.grid.GridCell;
+import org.eclipse.sirius.table.ui.tools.internal.paperclips.grid.GridColumn;
+import org.eclipse.sirius.table.ui.tools.internal.paperclips.grid.GridLookPainter;
+import org.eclipse.sirius.table.ui.tools.internal.paperclips.grid.GridMargins;
+import org.eclipse.sirius.table.ui.tools.internal.paperclips.grid.GridPrint;
+import org.eclipse.sirius.table.ui.tools.internal.paperclips.internal.util.PaperClipsUtil;
+import org.eclipse.sirius.table.ui.tools.internal.paperclips.internal.util.PrintSizeStrategy;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.graphics.Device;
+import org.eclipse.swt.graphics.GC;
+import org.eclipse.swt.graphics.Point;
+
+public class GridIterator implements PrintIterator {
+ final Device device;
+ final Point dpi;
+
+ final GridColumn[] columns;
+ final int[][] columnGroups;
+
+ final GridLookPainter look;
+
+ final GridCellIterator[][] header;
+ final GridCellIterator[][] body;
+ final GridCellIterator[][] footer;
+
+ final boolean cellClippingEnabled;
+
+ final int[] minimumColSizes; // PIXELS
+ final int[] preferredColSizes; // PIXELS
+
+ final Point minimumSize; // PIXELS
+ final Point preferredSize; // PIXELS
+
+ // This is the cursor!
+ private int row;
+
+ // Determines whether top edge of cell border is drawn open or closed for
+ // current row.
+ private boolean rowStarted;
+
+ public GridIterator(GridPrint grid, Device device, GC gc) {
+ this.device = device;
+ this.dpi = device.getDPI();
+ this.columns = new GridColumn[grid.getColumns().length];
+ System.arraycopy(grid.getColumns(), 0, this.columns, 0,
+ grid.getColumns().length);
+ this.columnGroups = grid.getColumnGroups();
+
+ this.header = createGridCellIterators(grid.getHeaderCells(), device, gc);
+ this.body = createGridCellIterators(grid.getBodyCells(), device, gc);
+ this.footer = createGridCellIterators(grid.getFooterCells(), device, gc);
+
+ this.cellClippingEnabled = grid.isCellClippingEnabled();
+
+ this.look = grid.getLook().getPainter(device, gc);
+
+ this.minimumColSizes = computeColumnSizes(PrintSizeStrategy.MINIMUM);
+ this.preferredColSizes = computeColumnSizes(PrintSizeStrategy.PREFERRED);
+
+ this.minimumSize = computeSize(PrintSizeStrategy.MINIMUM,
+ minimumColSizes);
+ this.preferredSize = computeSize(PrintSizeStrategy.PREFERRED,
+ preferredColSizes);
+
+ row = 0;
+ rowStarted = false;
+ }
+
+ private static GridCellIterator[][] createGridCellIterators(
+ GridCell[][] gridCells, Device device, GC gc) {
+ GridCellIterator[][] result = new GridCellIterator[gridCells.length][];
+ for (int rowIndex = 0; rowIndex < result.length; rowIndex++)
+ result[rowIndex] = createRowCellIterators(gridCells[rowIndex],
+ device, gc);
+ return result;
+ }
+
+ private static GridCellIterator[] createRowCellIterators(
+ GridCell[] rowCells, Device device, GC gc) {
+ GridCellIterator[] result = new GridCellIterator[rowCells.length];
+ for (int cellIndex = 0; cellIndex < rowCells.length; cellIndex++)
+ result[cellIndex] = ((GridCellImpl) rowCells[cellIndex]).iterator(
+ device, gc);
+ return result;
+ }
+
+ /** Copy constructor (used by copy() only) */
+ private GridIterator(GridIterator that) {
+ this.device = that.device;
+ this.dpi = that.dpi;
+
+ this.columns = that.columns;
+ this.columnGroups = that.columnGroups;
+
+ this.header = that.header; // never directly modified, clone not
+ // necessary
+ this.body = cloneRows(that.body, that.row); // Only need to deep copy
+ // the unconsumed rows.
+ this.footer = that.footer; // never directly modified, clone not
+ // necessary
+
+ this.cellClippingEnabled = that.cellClippingEnabled;
+
+ this.look = that.look;
+
+ this.minimumColSizes = that.minimumColSizes;
+ this.preferredColSizes = that.preferredColSizes;
+
+ this.minimumSize = that.minimumSize;
+ this.preferredSize = that.preferredSize;
+
+ this.row = that.row;
+ this.rowStarted = that.rowStarted;
+ }
+
+ private static GridCellIterator[][] cloneRows(GridCellIterator[][] rows,
+ int firstRow) {
+ GridCellIterator[][] result = (GridCellIterator[][]) rows.clone();
+ // Cloning the outer array is all that's necessary. The inner arrays
+ // (rows) are cloned every time a row
+ // is laid out, so all we have to do is make sure different
+ // GridIterators have distinct outer arrays.
+ // for ( int i = firstRow; i < result.length; i++ )
+ // result[i] = cloneRow( result[i] );
+ return result;
+ }
+
+ /**
+ * Compute the size of a column, respecting the constraints of the
+ * GridColumn.
+ */
+ private int computeCellWidth(GridCellIterator entry, GridColumn col,
+ PrintSizeStrategy strategy) {
+ if (col.size == SWT.DEFAULT)
+ return strategy.computeSize(entry.getTarget()).x;
+ if (col.size == GridPrint.PREFERRED)
+ return entry.getTarget().preferredSize().x;
+ return Math.round(col.size * device.getDPI().x / 72f);
+ }
+
+ private static boolean isExplicitSize(GridColumn col) {
+ return col.size > 0;
+ }
+
+ private void applyColumnGrouping(int[] columnSizes) {
+ for (int groupIndex = 0; groupIndex < columnGroups.length; groupIndex++) {
+ int[] group = columnGroups[groupIndex];
+
+ // find max column width in group
+ int maxSize = 0;
+ for (int columnInGroupIndex = 0; columnInGroupIndex < group.length; columnInGroupIndex++) {
+ int col = group[columnInGroupIndex];
+ maxSize = Math.max(maxSize, columnSizes[col]);
+ }
+
+ // grow all columns to max column width
+ for (int columnInGroupIndex = 0; columnInGroupIndex < group.length; columnInGroupIndex++) {
+ int col = group[columnInGroupIndex];
+ columnSizes[col] = maxSize;
+ }
+ }
+ }
+
+ private boolean isColumnGrouped(int col) {
+ for (int groupIndex = 0; groupIndex < columnGroups.length; groupIndex++) {
+ int[] group = columnGroups[groupIndex];
+ for (int columnInGroupIndex = 0; columnInGroupIndex < group.length; columnInGroupIndex++) {
+ int groupedColumn = group[columnInGroupIndex];
+ if (groupedColumn == col)
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ private int[] computeColumnSizes(PrintSizeStrategy strategy) {
+ final int[] result = new int[columns.length];
+ final GridCellIterator[][] rows = aggregateHeaderBodyAndFooterCells();
+
+ calculateExplicitlySizedColumnWidths(result);
+
+ calculateColumnWidthsForCellsSpanningOneColumn(result, rows, strategy);
+
+ applyColumnGrouping(result);
+
+ calculateColumnWidthsForCellsSpanningMultipleColumns(result, rows,
+ strategy);
+
+ applyColumnGrouping(result);
+
+ return result;
+ }
+
+ private GridCellIterator[][] aggregateHeaderBodyAndFooterCells() {
+ GridCellIterator[][] rows = new GridCellIterator[body.length
+ + header.length + footer.length][];
+
+ int offset = 0;
+
+ System.arraycopy(body, 0, rows, offset, body.length);
+ offset += body.length;
+
+ System.arraycopy(header, 0, rows, offset, header.length);
+ offset += header.length;
+
+ System.arraycopy(footer, 0, rows, offset, footer.length);
+
+ return rows;
+ }
+
+ private void calculateColumnWidthsForCellsSpanningMultipleColumns(
+ final int[] colSizes, final GridCellIterator[][] rows,
+ final PrintSizeStrategy strategy) {
+ int horizontalSpacing = look.getMargins().getHorizontalSpacing();
+
+ for (int rowIndex = 0; rowIndex < rows.length; rowIndex++) {
+ GridCellIterator[] row = rows[rowIndex];
+ int columnIndex = 0;
+ for (int cellIndex = 0; cellIndex < row.length; cellIndex++) {
+ GridCellIterator entry = row[cellIndex];
+ int colspan = entry.getColspan();
+ if (colspan > 1) {
+ int currentWidth = PaperClipsUtil.sum(colSizes,
+ columnIndex, colspan);
+
+ // Subtract column spacing so the weighted distribution of
+ // extra width stays proportional.
+ int minimumWidth = strategy.computeSize(entry.getTarget()).x
+ - horizontalSpacing * (colspan - 1);
+
+ if (currentWidth < minimumWidth) {
+ int extraWidth = minimumWidth - currentWidth;
+
+ int[] indices = getExpandableColumnIndices(columnIndex,
+ colspan);
+ int totalWidth = PaperClipsUtil.sumByIndex(colSizes,
+ indices);
+
+ if (totalWidth == 0)
+ resizeColumnsEqually(colSizes, extraWidth, indices);
+ else
+ resizeColumnsProportionateToCurrentSizes(colSizes,
+ indices, extraWidth, totalWidth);
+ }
+ }
+ columnIndex += colspan;
+ }
+ }
+ }
+
+ private void resizeColumnsProportionateToCurrentSizes(final int[] colSizes,
+ final int[] columnIndices, int adjustment, int totalWidth) {
+ for (int i = 0; i < columnIndices.length && totalWidth != 0
+ && adjustment != 0; i++) {
+ int columnIndex = columnIndices[i];
+
+ int addedWidth = (int) ((long) adjustment * colSizes[columnIndex] / totalWidth);
+
+ // Adjust extraWidth and totalCurrentWidth for future iterations.
+ totalWidth -= colSizes[columnIndex];
+ adjustment -= addedWidth;
+
+ // NOW we can add the added width.
+ colSizes[columnIndex] += addedWidth;
+ }
+ }
+
+ private void resizeColumnsEqually(final int[] colSizes, int adjustment,
+ int[] expandableColumns) {
+ int expandableCols = expandableColumns.length;
+ for (int expandableColIndex = 0; expandableColIndex < expandableCols; expandableColIndex++) {
+ int expandableColumn = expandableColumns[expandableColIndex];
+
+ int addedWidth = adjustment / expandableCols;
+
+ colSizes[expandableColumn] = addedWidth;
+ adjustment -= addedWidth;
+ expandableCols--;
+ }
+ }
+
+ private void calculateColumnWidthsForCellsSpanningOneColumn(int[] colSizes,
+ GridCellIterator[][] rows, PrintSizeStrategy strategy) {
+ for (int rowIndex = 0; rowIndex < rows.length; rowIndex++) {
+ GridCellIterator[] row = rows[rowIndex];
+ int col = 0;
+ for (int cellIndex = 0; cellIndex < row.length; cellIndex++) {
+ GridCellIterator entry = row[cellIndex];
+
+ // ignore explicitly sized cols
+ if (entry.getColspan() == 1 && !isExplicitSize(columns[col])) {
+ colSizes[col] = Math.max(colSizes[col],
+ computeCellWidth(entry, columns[col], strategy));
+ }
+ col += entry.getColspan();
+ }
+ }
+ }
+
+ private void calculateExplicitlySizedColumnWidths(int[] colSizes) {
+ for (int col = 0; col < columns.length; col++)
+ if (isExplicitSize(columns[col]))
+ colSizes[col] = Math.round(columns[col].size * dpi.x / 72f);
+ }
+
+ private int[] getExpandableColumnIndices(int firstColumn, int colspan) {
+ Condition[] conditions = getExpandableColumnConditions();
+ for (int i = 0; i < conditions.length; i++) {
+ int[] columns = findColumns(firstColumn, colspan, conditions[i]);
+ if (columns != null && columns.length > 0)
+ return columns;
+ }
+
+ return new int[0];
+ }
+
+ private interface Condition {
+ /**
+ * Returns whether the column at the specified index satisfies the
+ * condition.
+ *
+ * @param col
+ * the index of the column to test.
+ * @return whether the column at the specified index satisfies the
+ * condition.
+ */
+ boolean satisfiedBy(int col);
+ }
+
+ private Condition[] getExpandableColumnConditions() {
+ return new Condition[] { new Condition() {
+ public boolean satisfiedBy(int col) {
+ // Ungrouped columns with nonzero weight are first choice for
+ // expansion.
+ return !isColumnGrouped(col) && columns[col].weight > 0;
+ }
+ },
+
+ new Condition() {
+ public boolean satisfiedBy(int col) {
+ // Grouped columns with nonzero weight are next choice
+ return isColumnGrouped(col) && columns[col].weight > 0;
+ }
+ },
+
+ new Condition() {
+ public boolean satisfiedBy(int col) {
+ // Ungrouped columns with GridPrint.PREFERRED size are next
+ // choice.
+ return !isColumnGrouped(col)
+ && columns[col].size == GridPrint.PREFERRED;
+ }
+ },
+
+ new Condition() {
+ public boolean satisfiedBy(int col) {
+ // Grouped columns with GridPrint.PREFERRED size are next
+ // choice.
+ return isColumnGrouped(col)
+ && columns[col].size == GridPrint.PREFERRED;
+ }
+ },
+
+ new Condition() {
+ public boolean satisfiedBy(int col) {
+ // Ungrouped columns with SWT.DEFAULT size are next choice.
+ return !isColumnGrouped(col)
+ && columns[col].size == SWT.DEFAULT;
+ }
+ },
+
+ new Condition() {
+ public boolean satisfiedBy(int col) {
+ // Grouped columns with SWT.DEFAULT size are last choice.
+ return isColumnGrouped(col) && columns[col].size == SWT.DEFAULT;
+ }
+ } };
+ }
+
+ private int[] findColumns(Condition condition) {
+ return findColumns(0, columns.length, condition);
+ }
+
+ private int[] findColumns(int start, int count, Condition condition) {
+ int[] resultTemp = null;
+ int matches = 0;
+
+ final int end = start + count;
+ for (int index = start; index < end; index++)
+ if (condition.satisfiedBy(index)) {
+ if (resultTemp == null)
+ resultTemp = new int[count];
+ resultTemp[matches++] = index;
+ }
+
+ if (matches == 0)
+ return new int[0];
+
+ int[] result = new int[matches];
+ System.arraycopy(resultTemp, 0, result, 0, matches);
+ return result;
+ }
+
+ private Point computeSize(PrintSizeStrategy strategy, int[] colSizes) {
+ final GridMargins margins = look.getMargins();
+
+ int width = computeMarginWidth() + PaperClipsUtil.sum(colSizes);
+ int height = 0;
+
+ // This algorithm is not strictly accurate but probably good enough. The
+ // header and footer row heights
+ // are being calculated using getMinimumSize() and getPreferredSize(),
+ // which do not necessarily return
+ // the total content height.
+
+ if (header.length > 0)
+ height += computeHeaderHeight(margins, strategy);
+ else
+ height += Math.max(margins.getBodyTop(false, true),
+ margins.getBodyTop(false, false));
+
+ height += computeMaxBodyRowHeight(strategy);
+
+ if (footer.length > 0)
+ height += computeFooterHeight(strategy, margins);
+ else
+ height += Math.max(margins.getBodyBottom(false, false),
+ margins.getBodyBottom(false, true));
+
+ return new Point(width, height);
+ }
+
+ private int computeHeaderHeight(final GridMargins margins,
+ PrintSizeStrategy strategy) {
+ int headerHeight = margins.getHeaderTop()
+ + margins.getHeaderVerticalSpacing()
+ * (header.length - 1)
+ + Math.max(margins.getBodyTop(true, true),
+ margins.getBodyTop(true, false));
+ for (int rowIndex = 0; rowIndex < header.length; rowIndex++) {
+ GridCellIterator[] row = header[rowIndex];
+ int col = 0;
+ int rowHeight = 0;
+ for (int cellIndex = 0; cellIndex < row.length; cellIndex++) {
+ GridCellIterator entry = row[cellIndex];
+ // Find tallest cell in row.
+ rowHeight = Math.max(rowHeight,
+ strategy.computeSize(entry.getTarget()).y);
+ col += entry.getColspan();
+ }
+ headerHeight += rowHeight;
+ }
+ return headerHeight;
+ }
+
+ private int computeMaxBodyRowHeight(PrintSizeStrategy strategy) {
+ int maxBodyRowHeight = 0;
+ for (int rowIndex = 0; rowIndex < body.length; rowIndex++) {
+ GridCellIterator[] row = body[rowIndex];
+ int col = 0;
+ for (int cellIndex = 0; cellIndex < row.length; cellIndex++) {
+ GridCellIterator entry = row[cellIndex];
+ // Find the greatest height of all cells' calculated sizes.
+ maxBodyRowHeight = Math.max(maxBodyRowHeight,
+ strategy.computeSize(entry.getTarget()).y);
+ col += entry.getColspan();
+ }
+ }
+ return maxBodyRowHeight;
+ }
+
+ private int computeFooterHeight(PrintSizeStrategy strategy,
+ final GridMargins margins) {
+ int footerHeight = Math.max(margins.getBodyBottom(true, false),
+ margins.getBodyBottom(true, true))
+ + margins.getFooterVerticalSpacing()
+ * (footer.length - 1)
+ + margins.getFooterBottom();
+ for (int rowIndex = 0; rowIndex < footer.length; rowIndex++) {
+ GridCellIterator[] row = footer[rowIndex];
+ int col = 0;
+ int rowHeight = 0;
+ for (int cellIndex = 0; cellIndex < row.length; cellIndex++) {
+ GridCellIterator entry = row[cellIndex];
+ // Find tallest cell in row.
+ rowHeight = Math.max(rowHeight,
+ strategy.computeSize(entry.getTarget()).y);
+ col += entry.getColspan();
+ }
+ footerHeight += rowHeight;
+ }
+ return footerHeight;
+ }
+
+ public Point minimumSize() {
+ return new Point(minimumSize.x, minimumSize.y);
+ }
+
+ public Point preferredSize() {
+ return new Point(preferredSize.x, preferredSize.y);
+ }
+
+ private Condition[] getShrinkableColumnConditions() {
+ /*
+ * Disabled: new Condition() { public boolean satisfiedBy( int col ) {
+ * // Search first for columns with DEFAULT size. return
+ * columns[col].size == SWT.DEFAULT; } },
+ */
+ return new Condition[] { new Condition() {
+ public boolean satisfiedBy(int col) {
+ // Search next for columns with DEFAULT or PREFERRED size.
+ int size = columns[col].size;
+ return size == SWT.DEFAULT || size == GridPrint.PREFERRED;
+ }
+ } };
+ }
+
+ private int[] findShrinkableColumns(int extraWidth) {
+ Condition[] conditions = getShrinkableColumnConditions();
+ for (int i = 0; i < conditions.length; i++) {
+ int[] indices = findColumns(conditions[i]);
+ if (PaperClipsUtil.sumByIndex(minimumColSizes, indices) >= extraWidth)
+ return indices;
+ }
+
+ return findAllColumns();
+ }
+
+ private int[] findAllColumns() {
+ int[] result = new int[columns.length];
+ for (int i = 0; i < result.length; i++)
+ result[i] = i;
+ return result;
+ }
+
+ private int[] computeColumnWidths(int width) {
+ int minimumWidth = PaperClipsUtil.sum(minimumColSizes);
+ int preferredWidth = PaperClipsUtil.sum(preferredColSizes);
+
+ if (width < minimumWidth)
+ return reduceMinimumColumnWidths(minimumWidth - width);
+ else if (width == minimumWidth)
+ return minimumColSizes;
+ else if (width < preferredWidth)
+ return expandMinimumColumnWidths(width - minimumWidth);
+ else if (preferredWidth == width)
+ return preferredColSizes;
+ else
+ // ( preferredWidth < width )
+ return expandPreferredColumnWidthsByWeight(width - preferredWidth);
+ }
+
+ private int[] expandPreferredColumnWidthsByWeight(int extraWidth) {
+ int[] weightedCols = findColumns(new Condition() {
+ public boolean satisfiedBy(int col) {
+ return columns[col].weight > 0;
+ }
+ });
+ int totalWeight = 0;
+ for (int i = 0; i < weightedCols.length; i++)
+ totalWeight += columns[weightedCols[i]].weight;
+
+ int[] colSizes = PaperClipsUtil.copy(preferredColSizes);
+ for (int weightedColIndex = 0; weightedColIndex < weightedCols.length; weightedColIndex++) {
+ int columnIndex = weightedCols[weightedColIndex];
+
+ int columnWeight = columns[columnIndex].weight;
+ int addWidth = (int) ((long) extraWidth * columnWeight / totalWeight);
+
+ colSizes[columnIndex] += addWidth;
+
+ // adjust extraWidth and totalWeight - eliminates round-off error
+ extraWidth -= addWidth;
+ totalWeight -= columnWeight;
+ }
+
+ return colSizes;
+ }
+
+ private int[] expandMinimumColumnWidths(int expansion) {
+ int difference = PaperClipsUtil.sum(preferredColSizes)
+ - PaperClipsUtil.sum(minimumColSizes);
+ int[] colSizes = PaperClipsUtil.copy(minimumColSizes);
+ for (int i = 0; i < columns.length && difference != 0 && expansion != 0; i++) {
+ int columnDifference = preferredColSizes[i] - minimumColSizes[i];
+
+ int change = (int) ((long) expansion * columnDifference / difference);
+
+ colSizes[i] += change;
+
+ // adjust extraWidth and difference - eliminates round-off error
+ expansion -= change;
+ difference -= columnDifference;
+ }
+
+ return colSizes;
+ }
+
+ private int computeMarginWidth() {
+ GridMargins margins = look.getMargins();
+ return margins.getLeft() + margins.getRight()
+ + margins.getHorizontalSpacing() * (columns.length - 1);
+ }
+
+ private int[] reduceMinimumColumnWidths(int reduction) {
+ int[] colSizes = PaperClipsUtil.copy(minimumColSizes);
+
+ int[] shrinkableCols = findShrinkableColumns(reduction);
+ int shrinkableWidth = PaperClipsUtil.sumByIndex(colSizes,
+ shrinkableCols);
+
+ for (int i = 0; i < shrinkableCols.length && shrinkableWidth != 0
+ && reduction != 0; i++) {
+ int col = shrinkableCols[i];
+
+ int columnReduction = (int) ((long) colSizes[col] * reduction / shrinkableWidth);
+
+ shrinkableWidth -= colSizes[col];
+ colSizes[col] -= columnReduction;
+ reduction -= columnReduction;
+ }
+
+ return colSizes;
+ }
+
+ public boolean hasNext() {
+ return row < body.length;
+ }
+
+ private PrintPiece nextRow(final GridCellIterator[] cells,
+ final int[] columnWidths, final int height, final boolean bottomOpen) {
+ if (bottomOpen && rowContainsNonDefaultVertAlignment(cells))
+ return null;
+ if (height < 0)
+ return null;
+
+ final int[] cellWidths = calculateCellWidths(cells, columnWidths);
+
+ PrintPiece[] pieces = layoutCellsWithNonFillVertAlignment(cells,
+ height, bottomOpen, cellWidths);
+ if (pieces == null)
+ return null;
+
+ final int rowHeight = calculateRowHeight(pieces, cells);
+
+ pieces = layoutCellsWithFillVertAlignment(cells, rowHeight, cellWidths,
+ pieces);
+ if (pieces == null)
+ return null;
+
+ final int[] xOffsets = new int[cells.length];
+ final int[] yOffsets = new int[cells.length];
+ applyCellAlignment(cells, cellWidths, pieces, rowHeight, xOffsets,
+ yOffsets);
+
+ return createRowResult(pieces, xOffsets, yOffsets);
+ }
+
+ private static boolean rowContainsNonDefaultVertAlignment(
+ final GridCellIterator[] cells) {
+ for (int i = 0; i < cells.length; i++)
+ if (!isDefaultVerticalAlignment(cells[i].getVerticalAlignment()))
+ return true;
+ return false;
+ }
+
+ private static boolean isDefaultVerticalAlignment(int vAlignment) {
+ return vAlignment == SWT.DEFAULT || vAlignment == SWT.TOP;
+ }
+
+ private int[] calculateCellWidths(final GridCellIterator[] cells,
+ final int[] columnWidths) {
+ final int[] result = new int[cells.length];
+ final int horzSpacing = look.getMargins().getHorizontalSpacing();
+ int col = 0;
+ for (int cellIndex = 0; cellIndex < cells.length; cellIndex++) {
+ int colspan = cells[cellIndex].getColspan();
+ result[cellIndex] = (colspan - 1) * horzSpacing
+ + PaperClipsUtil.sum(columnWidths, col, colspan);
+ col += colspan;
+ }
+ return result;
+ }
+
+ private static PrintPiece[] layoutCellsWithNonFillVertAlignment(
+ final GridCellIterator[] cells, final int height,
+ final boolean bottomOpen, final int[] cellWidths) {
+ final PrintPiece[] pieces = new PrintPiece[cells.length];
+ for (int cellIndex = 0; cellIndex < cells.length; cellIndex++) {
+ final GridCellIterator cell = cells[cellIndex];
+ final PrintIterator iter = cell.getTarget();
+
+ final int cellWidth = cellWidths[cellIndex];
+
+ if (iter.hasNext() && cell.getVerticalAlignment() != SWT.FILL) {
+ PrintPiece piece = pieces[cellIndex] = PaperClips.next(iter,
+ cellWidth, height);
+ if ((piece == null) || (iter.hasNext() && !bottomOpen)) {
+ PaperClipsUtil.dispose(piece, pieces);
+ return null;
+ }
+ }
+ }
+ return pieces;
+ }
+
+ private static int calculateRowHeight(final PrintPiece[] cellPieces,
+ final GridCellIterator[] cells) {
+ int maxHeight = 0;
+ for (int cellIndex = 0; cellIndex < cells.length; cellIndex++) {
+ GridCellIterator cell = cells[cellIndex];
+ if (cell.getVerticalAlignment() == SWT.FILL)
+ maxHeight = Math.max(maxHeight,
+ cell.getTarget().minimumSize().y);
+ else if (cellPieces[cellIndex] != null)
+ maxHeight = Math.max(maxHeight,
+ cellPieces[cellIndex].getSize().y);
+ }
+ return maxHeight;
+ }
+
+ private static PrintPiece[] layoutCellsWithFillVertAlignment(
+ final GridCellIterator[] cells, final int height,
+ final int[] cellWidths, final PrintPiece[] cellPieces) {
+ for (int cellIndex = 0; cellIndex < cells.length; cellIndex++) {
+ GridCellIterator cell = cells[cellIndex];
+ PrintIterator iter = cell.getTarget();
+
+ if (cell.getVerticalAlignment() == SWT.FILL) {
+ PrintPiece piece = cellPieces[cellIndex] = PaperClips.next(
+ iter, cellWidths[cellIndex], height);
+ if (piece == null || iter.hasNext()) {
+ PaperClipsUtil.dispose(piece, cellPieces);
+ return null;
+ }
+ }
+ }
+ return cellPieces;
+ }
+
+ private void applyCellAlignment(final GridCellIterator[] cells,
+ final int[] cellWidths, final PrintPiece[] pieces,
+ final int rowHeight, final int[] xOffsets, final int[] yOffsets) {
+ final int horzSpacing = look.getMargins().getHorizontalSpacing();
+ int x = 0;
+ int col = 0;
+
+ for (int cellIndex = 0; cellIndex < cells.length; cellIndex++) {
+ xOffsets[cellIndex] = x;
+ yOffsets[cellIndex] = 0;
+
+ GridCellIterator cell = cells[cellIndex];
+ PrintPiece piece = pieces[cellIndex];
+ if (piece != null) {
+ Point size = piece.getSize();
+ int hAlignment = resolveHorzAlignment(
+ cell.getHorizontalAlignment(), columns[col].align);
+ xOffsets[cellIndex] += getHorzAlignmentOffset(hAlignment,
+ size.x, cellWidths[cellIndex]);
+ yOffsets[cellIndex] += getVertAlignmentOffset(
+ cell.getVerticalAlignment(), size.y, rowHeight);
+ }
+
+ x += cellWidths[cellIndex] + horzSpacing;
+ col += cell.getColspan();
+ }
+ }
+
+ private static int resolveHorzAlignment(int cellAlignment,
+ int columnAlignment) {
+ return cellAlignment == SWT.DEFAULT ? columnAlignment : cellAlignment;
+ }
+
+ private static int getHorzAlignmentOffset(int alignment, int pieceWidth,
+ int totalWidth) {
+ if (alignment == SWT.CENTER)
+ return (totalWidth - pieceWidth) / 2;
+ else if (alignment == SWT.RIGHT)
+ return totalWidth - pieceWidth;
+ return 0;
+ }
+
+ private static int getVertAlignmentOffset(final int alignment,
+ final int pieceHeight, final int cellHeight) {
+ int offset = 0;
+ if (alignment == SWT.CENTER) {
+ offset = (cellHeight - pieceHeight) / 2;
+ } else if (alignment == SWT.BOTTOM) {
+ offset = cellHeight - pieceHeight;
+ }
+ return offset;
+ }
+
+ private static PrintPiece createRowResult(final PrintPiece[] pieces,
+ final int[] xOffsets, final int[] yOffsets) {
+ List result = new ArrayList();
+ for (int cellIndex = 0; cellIndex < pieces.length; cellIndex++)
+ if (pieces[cellIndex] != null)
+ result.add(new CompositeEntry(pieces[cellIndex], new Point(
+ xOffsets[cellIndex], yOffsets[cellIndex])));
+ return new CompositePiece(result);
+ }
+
+ private static boolean hasNext(GridCellIterator[] cells) {
+ for (int i = 0; i < cells.length; i++)
+ if (cells[i].getTarget().hasNext())
+ return true;
+ return false;
+ }
+
+ public PrintPiece next(final int width, int height) {
+ if (!hasNext())
+ PaperClips.error(SWT.ERROR_UNSPECIFIED, "No more content"); //$NON-NLS-1$
+
+ GridMargins margins = look.getMargins();
+ int[] colSizes = computeColumnWidths(width - computeMarginWidth());
+
+ final boolean headerPresent = header.length > 0;
+ final int[] headerHeights = new int[header.length];
+ final int[][] headerColSpans = new int[header.length][];
+ PrintPiece headerPiece = null;
+ if (headerPresent) {
+ height -= margins.getHeaderTop();
+ headerPiece = nextHeaderPiece(colSizes, height, headerHeights,
+ headerColSpans);
+ if (headerPiece == null)
+ return null;
+ height -= headerPiece.getSize().y;
+ }
+
+ final boolean footerPresent = footer.length > 0;
+ final int[] footerHeights = new int[footer.length];
+ final int[][] footerColSpans = new int[footer.length][];
+ PrintPiece footerPiece = null;
+
+ if (footerPresent) {
+ height -= margins.getFooterBottom();
+ footerPiece = nextFooterPiece(colSizes, height, footerHeights,
+ footerColSpans);
+ if (footerPiece == null) {
+ PaperClipsUtil.dispose(headerPiece);
+ return null;
+ }
+ height -= footerPiece.getSize().y;
+ }
+
+ final int firstRow = row;
+ final boolean topOpen = rowStarted;
+ final List bodyRows = new ArrayList();
+ final List bodyColSpans = new ArrayList();
+
+ height -= margins.getBodyTop(headerPresent, topOpen);
+ final PrintPiece bodyPiece = nextBodyPiece(colSizes, height, bodyRows,
+ bodyColSpans, footerPresent);
+ if (bodyPiece == null)
+ return null;
+ final boolean bottomOpen = rowStarted;
+
+ return createResult(colSizes, headerPiece, headerHeights,
+ headerColSpans, firstRow, topOpen, bodyPiece,
+ PaperClipsUtil.toIntArray(bodyRows),
+ PaperClipsUtil.toIntIntArray(bodyColSpans), bottomOpen,
+ footerPiece, footerHeights, footerColSpans);
+ }
+
+ private PrintPiece nextHeaderPiece(final int[] colSizes, final int height,
+ final int[] rowHeights, final int[][] colSpans) {
+ return nextHeaderOrFooterPiece(colSizes, height, rowHeights, colSpans,
+ look.getMargins().getHeaderVerticalSpacing(), header);
+ }
+
+ private PrintPiece nextFooterPiece(final int[] colSizes, final int height,
+ final int[] rowHeights, final int[][] colSpans) {
+ return nextHeaderOrFooterPiece(colSizes, height, rowHeights, colSpans,
+ look.getMargins().getFooterVerticalSpacing(), footer);
+ }
+
+ private PrintPiece nextHeaderOrFooterPiece(final int[] colSizes,
+ final int height, final int[] rowHeights, final int[][] colSpans,
+ final int rowSpacing, GridCellIterator[][] headerOrFooter) {
+ int y = 0;
+ List entries = new ArrayList();
+ for (int rowIndex = 0; rowIndex < headerOrFooter.length; rowIndex++) {
+ GridCellIterator[] row = cloneRow(headerOrFooter[rowIndex]);
+
+ colSpans[rowIndex] = new int[row.length];
+ for (int cellIndex = 0; cellIndex < row.length; cellIndex++)
+ colSpans[rowIndex][cellIndex] = row[cellIndex].getColspan();
+
+ PrintPiece rowPiece = nextRow(row, colSizes, height - y, false);
+ boolean hasNext = hasNext(row);
+
+ if (rowPiece == null || hasNext) {
+ PaperClipsUtil.dispose(rowPiece);
+ for (Iterator iter = entries.iterator(); iter.hasNext();) {
+ CompositeEntry entry = (CompositeEntry) iter.next();
+ entry.dispose();
+ }
+ return null;
+ }
+
+ int rowHeight = rowHeights[rowIndex] = rowPiece.getSize().y;
+ entries.add(new CompositeEntry(rowPiece, new Point(0, y)));
+
+ y += rowHeight + rowSpacing;
+ }
+
+ return new CompositePiece(entries);
+ }
+
+ private PrintPiece nextBodyPiece(int[] colSizes, final int height,
+ final List rowHeights, final List colSpans,
+ final boolean footerPresent) {
+ final GridMargins margins = look.getMargins();
+ final int rowSpacing = margins.getBodyVerticalSpacing();
+ final int bodyBottomSpacingOpen = margins.getBodyBottom(footerPresent,
+ true);
+ final int bodyBottomSpacingClosed = margins.getBodyBottom(
+ footerPresent, false);
+
+ int y = 0;
+ List entries = new ArrayList();
+ while (hasNext()) {
+ GridCellIterator[] thisRow = cloneRow(body[row]);
+ PrintPiece rowPiece = nextRow(thisRow, colSizes, height - y
+ - bodyBottomSpacingClosed, rowStarted);
+ boolean hasNext = hasNext(thisRow);
+
+ if ((cellClippingEnabled || entries.isEmpty())
+ && (rowPiece == null || hasNext)) {
+ thisRow = cloneRow(body[row]);
+ rowPiece = nextRow(thisRow, colSizes, height - y
+ - bodyBottomSpacingOpen, true);
+ hasNext = true;
+ }
+
+ if (rowPiece == null)
+ break;
+
+ entries.add(new CompositeEntry(rowPiece, new Point(0, y)));
+ body[row] = thisRow;
+
+ final int[] rowColSpans = new int[thisRow.length];
+ for (int cellIndex = 0; cellIndex < rowColSpans.length; cellIndex++)
+ rowColSpans[cellIndex] = thisRow[cellIndex].getColspan();
+ colSpans.add(rowColSpans);
+
+ final int rowHeight = rowPiece.getSize().y;
+ rowHeights.add(new Integer(rowHeight));
+
+ rowStarted = hasNext;
+ if (hasNext)
+ break;
+
+ y += rowHeight + rowSpacing;
+ row++;
+ }
+
+ if (entries.isEmpty())
+ return null;
+
+ return new CompositePiece(entries);
+ }
+
+ private static GridCellIterator[] cloneRow(GridCellIterator[] row) {
+ GridCellIterator[] result = (GridCellIterator[]) row.clone();
+ for (int i = 0; i < result.length; i++)
+ result[i] = result[i].copy();
+ return result;
+ }
+
+ private PrintPiece createResult(final int[] colSizes,
+ final PrintPiece headerPiece, final int[] headerRows,
+ final int[][] headerColSpans, final int firstRow,
+ final boolean topOpen, final PrintPiece bodyPiece,
+ final int[] bodyRows, final int[][] bodyColSpans,
+ final boolean bottomOpen, final PrintPiece footerPiece,
+ final int[] footerRows, final int[][] footerColSpans) {
+ if (bodyPiece == null) {
+ if (headerPiece != null)
+ headerPiece.dispose();
+ if (footerPiece != null)
+ footerPiece.dispose();
+ return null;
+ }
+
+ List sections = new ArrayList();
+
+ PrintPiece lookPiece = new GridLookPainterPiece(look, colSizes,
+ headerRows, headerColSpans, firstRow, topOpen, bodyRows,
+ bodyColSpans, bottomOpen, footerRows, footerColSpans);
+ sections.add(new CompositeEntry(lookPiece, new Point(0, 0)));
+
+ GridMargins margins = look.getMargins();
+ final int x = margins.getLeft();
+
+ int y = 0;
+ if (headerPiece != null) {
+ y = margins.getHeaderTop();
+ sections.add(new CompositeEntry(headerPiece, new Point(x, y)));
+ y += headerPiece.getSize().y;
+ }
+
+ y += margins.getBodyTop(headerPiece != null, topOpen);
+ sections.add(new CompositeEntry(bodyPiece, new Point(x, y)));
+ y += bodyPiece.getSize().y
+ + margins.getBodyBottom(footerPiece != null, bottomOpen);
+
+ if (footerPiece != null)
+ sections.add(new CompositeEntry(footerPiece, new Point(x, y)));
+
+ return new CompositePiece(sections);
+ }
+
+ public PrintIterator copy() {
+ return new GridIterator(this);
+ }
+} \ No newline at end of file
diff --git a/plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/grid/internal/GridLookPainterPiece.java b/plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/grid/internal/GridLookPainterPiece.java
new file mode 100644
index 0000000000..9f0941faa1
--- /dev/null
+++ b/plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/grid/internal/GridLookPainterPiece.java
@@ -0,0 +1,122 @@
+/*
+ * Copyright (c) 2007 Matthew Hall 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:
+ * Matthew Hall - initial API and implementation
+ */
+package org.eclipse.sirius.table.ui.tools.internal.paperclips.grid.internal;
+
+import org.eclipse.sirius.table.ui.tools.internal.paperclips.PrintPiece;
+import org.eclipse.sirius.table.ui.tools.internal.paperclips.grid.GridLookPainter;
+import org.eclipse.sirius.table.ui.tools.internal.paperclips.grid.GridMargins;
+import org.eclipse.sirius.table.ui.tools.internal.paperclips.internal.util.PaperClipsUtil;
+import org.eclipse.sirius.table.ui.tools.internal.paperclips.internal.util.Util;
+import org.eclipse.swt.graphics.GC;
+import org.eclipse.swt.graphics.Point;
+
+class GridLookPainterPiece implements PrintPiece {
+ final GridLookPainter look;
+
+ final int[] columns;
+ final int[] headerRows;
+ final int[][] headerColSpans;
+ final int firstRowIndex;
+ final boolean topOpen;
+ final int[] bodyRows;
+ final int[][] bodyColSpans;
+ final boolean bottomOpen;
+ final int[] footerRows;
+ final int[][] footerColSpans;
+
+ final Point size;
+
+ GridLookPainterPiece(GridLookPainter look, int[] colSizes,
+ int[] headerRows, int[][] headerColSpans, int firstRowIndex,
+ boolean topOpen, int[] bodyRows, int[][] bodyColSpans,
+ boolean bottomOpen, int[] footerRows, int[][] footerColSpans) {
+ Util.notNull(look);
+
+ this.look = look;
+ this.columns = PaperClipsUtil.copy(colSizes);
+ this.headerRows = PaperClipsUtil.copy(headerRows);
+ this.headerColSpans = PaperClipsUtil.copy(headerColSpans);
+
+ this.firstRowIndex = firstRowIndex;
+ this.topOpen = topOpen;
+ this.bodyRows = PaperClipsUtil.copy(bodyRows);
+ this.bodyColSpans = PaperClipsUtil.copy(bodyColSpans);
+ this.bottomOpen = bottomOpen;
+
+ this.footerRows = PaperClipsUtil.copy(footerRows);
+ this.footerColSpans = PaperClipsUtil.copy(footerColSpans);
+
+ GridMargins margins = look.getMargins();
+
+ Point size = calculateSize(margins, colSizes, headerRows, topOpen,
+ bodyRows, bottomOpen, footerRows);
+ this.size = size;
+ }
+
+ private static Point calculateSize(GridMargins margins, int[] columns,
+ int[] headerRows, boolean topOpen, int[] bodyRows,
+ boolean bottomOpen, int[] footerRows) {
+ final boolean headerPresent = headerRows.length > 0;
+ final boolean footerPresent = footerRows.length > 0;
+
+ int width = calculateWidth(margins, columns);
+
+ int height = calculateBodyHeight(margins, topOpen, bodyRows,
+ bottomOpen, headerPresent, footerPresent);
+ if (headerPresent)
+ height += calculateHeaderHeight(margins, headerRows);
+ if (footerPresent)
+ height += calculateFooterHeight(margins, footerRows);
+
+ return new Point(width, height);
+ }
+
+ private static int calculateWidth(GridMargins margins, int[] columns) {
+ return margins.getLeft() + margins.getHorizontalSpacing()
+ * (columns.length - 1) + margins.getRight()
+ + PaperClipsUtil.sum(columns);
+ }
+
+ private static int calculateBodyHeight(GridMargins margins,
+ boolean topOpen, int[] bodyRows, boolean bottomOpen,
+ final boolean headerPresent, final boolean footerPresent) {
+ return margins.getBodyTop(headerPresent, topOpen)
+ + margins.getBodyVerticalSpacing() * (bodyRows.length - 1)
+ + margins.getBodyBottom(footerPresent, bottomOpen)
+ + PaperClipsUtil.sum(bodyRows);
+ }
+
+ private static int calculateHeaderHeight(GridMargins margins,
+ int[] headerRows) {
+ return margins.getHeaderTop() + margins.getHeaderVerticalSpacing()
+ * (headerRows.length - 1) + PaperClipsUtil.sum(headerRows);
+ }
+
+ private static int calculateFooterHeight(GridMargins margins,
+ int[] footerRows) {
+ return margins.getFooterVerticalSpacing() * (footerRows.length - 1)
+ + margins.getFooterBottom() + PaperClipsUtil.sum(footerRows);
+ }
+
+ public void dispose() {
+ look.dispose();
+ }
+
+ public Point getSize() {
+ return new Point(size.x, size.y);
+ }
+
+ public void paint(GC gc, int x, int y) {
+ look.paint(gc, x, y, columns, headerRows, headerColSpans,
+ firstRowIndex, topOpen, bodyRows, bodyColSpans, bottomOpen,
+ footerRows, footerColSpans);
+ }
+}
diff --git a/plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/internal/LayerEntryImpl.java b/plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/internal/LayerEntryImpl.java
new file mode 100644
index 0000000000..e7c0a24196
--- /dev/null
+++ b/plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/internal/LayerEntryImpl.java
@@ -0,0 +1,95 @@
+package org.eclipse.sirius.table.ui.tools.internal.paperclips.internal;
+
+import org.eclipse.sirius.table.ui.tools.internal.paperclips.LayerEntry;
+import org.eclipse.sirius.table.ui.tools.internal.paperclips.LayerEntryIterator;
+import org.eclipse.sirius.table.ui.tools.internal.paperclips.Print;
+import org.eclipse.sirius.table.ui.tools.internal.paperclips.internal.util.PaperClipsUtil;
+import org.eclipse.sirius.table.ui.tools.internal.paperclips.internal.util.Util;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.graphics.Device;
+import org.eclipse.swt.graphics.GC;
+
+/**
+ * Instances in this class represent an entry in a LayerPrint.
+ *
+ * @author Matthew Hall
+ */
+public class LayerEntryImpl implements LayerEntry {
+
+ private final Print target;
+ private final int align;
+
+ /**
+ * Create a new layer entry.
+ *
+ * @param target
+ * the target print of this entry.
+ * @param align
+ * the horizontal alignment applied to the target.
+ */
+ public LayerEntryImpl(Print target, int align) {
+ Util.notNull(target);
+ this.target = target;
+ this.align = checkAlign(align);
+ }
+
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + align;
+ result = prime * result + ((target == null) ? 0 : target.hashCode());
+ return result;
+ }
+
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (obj == null)
+ return false;
+ if (getClass() != obj.getClass())
+ return false;
+ LayerEntry other = (LayerEntry) obj;
+ if (align != other.getHorizontalAlignment())
+ return false;
+ if (target == null) {
+ if (other.getTarget() != null)
+ return false;
+ } else if (!target.equals(other.getTarget()))
+ return false;
+ return true;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.eclipse.sirius.ext.paperclips.core.internal.LayerEntry#getTarget()
+ */
+ public Print getTarget() {
+ return target;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.eclipse.sirius.ext.paperclips.core.internal.LayerEntry#getHorizontalAlignment
+ * ()
+ */
+ public int getHorizontalAlignment() {
+ return align;
+ }
+
+ private static int checkAlign(int align) {
+ return PaperClipsUtil.firstMatch(align, new int[] { SWT.LEFT,
+ SWT.CENTER, SWT.RIGHT }, SWT.LEFT);
+ }
+
+ /**
+ * @param device
+ * @param gc
+ * @return
+ */
+ public LayerEntryIterator iterator(Device device, GC gc) {
+ return new LayerEntryIteratorImpl(this, device, gc);
+ }
+} \ No newline at end of file
diff --git a/plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/internal/LayerEntryIteratorImpl.java b/plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/internal/LayerEntryIteratorImpl.java
new file mode 100644
index 0000000000..557b48bd06
--- /dev/null
+++ b/plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/internal/LayerEntryIteratorImpl.java
@@ -0,0 +1,48 @@
+package org.eclipse.sirius.table.ui.tools.internal.paperclips.internal;
+
+import org.eclipse.sirius.table.ui.tools.internal.paperclips.LayerEntry;
+import org.eclipse.sirius.table.ui.tools.internal.paperclips.LayerEntryIterator;
+import org.eclipse.sirius.table.ui.tools.internal.paperclips.PrintIterator;
+import org.eclipse.swt.graphics.Device;
+import org.eclipse.swt.graphics.GC;
+
+public class LayerEntryIteratorImpl implements LayerEntryIterator {
+ final PrintIterator target;
+ final int alignment;
+
+ public LayerEntryIteratorImpl(LayerEntry entry, Device device, GC gc) {
+ this.target = entry.getTarget().iterator(device, gc);
+ this.alignment = entry.getHorizontalAlignment();
+ }
+
+ public LayerEntryIteratorImpl(LayerEntryIterator that) {
+ this.target = that.getTarget().copy();
+ this.alignment = that.getAlignment();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.eclipse.sirius.ext.paperclips.core.internal.LayerEntryIterator#getTarget
+ * ()
+ */
+ public PrintIterator getTarget() {
+ return target;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.eclipse.sirius.ext.paperclips.core.internal.LayerEntryIterator#getAlignment
+ * ()
+ */
+ public int getAlignment() {
+ return alignment;
+ }
+
+ public LayerEntryIterator copy() {
+ return new LayerEntryIteratorImpl(this);
+ }
+} \ No newline at end of file
diff --git a/plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/internal/LayerIterator.java b/plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/internal/LayerIterator.java
new file mode 100644
index 0000000000..0cc320aa13
--- /dev/null
+++ b/plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/internal/LayerIterator.java
@@ -0,0 +1,125 @@
+package org.eclipse.sirius.table.ui.tools.internal.paperclips.internal;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+import org.eclipse.sirius.table.ui.tools.internal.paperclips.CompositeEntry;
+import org.eclipse.sirius.table.ui.tools.internal.paperclips.CompositePiece;
+import org.eclipse.sirius.table.ui.tools.internal.paperclips.LayerEntry;
+import org.eclipse.sirius.table.ui.tools.internal.paperclips.LayerEntryIterator;
+import org.eclipse.sirius.table.ui.tools.internal.paperclips.LayerPrint;
+import org.eclipse.sirius.table.ui.tools.internal.paperclips.PaperClips;
+import org.eclipse.sirius.table.ui.tools.internal.paperclips.PrintIterator;
+import org.eclipse.sirius.table.ui.tools.internal.paperclips.PrintPiece;
+import org.eclipse.sirius.table.ui.tools.internal.paperclips.internal.util.PrintSizeStrategy;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.graphics.Device;
+import org.eclipse.swt.graphics.GC;
+import org.eclipse.swt.graphics.Point;
+
+public class LayerIterator implements PrintIterator {
+ LayerEntryIterator[] entries;
+
+ public LayerIterator(LayerPrint print, Device device, GC gc) {
+ entries = new LayerEntryIteratorImpl[print.getEntries().length];
+ LayerEntry[] e = print.getEntries();
+ for (int i = 0; i < entries.length; i++) {
+ entries[i] = e[i].iterator(device, gc);
+ }
+ }
+
+ public LayerIterator(LayerIterator that) {
+ this.entries = (LayerEntryIterator[]) that.entries.clone();
+ for (int i = 0; i < entries.length; i++)
+ if (entries[i].getTarget().hasNext())
+ entries[i] = entries[i].copy();
+ }
+
+ public boolean hasNext() {
+ for (int i = 0; i < entries.length; i++)
+ if (entries[i].getTarget().hasNext())
+ return true;
+ return false;
+ }
+
+ public PrintPiece next(int width, int height) {
+ if (!hasNext())
+ PaperClips.error("No more content"); //$NON-NLS-1$
+
+ PrintPiece[] pieces = nextPieces(width, height);
+ if (pieces == null)
+ return null;
+
+ CompositeEntry[] entries = new CompositeEntry[pieces.length];
+ for (int i = 0; i < entries.length; i++) {
+ PrintPiece piece = pieces[i];
+ int offset = getHorzAlignmentOffset(this.entries[i].getAlignment(),
+ piece.getSize().x, width);
+ entries[i] = new CompositeEntry(piece, new Point(offset, 0));
+ }
+ return new CompositePiece(entries);
+ }
+
+ private PrintPiece[] nextPieces(int width, int height) {
+ LayerEntryIteratorImpl[] entries = (LayerEntryIteratorImpl[]) this.entries
+ .clone();
+
+ List pieces = new ArrayList();
+ for (int i = 0; i < entries.length; i++) {
+ LayerEntryIteratorImpl entry = entries[i];
+ if (entry.target.hasNext()) {
+ PrintPiece piece = PaperClips.next(entry.target, width, height);
+
+ if (piece == null) {
+ for (Iterator iter = pieces.iterator(); iter.hasNext();)
+ ((PrintPiece) iter.next()).dispose();
+ return null;
+ }
+ pieces.add(piece);
+ }
+ }
+
+ // Replace instance entries with the entries that were just consumed.
+ this.entries = entries;
+
+ return (PrintPiece[]) pieces.toArray(new PrintPiece[pieces.size()]);
+ }
+
+ private int getHorzAlignmentOffset(int alignment, int pieceWidth,
+ int totalWidth) {
+ int offset = 0;
+ switch (alignment) {
+ case SWT.CENTER:
+ offset = (totalWidth - pieceWidth) / 2;
+ break;
+ case SWT.RIGHT:
+ offset = totalWidth - pieceWidth;
+ break;
+ }
+ return offset;
+ }
+
+ Point computeSize(PrintSizeStrategy strategy) {
+ Point size = new Point(0, 0);
+ for (int i = 0; i < entries.length; i++) {
+ LayerEntryIterator entry = entries[i];
+ Point entrySize = strategy.computeSize(entry.getTarget());
+ size.x = Math.max(size.x, entrySize.x);
+ size.y = Math.max(size.y, entrySize.y);
+ }
+ return size;
+ }
+
+ public Point minimumSize() {
+ return computeSize(PrintSizeStrategy.MINIMUM);
+ }
+
+ public Point preferredSize() {
+ return computeSize(PrintSizeStrategy.PREFERRED);
+ }
+
+ public PrintIterator copy() {
+ return new LayerIterator(this);
+ }
+} \ No newline at end of file
diff --git a/plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/internal/RotatePiece.java b/plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/internal/RotatePiece.java
new file mode 100644
index 0000000000..851ab5545e
--- /dev/null
+++ b/plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/internal/RotatePiece.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright (c) 2007 Matthew Hall 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:
+ * Matthew Hall - initial API and implementation
+ */
+
+package org.eclipse.sirius.table.ui.tools.internal.paperclips.internal;
+
+import org.eclipse.sirius.table.ui.tools.internal.paperclips.PaperClips;
+import org.eclipse.sirius.table.ui.tools.internal.paperclips.PrintPiece;
+import org.eclipse.sirius.table.ui.tools.internal.paperclips.internal.util.Util;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.graphics.Device;
+import org.eclipse.swt.graphics.GC;
+import org.eclipse.swt.graphics.Point;
+import org.eclipse.swt.graphics.Transform;
+
+public final class RotatePiece implements PrintPiece {
+ private final Device device;
+ private final PrintPiece target;
+ private final int angle;
+ private final Point size;
+
+ private Transform oldTransform;
+ private Transform transform;
+
+ public RotatePiece(Device device, PrintPiece target, int angle, Point size) {
+ Util.notNull(device, target, size);
+ this.device = device;
+ this.target = target;
+ this.angle = angle;
+ this.size = size;
+ }
+
+ public Point getSize() {
+ return new Point(size.x, size.y);
+ }
+
+ private Transform getOldTransform() {
+ if (oldTransform == null)
+ oldTransform = new Transform(device);
+ return oldTransform;
+ }
+
+ private Transform getTransform() {
+ if (transform == null)
+ transform = new Transform(device);
+ return transform;
+ }
+
+ public void paint(GC gc, int x, int y) {
+ Transform oldTransform = getOldTransform();
+ gc.getTransform(oldTransform);
+
+ Transform transform = getTransform();
+ gc.getTransform(transform);
+ transform.translate(x, y);
+ rotateTransform(transform);
+ gc.setTransform(transform);
+
+ target.paint(gc, 0, 0);
+
+ gc.setTransform(oldTransform);
+ }
+
+ private void rotateTransform(Transform transform) {
+ switch (angle) {
+ case 90:
+ transform.translate(0, size.y);
+ break;
+ case 180:
+ transform.translate(size.x, size.y);
+ break;
+ case 270:
+ transform.translate(size.x, 0);
+ break;
+ default:
+ PaperClips.error(SWT.ERROR_INVALID_ARGUMENT,
+ "Rotation angle must be 90, 180 or 270."); //$NON-NLS-1$
+ }
+ transform.rotate(-angle); // reverse the angle since Transform.rotate
+ // goes clockwise
+ }
+
+ public void dispose() {
+ if (oldTransform != null) {
+ oldTransform.dispose();
+ oldTransform = null;
+ }
+ if (transform != null) {
+ transform.dispose();
+ transform = null;
+ }
+ target.dispose();
+ }
+} \ No newline at end of file
diff --git a/plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/internal/piece/EmptyPiece.java b/plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/internal/piece/EmptyPiece.java
new file mode 100644
index 0000000000..a02dce4db8
--- /dev/null
+++ b/plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/internal/piece/EmptyPiece.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (c) 2006 Matthew Hall 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:
+ * Matthew Hall - initial API and implementation
+ */
+package org.eclipse.sirius.table.ui.tools.internal.paperclips.internal.piece;
+
+import org.eclipse.sirius.table.ui.tools.internal.paperclips.PrintPiece;
+import org.eclipse.sirius.table.ui.tools.internal.paperclips.internal.util.Util;
+import org.eclipse.swt.graphics.GC;
+import org.eclipse.swt.graphics.Point;
+
+/**
+ * A blank PrintPiece of a predetermined size
+ *
+ * @author matt
+ */
+public class EmptyPiece implements PrintPiece {
+ private final Point size;
+
+ /**
+ * @param size
+ */
+ public EmptyPiece(Point size) {
+ Util.notNull(size);
+ this.size = size;
+ }
+
+ public Point getSize() {
+ return new Point(size.x, size.y);
+ }
+
+ public void paint(GC gc, int x, int y) {
+ // Nothing to paint
+ }
+
+ public void dispose() {
+ // Nothing to dispose
+ }
+} \ No newline at end of file
diff --git a/plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/internal/util/PaperClipsUtil.java b/plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/internal/util/PaperClipsUtil.java
new file mode 100644
index 0000000000..30063aff9a
--- /dev/null
+++ b/plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/internal/util/PaperClipsUtil.java
@@ -0,0 +1,231 @@
+/*
+ * Copyright (c) 2007-2008 Matthew Hall 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:
+ * Matthew Hall - initial API and implementation
+ */
+
+package org.eclipse.sirius.table.ui.tools.internal.paperclips.internal.util;
+
+import java.util.Iterator;
+import java.util.List;
+
+import org.eclipse.sirius.table.ui.tools.internal.paperclips.PrintPiece;
+
+/**
+ * Convenience methods specific to PaperClips
+ *
+ * @author Matthew Hall
+ */
+public class PaperClipsUtil {
+ private PaperClipsUtil() {
+ } // no instances
+
+ /**
+ * Disposes the print piece if not null.
+ *
+ * @param piece
+ * the print piece to dispose.
+ */
+ public static void dispose(final PrintPiece piece) {
+ if (piece != null)
+ piece.dispose();
+ }
+
+ /**
+ * Disposes the arguments that are not null.
+ *
+ * @param p1
+ * print piece to dispose
+ * @param p2
+ * print piece to dispose
+ */
+ public static void dispose(PrintPiece p1, PrintPiece p2) {
+ dispose(p1);
+ dispose(p2);
+ }
+
+ /**
+ * Disposes the print pieces that are not null.
+ *
+ * @param pieces
+ * array of print pieces to dispose.
+ */
+ public static void dispose(final PrintPiece[] pieces) {
+ if (pieces != null)
+ for (int i = 0; i < pieces.length; i++)
+ dispose(pieces[i]);
+ }
+
+ /**
+ * Disposes the print pieces in the array from start (inclusive) to end
+ * (exclusive).
+ *
+ * @param pages
+ * array of print pieces to dispose.
+ * @param start
+ * the start index.
+ * @param end
+ * the end index.
+ */
+ public static void dispose(PrintPiece[] pages, int start, int end) {
+ for (int i = start; i < end; i++)
+ pages[i].dispose();
+ }
+
+ /**
+ * Disposes the print pieces in the list.
+ *
+ * @param pages
+ * list of print pieces to dispose.
+ */
+ public static void dispose(List pages) {
+ for (Iterator it = pages.iterator(); it.hasNext();)
+ ((PrintPiece) it.next()).dispose();
+ pages.clear();
+ }
+
+ /**
+ * Disposes the print pieces that are not null.
+ *
+ * @param piece
+ * a print piece to dispose
+ * @param pieces
+ * array of print pieces to dispose
+ */
+ public static void dispose(PrintPiece piece, final PrintPiece[] pieces) {
+ dispose(piece);
+ dispose(pieces);
+ }
+
+ /**
+ * Returns a copy of the array.
+ *
+ * @param array
+ * the array to copy
+ * @return a copy of the array.
+ */
+ public static int[] copy(int[] array) {
+ Util.notNull(array);
+ return (int[]) array.clone();
+ }
+
+ /**
+ * Returns a deep copy of the array.
+ *
+ * @param array
+ * the array to copy
+ * @return a copy of the array.
+ */
+ public static int[][] copy(int[][] array) {
+ Util.notNull(array);
+ int[][] result = (int[][]) array.clone();
+ for (int i = 0; i < result.length; i++)
+ result[i] = copy(result[i]);
+ return result;
+ }
+
+ /**
+ * Returns the sum of all elements in the array.
+ *
+ * @param array
+ * the array
+ * @return the sum of all elements in the array.
+ */
+ public static int sum(int[] array) {
+ return PaperClipsUtil.sum(array, 0, array.length);
+ }
+
+ /**
+ * Returns the sum of all elements in the array in the range
+ * <code>[start, start+count)</code>.
+ *
+ * @param array
+ * the array containing the elements to add up.
+ * @param start
+ * the index of the first element to add.
+ * @param count
+ * the number of elements to add.
+ * @return the sum of all elements in the array in the specified range.
+ */
+ public static int sum(final int[] array, final int start, final int count) {
+ Util.notNull(array);
+ int result = 0;
+ final int end = start + count;
+ for (int i = start; i < end; i++)
+ result += array[i];
+ return result;
+ }
+
+ /**
+ * Returns the sum of all elements in the array at the given indices.
+ *
+ * @param array
+ * the array of elements to add up.
+ * @param indices
+ * the indices of the elements in the array to add up.
+ * @return the sum of all elements in the array at the given indices.
+ */
+ public static int sumByIndex(final int[] array, final int[] indices) {
+ Util.notNull(array);
+ int result = 0;
+ for (int i = 0; i < indices.length; i++)
+ result += array[indices[i]];
+ return result;
+ }
+
+ /**
+ * Converts the argument to an int[] array.
+ *
+ * @param list
+ * a List of Integers.
+ * @return a primitive int[] array.
+ */
+ public static int[] toIntArray(List list) {
+ final int[] array = new int[list.size()];
+ for (int i = 0; i < array.length; i++)
+ array[i] = ((Integer) list.get(i)).intValue();
+ return array;
+ }
+
+ /**
+ * Converts the argument to an int[][] array.
+ *
+ * @param list
+ * a List of int[] arrays.
+ * @return a primitive int[][] array.
+ */
+ public static int[][] toIntIntArray(List list) {
+ final int[][] array = new int[list.size()][];
+ for (int i = 0; i < array.length; i++)
+ array[i] = (int[]) list.get(i);
+ return array;
+ }
+
+ /**
+ * Returns the first element in masks where (value & mask[index]) ==
+ * mask[index].
+ *
+ * @param value
+ * the value to match
+ * @param masks
+ * the possible values.
+ * @param defaultMask
+ * the value to return if no match is found.
+ * @return the first value in possibleValues which is a bitwise match to
+ * value, or 0 if none is found.
+ */
+ public static int firstMatch(int value, int[] masks, int defaultMask) {
+ Util.notNull(masks);
+ for (int i = 0; i < masks.length; i++) {
+ int mask = masks[i];
+ if ((value & mask) == mask)
+ return mask;
+ }
+ return defaultMask;
+ }
+}
diff --git a/plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/internal/util/PrintSizeStrategy.java b/plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/internal/util/PrintSizeStrategy.java
new file mode 100644
index 0000000000..350062a75d
--- /dev/null
+++ b/plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/internal/util/PrintSizeStrategy.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) 2005 Matthew Hall 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:
+ * Matthew Hall - initial API and implementation
+ */
+package org.eclipse.sirius.table.ui.tools.internal.paperclips.internal.util;
+
+import org.eclipse.sirius.table.ui.tools.internal.paperclips.PrintIterator;
+import org.eclipse.swt.graphics.Point;
+
+/**
+ * The static instance members of this class aid in the calculation of prints
+ * and help abstract out the minimum/preferred size concepts to simplify
+ * algorithms.
+ *
+ * @author Matthew Hall
+ */
+public abstract class PrintSizeStrategy {
+ /** Compute the minimum size */
+ public static final PrintSizeStrategy MINIMUM = new PrintSizeStrategy() {
+ public Point computeSize(PrintIterator iter) {
+ return iter.minimumSize();
+ }
+ };
+
+ /** Compute the preferred size. */
+ public static final PrintSizeStrategy PREFERRED = new PrintSizeStrategy() {
+ public Point computeSize(PrintIterator iter) {
+ return iter.preferredSize();
+ }
+ };
+
+ private PrintSizeStrategy() {
+ }
+
+ /**
+ * Computes the size of the PrintIterator.
+ *
+ * @param print
+ * the iterator
+ * @return the computed size of the PrintIterator.
+ */
+ public abstract Point computeSize(PrintIterator print);
+} \ No newline at end of file
diff --git a/plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/internal/util/ResourcePool.java b/plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/internal/util/ResourcePool.java
new file mode 100644
index 0000000000..7573b63749
--- /dev/null
+++ b/plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/internal/util/ResourcePool.java
@@ -0,0 +1,109 @@
+/*
+ * Copyright (c) 2007-2008 Matthew Hall 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:
+ * Matthew Hall - initial API and implementation
+ */
+
+package org.eclipse.sirius.table.ui.tools.internal.paperclips.internal.util;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.WeakHashMap;
+
+import org.eclipse.sirius.table.ui.tools.internal.paperclips.PaperClips;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.graphics.Color;
+import org.eclipse.swt.graphics.Device;
+import org.eclipse.swt.graphics.Font;
+import org.eclipse.swt.graphics.FontData;
+import org.eclipse.swt.graphics.RGB;
+
+/**
+ * Manages a pool of graphics resources for a graphics device (fonts, colors).
+ *
+ * @author Matthew Hall
+ */
+public class ResourcePool {
+ private static Map devices = new WeakHashMap(); // Map <Device,
+
+ // ResourcePool>
+
+ /**
+ * Returns a SharedGraphics which creates resources on the given device.
+ *
+ * @param device
+ * the device which resources will be created on.
+ * @return a SharedGraphics which creates resources on the given device.
+ */
+ public synchronized static ResourcePool forDevice(Device device) {
+ Util.notNull(device);
+ notDisposed(device);
+
+ ResourcePool sharedGraphics = (ResourcePool) devices.get(device);
+ if (sharedGraphics == null) {
+ sharedGraphics = new ResourcePool(device);
+ devices.put(device, sharedGraphics);
+ }
+ return sharedGraphics;
+ }
+
+ private static void notDisposed(Device device) {
+ if (device.isDisposed())
+ PaperClips.error(SWT.ERROR_DEVICE_DISPOSED);
+ }
+
+ private final Device device;
+ private final Map fonts; // Map <FontData, Font>
+ private final Map colors; // Map <RGB, Color>
+
+ private ResourcePool(Device device) {
+ this.device = device;
+ this.fonts = new HashMap();
+ this.colors = new HashMap();
+ }
+
+ /**
+ * Returns a font for the passed in FontData.
+ *
+ * @param fontData
+ * FontData describing the required font.
+ * @return a font for the passed in FontData.
+ */
+ public Font getFont(FontData fontData) {
+ if (fontData == null)
+ return null;
+ notDisposed(device);
+
+ Font font = (Font) fonts.get(fontData);
+ if (font == null) {
+ font = new Font(device, fontData);
+ fonts.put(SWTUtil.copy(fontData), font);
+ }
+ return font;
+ }
+
+ /**
+ * Returns a color for the passed in RGB.
+ *
+ * @param rgb
+ * RGB describing the required color.
+ * @return a color for the passed in RGB.
+ */
+ public Color getColor(RGB rgb) {
+ if (rgb == null)
+ return null;
+ notDisposed(device);
+
+ Color color = (Color) colors.get(rgb);
+ if (color == null) {
+ color = new Color(device, rgb);
+ colors.put(SWTUtil.copy(rgb), color);
+ }
+ return color;
+ }
+} \ No newline at end of file
diff --git a/plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/internal/util/SWTUtil.java b/plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/internal/util/SWTUtil.java
new file mode 100644
index 0000000000..111f9cae82
--- /dev/null
+++ b/plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/internal/util/SWTUtil.java
@@ -0,0 +1,187 @@
+/*
+ * Copyright (c) 2008 Matthew Hall 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:
+ * Matthew Hall - initial API and implementation
+ */
+package org.eclipse.sirius.table.ui.tools.internal.paperclips.internal.util;
+
+import org.eclipse.swt.graphics.FontData;
+import org.eclipse.swt.graphics.ImageData;
+import org.eclipse.swt.graphics.PaletteData;
+import org.eclipse.swt.graphics.RGB;
+
+/**
+ * Utility methods for dealing with SWT objects
+ *
+ * @author Matthew Hall
+ */
+public class SWTUtil {
+
+ /**
+ * Returns a defensive copy of the passed in FontData.
+ *
+ * @param fontData
+ * the FontData to copy. May be null.
+ * @return a copy of the passed in FontData, or null if the argument was
+ * null.
+ */
+ public static FontData copy(FontData fontData) {
+ return fontData == null ? null : new FontData(fontData.getName(),
+ fontData.getHeight(), fontData.getStyle());
+ }
+
+ /**
+ * Returns a defensive copy of the passed in RGB.
+ *
+ * @param rgb
+ * the RGB to copy. May be null.
+ * @return a copy of the passed in RGB, or null if the argument was null.
+ */
+ public static RGB copy(RGB rgb) {
+ return rgb == null ? null : new RGB(rgb.red, rgb.green, rgb.blue);
+ }
+
+ /**
+ * Returns an RGB representing the color described by the argument.
+ * <p>
+ * Sample colors:<br>
+ * 0xFFFFFF: white<br>
+ * 0x000000: black<br>
+ * 0xFF0000: red<br>
+ * 0x00FF00: green<br>
+ * 0x0000FF: blue
+ *
+ * @param rgb
+ * an integer containing the red, green and blue components in
+ * the 0xFF0000, 0x00FF00, and 0x0000FF positions, respectively.
+ * @return an RGB representing the color described by the argument.
+ */
+ public static RGB deriveRGB(final int rgb) {
+ int red = (rgb >> 16) & 0xFF;
+ int green = (rgb >> 8) & 0xFF;
+ int blue = rgb & 0xFF;
+ return new RGB(red, green, blue);
+ }
+
+ /**
+ * Returns whether the PaletteData arguments are equivalent.
+ *
+ * @param left
+ * the left PaletteData
+ * @param right
+ * the right PaletteData
+ * @return whether the PaletteData arguments are equivalent.
+ */
+ public static boolean equal(PaletteData left, PaletteData right) {
+ if (left == right)
+ return true;
+ if (left == null || right == null)
+ return false;
+ return left.isDirect == right.isDirect
+ && left.blueMask == right.blueMask
+ && left.blueShift == right.blueShift
+ && left.greenMask == right.greenMask
+ && left.greenShift == right.greenShift
+ && left.redMask == right.redMask
+ && left.redShift == right.redShift
+ && Util.equal(left.colors, right.colors);
+ }
+
+ /**
+ * Returns a hash code for the PaletteData.
+ *
+ * @param data
+ * the PaletteData
+ * @return a hash code for the PaletteData.
+ */
+ public static int hashCode(PaletteData data) {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + (data.isDirect ? 1231 : 1237);
+ result = prime * result + data.blueMask;
+ result = prime * result + data.blueShift;
+ result = prime * result + data.greenMask;
+ result = prime * result + data.greenShift;
+ result = prime * result + data.redMask;
+ result = prime * result + data.redShift;
+ result = prime * result + hashCode(data.colors);
+ return result;
+ }
+
+ private static int hashCode(Object[] array) {
+ int prime = 31;
+ if (array == null)
+ return 0;
+ int result = 1;
+ for (int index = 0; index < array.length; index++) {
+ result = prime * result
+ + (array[index] == null ? 0 : array[index].hashCode());
+ }
+ return result;
+ }
+
+ /**
+ * Returns whether the ImageData arguments are equivalent.
+ *
+ * @param left
+ * the left ImageData
+ * @param right
+ * the right ImageData
+ * @return whether the ImageData arguments are equivalent.
+ */
+ public static boolean equal(ImageData left, ImageData right) {
+ if (left == right)
+ return true;
+ if (left == null || right == null)
+ return false;
+ if (left.width != right.width || left.height != right.height)
+ return false;
+ if (!equal(left.palette, right.palette))
+ return false;
+
+ final int width = left.width;
+ int[] leftPixels = new int[width];
+ int[] rightPixels = new int[width];
+ byte[] leftAlphas = new byte[width];
+ byte[] rightAlphas = new byte[width];
+ for (int y = 0; y < left.height; y++) {
+ left.getAlphas(0, y, width, leftAlphas, 0);
+ right.getAlphas(0, y, width, rightAlphas, 0);
+ if (!Util.equal(leftAlphas, rightAlphas))
+ return false;
+
+ left.getPixels(0, y, width, leftPixels, 0);
+ right.getPixels(0, y, width, rightPixels, 0);
+ if (!Util.equal(leftPixels, rightPixels)) {
+ for (int x = 0; x < width; x++) {
+ if (leftAlphas[x] != 0 && leftPixels[x] != rightPixels[x])
+ return false;
+ }
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Returns a hash code for the ImageData
+ *
+ * @param data
+ * the ImageData
+ * @return a hash code for the ImageData
+ */
+ public static int hashCode(ImageData data) {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + data.width;
+ result = prime * result + data.height;
+ result = prime * result + hashCode(data.palette);
+ // Neglect pixel data
+ return result;
+ }
+}
diff --git a/plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/internal/util/Util.java b/plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/internal/util/Util.java
new file mode 100644
index 0000000000..17aed88d0c
--- /dev/null
+++ b/plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/internal/util/Util.java
@@ -0,0 +1,170 @@
+/*
+ * Copyright (c) 2007-2008 Matthew Hall 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:
+ * Matthew Hall - initial API and implementation
+ */
+package org.eclipse.sirius.table.ui.tools.internal.paperclips.internal.util;
+
+import java.util.Arrays;
+import java.util.List;
+
+import org.eclipse.sirius.table.ui.tools.internal.paperclips.PaperClips;
+import org.eclipse.swt.SWT;
+
+/**
+ * General use convenience methods: null checking, equality
+ *
+ * @author Matthew Hall
+ */
+public class Util {
+ /**
+ * Returns whether the objects are of the same class.
+ *
+ * @param left
+ * object to test
+ * @param right
+ * object to test
+ * @return whether the objects are of the same class.
+ */
+ public static boolean sameClass(Object left, Object right) {
+ if (left == right)
+ return true;
+ if (left == null || right == null)
+ return false;
+ return left.getClass() == right.getClass();
+ }
+
+ /**
+ * Returns whether the arguments are equal.
+ *
+ * @param left
+ * object to test
+ * @param right
+ * object to test
+ * @return whether the arguments are equal.
+ */
+ public static boolean equal(Object left, Object right) {
+ if (!sameClass(left, right))
+ return false;
+ if (left == right)
+ return true;
+ Class clazz = left.getClass();
+ if (clazz.isArray()) {
+ Class componentType = clazz.getComponentType();
+ if (componentType.isPrimitive()) {
+ if (componentType == Byte.TYPE)
+ return Arrays.equals((byte[]) left, (byte[]) right);
+ if (componentType == Short.TYPE)
+ return Arrays.equals((short[]) left, (short[]) right);
+ if (componentType == Integer.TYPE)
+ return Arrays.equals((int[]) left, (int[]) right);
+ if (componentType == Long.TYPE)
+ return Arrays.equals((long[]) left, (long[]) right);
+ if (componentType == Character.TYPE)
+ return Arrays.equals((char[]) left, (char[]) right);
+ if (componentType == Float.TYPE)
+ return Arrays.equals((float[]) left, (float[]) right);
+ if (componentType == Double.TYPE)
+ return Arrays.equals((double[]) left, (double[]) right);
+ if (componentType == Boolean.TYPE)
+ return Arrays.equals((boolean[]) left, (boolean[]) right);
+ }
+ return equal((Object[]) left, (Object[]) right);
+ }
+ return left.equals(right);
+ }
+
+ private static boolean equal(Object[] left, Object[] right) {
+ int length = left.length;
+ if (length != right.length)
+ return false;
+ for (int i = 0; i < length; i++)
+ if (!equal(left[i], right[i]))
+ return false;
+ return true;
+ }
+
+ /**
+ * Returns whether the arguments are equal.
+ *
+ * @param left
+ * double value to test
+ * @param right
+ * double value to test
+ * @return whether the arguments are equal.
+ */
+ public static boolean equal(double left, double right) {
+ return Double.doubleToLongBits(left) == Double.doubleToLongBits(right);
+ }
+
+ /**
+ * Triggers a SWT.ERROR_NULL_ARGUMENT exception if the argument or any of
+ * its elements is null.
+ *
+ * @param list
+ * a list to test for null elements.
+ */
+ public static void noNulls(List list) {
+ notNull(list);
+ if (list.contains(null))
+ PaperClips.error(SWT.ERROR_NULL_ARGUMENT);
+ }
+
+ /**
+ * Triggers a SWT.ERROR_NULL_ARGUMENT exception if the argument or any of
+ * its elements is null.
+ *
+ * @param objs
+ * an array to test for null elements.
+ */
+ public static void noNulls(Object[] objs) {
+ notNull(objs);
+ for (int i = 0; i < objs.length; i++)
+ notNull(objs[i]);
+ }
+
+ /**
+ * Triggers a SWT.ERROR_NULL_ARGUMENT exception if the argument is null.
+ *
+ * @param obj
+ * the object to test for null.
+ */
+ public static void notNull(Object obj) {
+ if (obj == null)
+ PaperClips.error(SWT.ERROR_NULL_ARGUMENT);
+ }
+
+ /**
+ * Triggers a SWT.ERROR_NULL_ARGUMENT exception if any argument is null.
+ *
+ * @param o1
+ * an object to test for null.
+ * @param o2
+ * an object to test for null.
+ */
+ public static void notNull(Object o1, Object o2) {
+ notNull(o1);
+ notNull(o2);
+ }
+
+ /**
+ * Triggers a SWT.ERROR_NULL_ARGUMENT exception if any argument is null.
+ *
+ * @param o1
+ * an object to test for null.
+ * @param o2
+ * an object to test for null.
+ * @param o3
+ * an object to test for null.
+ */
+ public static void notNull(Object o1, Object o2, Object o3) {
+ notNull(o1);
+ notNull(o2);
+ notNull(o3);
+ }
+}
diff --git a/plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/messages.properties b/plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/messages.properties
new file mode 100644
index 0000000000..2a7dda53fe
--- /dev/null
+++ b/plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/messages.properties
@@ -0,0 +1 @@
+PAGE_X_OF_Y=Page {0} of {1}
diff --git a/plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/messages_de.properties b/plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/messages_de.properties
new file mode 100644
index 0000000000..4b88bc1e7d
--- /dev/null
+++ b/plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/messages_de.properties
@@ -0,0 +1 @@
+PAGE_X_OF_Y=Seite {0} von {1}
diff --git a/plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/messages_en.properties b/plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/messages_en.properties
new file mode 100644
index 0000000000..2a7dda53fe
--- /dev/null
+++ b/plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/messages_en.properties
@@ -0,0 +1 @@
+PAGE_X_OF_Y=Page {0} of {1}
diff --git a/plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/messages_fr.properties b/plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/messages_fr.properties
new file mode 100644
index 0000000000..a9117f20ee
--- /dev/null
+++ b/plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/messages_fr.properties
@@ -0,0 +1 @@
+PAGE_X_OF_Y=Page {0} sur {1}
diff --git a/plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/package.html b/plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/package.html
new file mode 100644
index 0000000000..ee45ddd2c3
--- /dev/null
+++ b/plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/package.html
@@ -0,0 +1,6 @@
+<html>
+<head></head>
+<body>
+Core classes for creating printable documents.
+</body>
+</html> \ No newline at end of file
diff --git a/plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/page/DefaultPageNumberFormat.java b/plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/page/DefaultPageNumberFormat.java
new file mode 100644
index 0000000000..1d407384b3
--- /dev/null
+++ b/plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/page/DefaultPageNumberFormat.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (c) 2006-2008 Matthew Hall 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:
+ * Matthew Hall - initial API and implementation
+ */
+package org.eclipse.sirius.table.ui.tools.internal.paperclips.page;
+
+import java.text.MessageFormat;
+
+import org.eclipse.sirius.table.ui.tools.internal.paperclips.Messages;
+import org.eclipse.sirius.table.ui.tools.internal.paperclips.internal.util.Util;
+
+/**
+ * The default PageNumberFormat used by PageNumberPrints.
+ * <p>
+ * This class formats page numbers as "Page x of y".
+ *
+ * @author Matthew Hall
+ */
+public final class DefaultPageNumberFormat implements PageNumberFormat {
+ private static MessageFormat messageFormat = new MessageFormat(Messages
+ .getString(Messages.PAGE_X_OF_Y));
+
+ public String format(PageNumber pageNumber) {
+ return messageFormat.format(new Object[] {
+ new Integer(pageNumber.getPageNumber() + 1),
+ new Integer(pageNumber.getPageCount()) });
+ }
+
+ public boolean equals(Object obj) {
+ return Util.sameClass(this, obj);
+ }
+
+ public int hashCode() {
+ return 47 * 41;
+ }
+} \ No newline at end of file
diff --git a/plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/page/PageDecoration.java b/plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/page/PageDecoration.java
new file mode 100644
index 0000000000..f8297770a1
--- /dev/null
+++ b/plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/page/PageDecoration.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (c) 2005 Matthew Hall 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:
+ * Matthew Hall - initial API and implementation
+ */
+package org.eclipse.sirius.table.ui.tools.internal.paperclips.page;
+
+import org.eclipse.sirius.table.ui.tools.internal.paperclips.Print;
+
+/**
+ * An interface for creating page decorations. Instances of this interface are
+ * used as headers and footers in conjunction with the PagePrint class.
+ *
+ * @see PagePrint
+ * @see SimplePageDecoration
+ * @see PageNumberPageDecoration
+ * @author Matthew Hall
+ */
+public interface PageDecoration {
+ /**
+ * Returns a decorator Print for the page with the given page number, or
+ * null if no decoration is provided for the given page.
+ *
+ * @param pageNumber
+ * the page number of the page being decorated.
+ * @return a decorator Print for the page with the given page number, or
+ * null if no decoration is provided for the given page.
+ */
+ public Print createPrint(PageNumber pageNumber);
+}
diff --git a/plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/page/PageNumber.java b/plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/page/PageNumber.java
new file mode 100644
index 0000000000..838c150404
--- /dev/null
+++ b/plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/page/PageNumber.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (c) 2005 Matthew Hall 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:
+ * Matthew Hall - initial API and implementation
+ */
+package org.eclipse.sirius.table.ui.tools.internal.paperclips.page;
+
+/**
+ * Instances of this class represent a page index in the output of a PagePrint.
+ *
+ * @author Matthew Hall
+ */
+public interface PageNumber {
+ /**
+ * Returns the zero-based page index.
+ *
+ * @return the zero-based page index.
+ */
+ public int getPageNumber();
+
+ /**
+ * Returns the total number of pages. Note that this method may not return
+ * an accurate value until all pages have been laid out. Therefore this
+ * method should not be used inside
+ * {@link PageDecoration#createPrint(PageNumber)}.
+ *
+ * @return the total number of pages.
+ */
+ public int getPageCount();
+}
diff --git a/plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/page/PageNumberFormat.java b/plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/page/PageNumberFormat.java
new file mode 100644
index 0000000000..74fd57d8ea
--- /dev/null
+++ b/plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/page/PageNumberFormat.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright (c) 2005 Matthew Hall 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:
+ * Matthew Hall - initial API and implementation
+ */
+package org.eclipse.sirius.table.ui.tools.internal.paperclips.page;
+
+/**
+ * Interface for formatting a PageNumber instance into a printable string.
+ *
+ * @author Matthew Hall
+ */
+public interface PageNumberFormat {
+ /**
+ * Returns a formatted String representing the pageNumber argument.
+ *
+ * @param pageNumber
+ * the page number to be formatted into a String.
+ * @return a formatted String representing the pageNumber argument.
+ */
+ public String format(PageNumber pageNumber);
+}
diff --git a/plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/page/PageNumberPageDecoration.java b/plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/page/PageNumberPageDecoration.java
new file mode 100644
index 0000000000..aecfe9f7b7
--- /dev/null
+++ b/plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/page/PageNumberPageDecoration.java
@@ -0,0 +1,183 @@
+/*
+ * Copyright (c) 2006 Matthew Hall 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:
+ * Matthew Hall - initial API and implementation
+ */
+package org.eclipse.sirius.table.ui.tools.internal.paperclips.page;
+
+import org.eclipse.sirius.table.ui.tools.internal.paperclips.Print;
+import org.eclipse.sirius.table.ui.tools.internal.paperclips.internal.util.PaperClipsUtil;
+import org.eclipse.sirius.table.ui.tools.internal.paperclips.internal.util.Util;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.graphics.FontData;
+import org.eclipse.swt.graphics.RGB;
+
+/**
+ * A PageDecoration which displays the page number. This convenience class helps
+ * avoid the need for writing a new PageDecoration class if only a page number
+ * is needed. Getter and setter methods are provided for all the properties
+ * available in the PagePrint class itself.
+ *
+ * @author Matthew Hall
+ */
+public class PageNumberPageDecoration implements PageDecoration {
+ FontData fontData = new FontData();
+ int align = SWT.LEFT;
+ RGB rgb = new RGB(0, 0, 0); // black
+ PageNumberFormat format = new DefaultPageNumberFormat();
+
+ /**
+ * Constructs a PageNumberPageDecoration with default font, alignment, and
+ * page number format.
+ */
+ public PageNumberPageDecoration() {
+ }
+
+ /**
+ * Constructs a PageNumberPageDecoration with the given alignment.
+ *
+ * @param align
+ * horizontal text alignment.
+ */
+ public PageNumberPageDecoration(int align) {
+ setAlign(align);
+ }
+
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + align;
+ result = prime * result
+ + ((fontData == null) ? 0 : fontData.hashCode());
+ result = prime * result + ((format == null) ? 0 : format.hashCode());
+ result = prime * result + ((rgb == null) ? 0 : rgb.hashCode());
+ return result;
+ }
+
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (obj == null)
+ return false;
+ if (getClass() != obj.getClass())
+ return false;
+ PageNumberPageDecoration other = (PageNumberPageDecoration) obj;
+ if (align != other.align)
+ return false;
+ if (fontData == null) {
+ if (other.fontData != null)
+ return false;
+ } else if (!fontData.equals(other.fontData))
+ return false;
+ if (format == null) {
+ if (other.format != null)
+ return false;
+ } else if (!format.equals(other.format))
+ return false;
+ if (rgb == null) {
+ if (other.rgb != null)
+ return false;
+ } else if (!rgb.equals(other.rgb))
+ return false;
+ return true;
+ }
+
+ /**
+ * Returns the font.
+ *
+ * @return the font.
+ */
+ public FontData getFontData() {
+ return fontData;
+ }
+
+ /**
+ * Sets the font.
+ *
+ * @param fontData
+ * the new font.
+ */
+ public void setFontData(FontData fontData) {
+ Util.notNull(fontData);
+ this.fontData = fontData;
+ }
+
+ /**
+ * Returns the horizontal text alignment.
+ *
+ * @return the horizontal text alignment.
+ */
+ public int getAlign() {
+ return align;
+ }
+
+ /**
+ * Sets the horizontal text alignment.
+ *
+ * @param align
+ * the horizontal text alignment.
+ */
+ public void setAlign(int align) {
+ align = checkAlign(align);
+ this.align = align;
+ }
+
+ private int checkAlign(int align) {
+ return PaperClipsUtil.firstMatch(align, new int[] { SWT.LEFT,
+ SWT.CENTER, SWT.RIGHT }, SWT.LEFT);
+ }
+
+ /**
+ * Returns the text color.
+ *
+ * @return the text color.
+ */
+ public RGB getRGB() {
+ return rgb;
+ }
+
+ /**
+ * Sets the text color.
+ *
+ * @param rgb
+ * the new text color.
+ */
+ public void setRGB(RGB rgb) {
+ Util.notNull(rgb);
+ this.rgb = rgb;
+ }
+
+ /**
+ * Returns the page number format.
+ *
+ * @return the page number format.
+ */
+ public PageNumberFormat getFormat() {
+ return format;
+ }
+
+ /**
+ * Sets the page number format.
+ *
+ * @param format
+ * the page number format.
+ */
+ public void setFormat(PageNumberFormat format) {
+ Util.notNull(format);
+ this.format = format;
+ }
+
+ public Print createPrint(PageNumber pageNumber) {
+ PageNumberPrint result = new PageNumberPrint(pageNumber);
+ result.setFontData(fontData);
+ result.setAlign(align);
+ result.setPageNumberFormat(format);
+ result.setRGB(rgb);
+ return result;
+ }
+} \ No newline at end of file
diff --git a/plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/page/PageNumberPrint.java b/plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/page/PageNumberPrint.java
new file mode 100644
index 0000000000..cbb037869a
--- /dev/null
+++ b/plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/page/PageNumberPrint.java
@@ -0,0 +1,443 @@
+/*
+ * Copyright (c) 2005 Matthew Hall 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:
+ * Matthew Hall - initial API and implementation
+ */
+package org.eclipse.sirius.table.ui.tools.internal.paperclips.page;
+
+import org.eclipse.sirius.table.ui.tools.internal.paperclips.Print;
+import org.eclipse.sirius.table.ui.tools.internal.paperclips.PrintIterator;
+import org.eclipse.sirius.table.ui.tools.internal.paperclips.PrintPiece;
+import org.eclipse.sirius.table.ui.tools.internal.paperclips.internal.util.PaperClipsUtil;
+import org.eclipse.sirius.table.ui.tools.internal.paperclips.internal.util.ResourcePool;
+import org.eclipse.sirius.table.ui.tools.internal.paperclips.internal.util.Util;
+import org.eclipse.sirius.table.ui.tools.internal.paperclips.text.TextStyle;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.graphics.Color;
+import org.eclipse.swt.graphics.Device;
+import org.eclipse.swt.graphics.Font;
+import org.eclipse.swt.graphics.FontData;
+import org.eclipse.swt.graphics.GC;
+import org.eclipse.swt.graphics.Point;
+import org.eclipse.swt.graphics.RGB;
+
+/**
+ * Displays the page number and page count within the context of a
+ * {@link PagePrint}. To properly display page numbers, instances of this class
+ * should be created using the {@link PageNumber} argument which is passed to
+ * the {@link PageDecoration#createPrint(PageNumber)} method by PagePrint.
+ * <p>
+ * PageNumberPrints are never greedy with layout space, even with center- or
+ * right-alignment. (Greedy prints take up all the available space on the page.)
+ * Therefore, when center- or right-alignment is required, it is necessary to
+ * wrap the page number in a Print which will enforce the same alignment.
+ * Usually this is a center:default:grow or right:default:grow column in a
+ * GridPrint.
+ *
+ * @author Matthew Hall
+ * @see PagePrint
+ * @see PageDecoration
+ * @see PageNumber
+ * @see PageNumberFormat
+ * @see DefaultPageNumberFormat
+ */
+public class PageNumberPrint implements Print {
+ /** The default font data for a PageNumberPrint. Value is device-dependent. */
+ public static final FontData DEFAULT_FONT_DATA = new FontData();
+
+ /** The default alignment for a PageNumberPrint. Value is SWT.LEFT. */
+ public static final int DEFAULT_ALIGN = SWT.LEFT;
+
+ /** The default text style. Value is device-dependent. */
+ public static final TextStyle DEFAULT_TEXT_STYLE = new TextStyle().font(
+ DEFAULT_FONT_DATA).align(DEFAULT_ALIGN);
+
+ PageNumber pageNumber;
+ TextStyle textStyle;
+ PageNumberFormat format;
+
+ /**
+ * Constructs a PageNumberPrint for the given page number.
+ *
+ * @param pageNumber
+ * the page number of the page this Print will appear on.
+ */
+ public PageNumberPrint(PageNumber pageNumber) {
+ this(pageNumber, DEFAULT_TEXT_STYLE);
+ }
+
+ /**
+ * Constructs a PageNumberPrint for the given page number and font.
+ *
+ * @param pageNumber
+ * the page number of the page this Print will appear on.
+ * @param fontData
+ * the font that this Print will appear in.
+ */
+ public PageNumberPrint(PageNumber pageNumber, FontData fontData) {
+ this(pageNumber, DEFAULT_TEXT_STYLE.font(fontData));
+ }
+
+ /**
+ * Constructs a PageNumberPrint for the given page number and alignment.
+ *
+ * @param pageNumber
+ * the page number of the page this Print will appear on.
+ * @param align
+ * the horizontal alignment of the text.
+ */
+ public PageNumberPrint(PageNumber pageNumber, int align) {
+ this(pageNumber, DEFAULT_TEXT_STYLE.align(align));
+ }
+
+ /**
+ * Constructs a PageNumberPrint for the given page number, font and
+ * alignment.
+ *
+ * @param pageNumber
+ * the page number of the page this Print will appear on.
+ * @param fontData
+ * the font that this Print will appear in.
+ * @param align
+ * the horizontal alignment of the text.
+ */
+ public PageNumberPrint(PageNumber pageNumber, FontData fontData, int align) {
+ this(pageNumber, DEFAULT_TEXT_STYLE.font(fontData).align(align));
+ }
+
+ /**
+ * Constructs a PageNumberPrint for the given page number and text style.
+ *
+ * @param pageNumber
+ * the page number of the page this Print will appear on.
+ * @param textStyle
+ * the text style that this Print will appear in.
+ */
+ public PageNumberPrint(PageNumber pageNumber, TextStyle textStyle) {
+ setPageNumber(pageNumber);
+ setTextStyle(textStyle);
+ setPageNumberFormat(new DefaultPageNumberFormat());
+ }
+
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result
+ + ((pageNumber == null) ? 0 : pageNumber.hashCode());
+ result = prime * result
+ + ((textStyle == null) ? 0 : textStyle.hashCode());
+ result = prime * result + ((format == null) ? 0 : format.hashCode());
+ return result;
+ }
+
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (obj == null)
+ return false;
+ if (getClass() != obj.getClass())
+ return false;
+ PageNumberPrint other = (PageNumberPrint) obj;
+
+ if (pageNumber == null) {
+ if (other.pageNumber != null)
+ return false;
+ } else if (!pageNumber.equals(other.pageNumber))
+ return false;
+
+ if (textStyle == null) {
+ if (other.textStyle != null)
+ return false;
+ } else if (!textStyle.equals(other.textStyle))
+ return false;
+
+ if (format == null) {
+ if (other.format != null)
+ return false;
+ } else if (!format.equals(other.format))
+ return false;
+
+ return true;
+ }
+
+ /**
+ * Sets the page number to the argument.
+ *
+ * @param pageNumber
+ * the new page number.
+ */
+ public void setPageNumber(PageNumber pageNumber) {
+ Util.notNull(pageNumber);
+ this.pageNumber = pageNumber;
+ }
+
+ /**
+ * Returns the page number of this Print.
+ *
+ * @return the page number of this Print.
+ */
+ public PageNumber getPageNumber() {
+ return pageNumber;
+ }
+
+ /**
+ * Sets the text font to the argument.
+ *
+ * @param fontData
+ * the new text font.
+ */
+ public void setFontData(FontData fontData) {
+ Util.notNull(fontData);
+ setTextStyle(textStyle.font(fontData));
+ }
+
+ /**
+ * Returns the text font.
+ *
+ * @return the text font.
+ */
+ public FontData getFontData() {
+ return textStyle.getFontData();
+ }
+
+ /**
+ * Sets the horizontal text alignment to the argument.
+ *
+ * @param align
+ * the horizontal alignment. Must be one of {@link SWT#LEFT },
+ * {@link SWT#CENTER } or {@link SWT#RIGHT }.
+ */
+ public void setAlign(int align) {
+ setTextStyle(textStyle.align(checkAlign(align)));
+ }
+
+ /**
+ * Returns the horizontal text alignment.
+ *
+ * @return the horizontal text alignment.
+ */
+ public int getAlign() {
+ return textStyle.getAlignment();
+ }
+
+ private int checkAlign(int align) {
+ return PaperClipsUtil.firstMatch(align, new int[] { SWT.LEFT,
+ SWT.CENTER, SWT.RIGHT }, SWT.LEFT);
+ }
+
+ /**
+ * Returns the text style that will be used to render the page number
+ *
+ * @return the text style that will be used to render the page number
+ */
+ public TextStyle getTextStyle() {
+ return textStyle;
+ }
+
+ /**
+ * Sets the text style that will be used to render the page number
+ *
+ * @param textStyle
+ * the text style
+ */
+ public void setTextStyle(TextStyle textStyle) {
+ Util.notNull(textStyle);
+ this.textStyle = textStyle;
+ }
+
+ /**
+ * Sets the format that will be used to convert the page number to a text
+ * string.
+ *
+ * @param format
+ * the new page number format.
+ */
+ public void setPageNumberFormat(PageNumberFormat format) {
+ Util.notNull(format);
+ this.format = format;
+ }
+
+ /**
+ * Returns the page number format. This property determines how the
+ * PageNumber will be converted into a String representing the page number.
+ * The default value of this property formats page numbers as follows:<br>
+ *
+ * <pre>
+ * Page 1 of 5
+ * </pre>
+ *
+ * @return the page number format.
+ */
+ public PageNumberFormat getPageNumberFormat() {
+ return format;
+ }
+
+ /**
+ * Sets the text color.
+ *
+ * @param foreground
+ * the new text color.
+ */
+ public void setRGB(RGB foreground) {
+ Util.notNull(foreground);
+ setTextStyle(textStyle.foreground(foreground));
+ }
+
+ /**
+ * Returns the text color.
+ *
+ * @return the text color.
+ */
+ public RGB getRGB() {
+ return textStyle.getForeground();
+ }
+
+ public PrintIterator iterator(Device device, GC gc) {
+ return new PageNumberIterator(this, device, gc);
+ }
+}
+
+class PageNumberIterator implements PrintIterator {
+ private final Device device;
+ private final GC gc;
+
+ final PageNumber pageNumber;
+ final TextStyle textStyle;
+ final PageNumberFormat format;
+ final Point size;
+
+ boolean hasNext = true;
+
+ PageNumberIterator(PageNumberPrint print, Device device, GC gc) {
+ this.device = device;
+ this.gc = gc;
+
+ this.pageNumber = print.pageNumber;
+ this.textStyle = print.textStyle;
+ this.format = print.format;
+
+ // Calculate the size for the largest possible page number string.
+ Font oldFont = gc.getFont();
+ try {
+ gc.setFont(ResourcePool.forDevice(device).getFont(
+ textStyle.getFontData()));
+
+ size = gc.textExtent(format.format(new PageNumber() {
+ public int getPageCount() {
+ return 9999;
+ }
+
+ public int getPageNumber() {
+ return 9998;
+ } // (zero-based index)
+ }));
+ } finally {
+ gc.setFont(oldFont);
+ }
+ }
+
+ PageNumberIterator(PageNumberIterator that) {
+ this.device = that.device;
+ this.gc = that.gc;
+
+ this.pageNumber = that.pageNumber;
+ this.textStyle = that.textStyle;
+ this.format = that.format;
+ this.size = that.size;
+ this.hasNext = that.hasNext;
+ }
+
+ public boolean hasNext() {
+ return hasNext;
+ }
+
+ public Point minimumSize() {
+ return size;
+ }
+
+ public Point preferredSize() {
+ return size;
+ }
+
+ public PrintPiece next(int width, int height) {
+ if (width < size.x || height < size.y)
+ return null;
+
+ Point size = new Point(this.size.x, this.size.y);
+ int align = textStyle.getAlignment();
+ if (align == SWT.CENTER || align == SWT.RIGHT)
+ size.x = width;
+
+ PageNumberPiece piece = new PageNumberPiece(this, device, size);
+ hasNext = false;
+
+ return piece;
+ }
+
+ public PrintIterator copy() {
+ return new PageNumberIterator(this);
+ }
+}
+
+class PageNumberPiece implements PrintPiece {
+ private final Device device;
+ private final Point size;
+
+ private final PageNumber pageNumber;
+ private final TextStyle textStyle;
+ private final PageNumberFormat format;
+
+ PageNumberPiece(PageNumberIterator iter, Device device, Point size) {
+ this.device = device;
+ this.size = size;
+ this.pageNumber = iter.pageNumber;
+ this.textStyle = iter.textStyle;
+ this.format = iter.format;
+ }
+
+ public Point getSize() {
+ return new Point(size.x, size.y);
+ }
+
+ public void paint(final GC gc, final int x, final int y) {
+ Font oldFont = gc.getFont();
+ Color oldForeground = gc.getForeground();
+
+ Point size = getSize();
+
+ try {
+ ResourcePool resources = ResourcePool.forDevice(device);
+ gc.setFont(resources.getFont(textStyle.getFontData()));
+ gc.setForeground(resources.getColor(textStyle.getForeground()));
+
+ String text = format.format(pageNumber);
+ gc.drawText(text, x
+ + getHorzAlignmentOffset(gc.textExtent(text).x, size.x), y,
+ true);
+ } finally {
+ gc.setFont(oldFont);
+ gc.setForeground(oldForeground);
+ }
+ }
+
+ private int getHorzAlignmentOffset(int textWidth, int totalWidth) {
+ int offset = 0;
+ switch (textStyle.getAlignment()) {
+ case SWT.CENTER:
+ offset = (totalWidth - textWidth) / 2;
+ break;
+ case SWT.RIGHT:
+ offset = totalWidth - textWidth;
+ break;
+ }
+ return offset;
+ }
+
+ public void dispose() {
+ } // Shared resources, nothing to dispose
+} \ No newline at end of file
diff --git a/plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/page/PagePrint.java b/plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/page/PagePrint.java
new file mode 100644
index 0000000000..63b456892b
--- /dev/null
+++ b/plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/page/PagePrint.java
@@ -0,0 +1,551 @@
+/*
+ * Copyright (c) 2005 Matthew Hall 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:
+ * Matthew Hall - initial API and implementation
+ */
+package org.eclipse.sirius.table.ui.tools.internal.paperclips.page;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.eclipse.sirius.table.ui.tools.internal.paperclips.CompositeEntry;
+import org.eclipse.sirius.table.ui.tools.internal.paperclips.CompositePiece;
+import org.eclipse.sirius.table.ui.tools.internal.paperclips.PaperClips;
+import org.eclipse.sirius.table.ui.tools.internal.paperclips.Print;
+import org.eclipse.sirius.table.ui.tools.internal.paperclips.PrintIterator;
+import org.eclipse.sirius.table.ui.tools.internal.paperclips.PrintPiece;
+import org.eclipse.sirius.table.ui.tools.internal.paperclips.internal.util.PaperClipsUtil;
+import org.eclipse.sirius.table.ui.tools.internal.paperclips.internal.util.PrintSizeStrategy;
+import org.eclipse.sirius.table.ui.tools.internal.paperclips.internal.util.Util;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.graphics.Device;
+import org.eclipse.swt.graphics.GC;
+import org.eclipse.swt.graphics.Point;
+
+/**
+ * A decorator Print which displays page headers and footers around a document
+ * body, with page numbering capabilities.
+ * <p>
+ * PagePrint is horizontally and vertically greedy. Greedy prints take up all
+ * the available space on the page.
+ * <p>
+ * <b>Note:</b> Avoid wrapping PagePrint in prints with space-optimizing
+ * semantics (e.g. ColumnPrint equalizes columns on the last page), as this may
+ * cause the total page count to be incorrect on some pages. At this time there
+ * is no known fix. If wrapping a PagePrint is unavoidable, consider using a
+ * custom PageNumberFormat which does not display the total page count.
+ *
+ * @author Matthew Hall
+ */
+public class PagePrint implements Print {
+ private static final int DEFAULT_GAP = 1;
+
+ PageDecoration header;
+ int headerGap = DEFAULT_GAP; // in points
+ Print body;
+ int footerGap = DEFAULT_GAP; // in points
+ PageDecoration footer;
+
+ /**
+ * Constructs a PagePrint with the given header and body.
+ *
+ * @param header
+ * a PageDecoration for creating the header. May be null.
+ * @param headerGap
+ * the gap between the header and body, in points.
+ * @param body
+ * the Print being decorated.
+ */
+ public PagePrint(PageDecoration header, int headerGap, Print body) {
+ this(header, headerGap, body, DEFAULT_GAP, null);
+ }
+
+ /**
+ * Constructs a PagePrint with the given header and body.
+ *
+ * @param body
+ * the Print being decorated.
+ * @param header
+ * a PageDecoration for creating the header. May be null.
+ */
+ public PagePrint(PageDecoration header, Print body) {
+ this(header, DEFAULT_GAP, body);
+ }
+
+ /**
+ * Constructs a PagePrint with the given body.
+ *
+ * @param body
+ * the Print being decorated.
+ */
+ public PagePrint(Print body) {
+ this(null, body, null);
+ }
+
+ /**
+ * Constructs a PagePrint with the given body and footer.
+ *
+ * @param body
+ * the Print being decorated.
+ * @param footer
+ * a PageDecoration for creating the footer. may be null.
+ */
+ public PagePrint(Print body, PageDecoration footer) {
+ this(body, DEFAULT_GAP, footer);
+ }
+
+ /**
+ * Constructs a PagePrint with the given body, header and footer.
+ *
+ * @param body
+ * the Print being decorated.
+ * @param footerGap
+ * the gap between the body and footer, in points.
+ * @param footer
+ * a PageDecoration for creating the footer. May be null.
+ */
+ public PagePrint(Print body, int footerGap, PageDecoration footer) {
+ this(null, DEFAULT_GAP, body, footerGap, footer);
+ }
+
+ /**
+ * Constructs a PagePrint with the given body, header and footer.
+ *
+ * @param header
+ * a PageDecoration for creating the header. May be null.
+ * @param body
+ * the Print being decorated.
+ * @param footer
+ * a PageDecoration for creating the footer. may be null.
+ */
+ public PagePrint(PageDecoration header, Print body, PageDecoration footer) {
+ this(header, DEFAULT_GAP, body, DEFAULT_GAP, footer);
+ }
+
+ /**
+ * Constructs a PagePrint with the given body, header and footer.
+ *
+ * @param header
+ * a PageDecoration for creating the header. May be null.
+ * @param headerGap
+ * the gap between the header and body, in points.
+ * @param body
+ * the Print being decorated.
+ * @param footerGap
+ * the gap between the body and footer, in points.
+ * @param footer
+ * a PageDecoration for creating the footer. May be null.
+ */
+ public PagePrint(PageDecoration header, int headerGap, Print body,
+ int footerGap, PageDecoration footer) {
+ setHeader(header);
+ setHeaderGap(headerGap);
+ setBody(body);
+ setFooterGap(footerGap);
+ setFooter(footer);
+ }
+
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + ((body == null) ? 0 : body.hashCode());
+ result = prime * result + ((footer == null) ? 0 : footer.hashCode());
+ result = prime * result + footerGap;
+ result = prime * result + ((header == null) ? 0 : header.hashCode());
+ result = prime * result + headerGap;
+ return result;
+ }
+
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (obj == null)
+ return false;
+ if (getClass() != obj.getClass())
+ return false;
+ PagePrint other = (PagePrint) obj;
+ if (body == null) {
+ if (other.body != null)
+ return false;
+ } else if (!body.equals(other.body))
+ return false;
+ if (footer == null) {
+ if (other.footer != null)
+ return false;
+ } else if (!footer.equals(other.footer))
+ return false;
+ if (footerGap != other.footerGap)
+ return false;
+ if (header == null) {
+ if (other.header != null)
+ return false;
+ } else if (!header.equals(other.header))
+ return false;
+ if (headerGap != other.headerGap)
+ return false;
+ return true;
+ }
+
+ /**
+ * Returns the page header.
+ *
+ * @return the page header.
+ */
+ public PageDecoration getHeader() {
+ return header;
+ }
+
+ /**
+ * Sets the page header to the argument.
+ *
+ * @param header
+ * a PageDecoration which creates the header. May be null.
+ */
+ public void setHeader(PageDecoration header) {
+ this.header = header;
+ }
+
+ /**
+ * Returns the gap between the header and body, expressed in points.
+ *
+ * @return the gap between the header and body, expressed in points.
+ */
+ public int getHeaderGap() {
+ return headerGap;
+ }
+
+ /**
+ * Sets the gap between the header and body to the argument, expressed in
+ * points.
+ *
+ * @param points
+ * the new gap between the header and body, expressed in points.
+ * 72 points = 1".
+ */
+ public void setHeaderGap(int points) {
+ this.headerGap = checkGap(points);
+ }
+
+ /**
+ * Returns the page body.
+ *
+ * @return the page body.
+ */
+ public Print getBody() {
+ return body;
+ }
+
+ /**
+ * Sets the page body to the argument.
+ *
+ * @param body
+ * the new page body.
+ */
+ public void setBody(Print body) {
+ Util.notNull(body);
+ this.body = body;
+ }
+
+ /**
+ * Returns the page footer.
+ *
+ * @return the page footer.
+ */
+ public PageDecoration getFooter() {
+ return footer;
+ }
+
+ /**
+ * Sets the page footer to the argument.
+ *
+ * @param footer
+ * a PageDecoration which creates the footer. May be null.
+ */
+ public void setFooter(PageDecoration footer) {
+ this.footer = footer;
+ }
+
+ /**
+ * Returns the gap between the body and footer, expressed in points.
+ *
+ * @return the gap between the body and footer, expressed in points.
+ */
+ public int getFooterGap() {
+ return footerGap;
+ }
+
+ /**
+ * Sets the gap between the body and footer to the argument, expressed in
+ * points.
+ *
+ * @param points
+ * the new gap between the body and footer (if there is a
+ * footer).
+ */
+ public void setFooterGap(int points) {
+ this.footerGap = checkGap(points);
+ }
+
+ private static int checkGap(int gap) {
+ if (gap < 0)
+ PaperClips.error(SWT.ERROR_INVALID_ARGUMENT,
+ "Gap must be >= 0 (value is " + gap + ")"); //$NON-NLS-1$ //$NON-NLS-2$
+ return gap;
+ }
+
+ public PrintIterator iterator(Device device, GC gc) {
+ if (header == null && footer == null)
+ return body.iterator(device, gc);
+
+ return new PageIterator(this, device, gc);
+ }
+}
+
+class PageIterator implements PrintIterator {
+ class PageNumberer {
+ int pageCount = 0;
+
+ synchronized PageNumber next() {
+ return new InnerPageNumber();
+ }
+
+ class InnerPageNumber implements PageNumber {
+ final int pageNumber = pageCount++; // POST-increment
+
+ public int getPageCount() {
+ return pageCount;
+ }
+
+ public int getPageNumber() {
+ return pageNumber;
+ }
+ }
+
+ PageNumberer copy() {
+ PageNumberer result = new PageNumberer();
+ result.pageCount = this.pageCount;
+ return result;
+ }
+ }
+
+ final Device device;
+ final GC gc;
+
+ final PageDecoration header;
+ final int headerGap; // pixels
+ final PrintIterator body;
+ final int footerGap; // pixels
+ final PageDecoration footer;
+
+ final PageNumberer numberer;
+
+ final Point minimumSize;
+ final Point preferredSize;
+
+ PageIterator(PagePrint print, Device device, GC gc) {
+ this.device = device;
+ this.gc = gc;
+
+ Point dpi = device.getDPI();
+
+ body = print.body.iterator(device, gc);
+ header = print.header;
+ headerGap = header == null ? 0 : print.headerGap * dpi.y / 72;
+ footer = print.footer;
+ footerGap = footer == null ? 0 : print.footerGap * dpi.y / 72;
+
+ this.numberer = new PageNumberer();
+
+ this.minimumSize = computeSize(PrintSizeStrategy.MINIMUM);
+ this.preferredSize = computeSize(PrintSizeStrategy.PREFERRED);
+ }
+
+ PageIterator(PageIterator that) {
+ this.device = that.device;
+ this.gc = that.gc;
+
+ this.body = that.body.copy();
+ this.header = that.header;
+ this.headerGap = that.headerGap;
+ this.footer = that.footer;
+ this.footerGap = that.footerGap;
+
+ // FIXME: Wrapping PagePrint in a class with space-optimizing semantics
+ // (ColumnPrint) can fork the total
+ // page count. i.e. if the copied PageIterator is chosen as the optimal
+ // layout, then previous pages will
+ // have a page number spawned from a different page numberer. Thus the
+ // total page count for those
+ // previous pages will no longer be incremented with each new page.
+ this.numberer = that.numberer.copy();
+ this.pageNumber = that.pageNumber;
+
+ this.minimumSize = that.minimumSize;
+ this.preferredSize = that.preferredSize;
+ }
+
+ private Point computeSize(PrintSizeStrategy strategy) {
+ Point size = strategy.computeSize(body);
+
+ PageNumber samplePageNumber = new PageNumber() {
+ public int getPageCount() {
+ return 1;
+ }
+
+ public int getPageNumber() {
+ return 0;
+ }
+ };
+
+ if (header != null) {
+ Print headerPrint = header.createPrint(samplePageNumber);
+ if (headerPrint != null) {
+ PrintIterator iter = headerPrint.iterator(device, gc);
+
+ size.y += headerGap;
+
+ Point headerSize = strategy.computeSize(iter);
+ size.x = Math.max(size.x, headerSize.x);
+ size.y += headerSize.y;
+ }
+ }
+
+ if (footer != null) {
+ Print footerPrint = footer.createPrint(samplePageNumber);
+ if (footerPrint != null) {
+ PrintIterator iter = footerPrint.iterator(device, gc);
+
+ size.y += footerGap;
+
+ Point footerSize = strategy.computeSize(iter);
+ size.x = Math.max(size.x, footerSize.x);
+ size.y += footerSize.y;
+ }
+ }
+
+ return size;
+ }
+
+ public boolean hasNext() {
+ return body.hasNext();
+ }
+
+ public Point minimumSize() {
+ return new Point(minimumSize.x, minimumSize.y);
+ }
+
+ public Point preferredSize() {
+ return new Point(preferredSize.x, preferredSize.y);
+ }
+
+ public PrintPiece next(int width, final int height) {
+ PageNumber pageNumber = getCurrentPageNumber();
+
+ // HEADER
+ PrintPiece headerPiece = null;
+ int availableHeight = height;
+ if (header != null) {
+ Print headerPrint = header.createPrint(pageNumber);
+ if (headerPrint != null) {
+ headerPiece = getDecorationPrintPiece(headerPrint, width,
+ availableHeight);
+ if (headerPiece == null)
+ return null;
+ availableHeight -= (heightOf(headerPiece) + headerGap);
+ }
+ }
+
+ // FOOTER
+ PrintPiece footerPiece = null;
+ if (footer != null) {
+ Print footerPrint = footer.createPrint(pageNumber);
+ if (footerPrint != null) {
+ footerPiece = getDecorationPrintPiece(footerPrint, width,
+ availableHeight);
+ if (footerPiece == null) {
+ PaperClipsUtil.dispose(headerPiece);
+ return null;
+ }
+ availableHeight -= (heightOf(footerPiece) + footerGap);
+ }
+ }
+
+ // BODY
+ PrintPiece bodyPiece = PaperClips.next(body, width, availableHeight);
+ if (bodyPiece == null) {
+ PaperClipsUtil.dispose(headerPiece, footerPiece);
+ return null;
+ }
+
+ PrintPiece result = createResult(height, headerPiece, bodyPiece,
+ footerPiece);
+ advancePageNumber();
+ return result;
+ }
+
+ private int heightOf(PrintPiece piece) {
+ return piece.getSize().y;
+ }
+
+ PageNumber pageNumber;
+
+ private void advancePageNumber() {
+ // Null the pageNumber field so the next iteration advances to the next
+ // page.
+ pageNumber = null;
+ }
+
+ private PageNumber getCurrentPageNumber() {
+ if (pageNumber == null)
+ pageNumber = numberer.next();
+ return pageNumber;
+ }
+
+ private PrintPiece createResult(int height, PrintPiece headerPiece,
+ PrintPiece bodyPiece, PrintPiece footerPiece) {
+ if (headerPiece == null && footerPiece == null)
+ return bodyPiece;
+
+ List entries = new ArrayList();
+
+ if (headerPiece != null)
+ entries.add(createEntry(headerPiece, 0));
+
+ int y = headerPiece == null ? 0 : heightOf(headerPiece) + headerGap;
+ entries.add(createEntry(bodyPiece, y));
+
+ if (footerPiece != null) {
+ y = height - heightOf(footerPiece);
+ entries.add(createEntry(footerPiece, y));
+ }
+
+ return new CompositePiece(entries);
+ }
+
+ private CompositeEntry createEntry(PrintPiece piece, int y) {
+ return new CompositeEntry(piece, new Point(0, y));
+ }
+
+ private PrintPiece getDecorationPrintPiece(Print decoration, int width,
+ int height) {
+ PrintIterator iterator = decoration.iterator(device, gc);
+ PrintPiece piece = PaperClips.next(iterator, width, height);
+
+ if (piece == null)
+ return null;
+ if (iterator.hasNext()) {
+ piece.dispose();
+ return null;
+ }
+ return piece;
+ }
+
+ public PrintIterator copy() {
+ return new PageIterator(this);
+ }
+} \ No newline at end of file
diff --git a/plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/page/SimplePageDecoration.java b/plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/page/SimplePageDecoration.java
new file mode 100644
index 0000000000..28cfc9b76b
--- /dev/null
+++ b/plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/page/SimplePageDecoration.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (c) 2006 Matthew Hall 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:
+ * Matthew Hall - initial API and implementation
+ */
+package org.eclipse.sirius.table.ui.tools.internal.paperclips.page;
+
+import org.eclipse.sirius.table.ui.tools.internal.paperclips.Print;
+import org.eclipse.sirius.table.ui.tools.internal.paperclips.internal.util.Util;
+
+/**
+ * A PageDecoration which displays the same decoration on every page (ignoring
+ * the page number).
+ * <p>
+ * Typically the page number will be in either the header or footer, but not in
+ * both. Often the page number is the only thing that changes from page to page
+ * in a header. Use this class for a header or footer which does not display the
+ * page number.
+ *
+ * @author Matthew Hall
+ */
+public class SimplePageDecoration implements PageDecoration {
+ private final Print print;
+
+ /**
+ * Constructs a BasicPageDecoration.
+ *
+ * @param print
+ * the decoration which will appear on every page.
+ */
+ public SimplePageDecoration(Print print) {
+ Util.notNull(print);
+ this.print = print;
+ }
+
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + ((print == null) ? 0 : print.hashCode());
+ return result;
+ }
+
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (obj == null)
+ return false;
+ if (getClass() != obj.getClass())
+ return false;
+ SimplePageDecoration other = (SimplePageDecoration) obj;
+ if (print == null) {
+ if (other.print != null)
+ return false;
+ } else if (!print.equals(other.print))
+ return false;
+ return true;
+ }
+
+ public Print createPrint(PageNumber pageNumber) {
+ return print;
+ }
+}
diff --git a/plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/text/LineBreakPrint.java b/plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/text/LineBreakPrint.java
new file mode 100644
index 0000000000..d9bdf00084
--- /dev/null
+++ b/plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/text/LineBreakPrint.java
@@ -0,0 +1,124 @@
+/*
+ * Copyright (c) 2007 Matthew Hall 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:
+ * Matthew Hall - initial API and implementation
+ */
+package org.eclipse.sirius.table.ui.tools.internal.paperclips.text;
+
+import org.eclipse.sirius.table.ui.tools.internal.paperclips.Print;
+import org.eclipse.sirius.table.ui.tools.internal.paperclips.PrintIterator;
+import org.eclipse.sirius.table.ui.tools.internal.paperclips.PrintPiece;
+import org.eclipse.sirius.table.ui.tools.internal.paperclips.internal.piece.EmptyPiece;
+import org.eclipse.sirius.table.ui.tools.internal.paperclips.internal.util.ResourcePool;
+import org.eclipse.sirius.table.ui.tools.internal.paperclips.internal.util.Util;
+import org.eclipse.swt.graphics.Device;
+import org.eclipse.swt.graphics.Font;
+import org.eclipse.swt.graphics.FontData;
+import org.eclipse.swt.graphics.GC;
+import org.eclipse.swt.graphics.Point;
+
+/**
+ * A class for adding line breaks corresponding to a particular font size.
+ * Currently this class is used internally by StyledTextPrint to implement the
+ * newline() feature.
+ *
+ * @author Matthew Hall
+ */
+public class LineBreakPrint implements Print {
+ final FontData font;
+
+ /**
+ * Constructs a new LineBreakPrint on the given font.
+ *
+ * @param font
+ * the font which determines the height of the line break.
+ */
+ public LineBreakPrint(FontData font) {
+ Util.notNull(font);
+ this.font = font;
+ }
+
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + ((font == null) ? 0 : font.hashCode());
+ return result;
+ }
+
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (obj == null)
+ return false;
+ if (getClass() != obj.getClass())
+ return false;
+ LineBreakPrint other = (LineBreakPrint) obj;
+ if (font == null) {
+ if (other.font != null)
+ return false;
+ } else if (!font.equals(other.font))
+ return false;
+ return true;
+ }
+
+ public PrintIterator iterator(Device device, GC gc) {
+ return new LineBreakIterator(this, device, gc);
+ }
+}
+
+class LineBreakIterator implements PrintIterator {
+ private static final int MIN_HEIGHT = 0;
+ private static final int MIN_WIDTH = 1;
+
+ private final int lineHeight;
+ private boolean hasNext = true;
+
+ LineBreakIterator(LineBreakPrint print, Device device, GC gc) {
+ this(calculateLineHeight(print, device, gc));
+ }
+
+ private LineBreakIterator(int lineHeight) {
+ this.lineHeight = lineHeight;
+ }
+
+ private static int calculateLineHeight(LineBreakPrint print, Device device,
+ GC gc) {
+ Font oldFont = gc.getFont();
+
+ gc.setFont(ResourcePool.forDevice(device).getFont(print.font));
+ int result = gc.getFontMetrics().getHeight();
+
+ gc.setFont(oldFont);
+
+ return result;
+ }
+
+ public Point minimumSize() {
+ return new Point(MIN_WIDTH, MIN_HEIGHT);
+ }
+
+ public Point preferredSize() {
+ return new Point(MIN_WIDTH, lineHeight);
+ }
+
+ public boolean hasNext() {
+ return hasNext;
+ }
+
+ public PrintPiece next(int width, int height) {
+ if (width < MIN_WIDTH || height < MIN_HEIGHT)
+ return null;
+
+ hasNext = false;
+ return new EmptyPiece(new Point(width, Math.min(height, lineHeight)));
+ }
+
+ public PrintIterator copy() {
+ return hasNext ? new LineBreakIterator(lineHeight) : this;
+ }
+} \ No newline at end of file
diff --git a/plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/text/StyledTextPrint.java b/plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/text/StyledTextPrint.java
new file mode 100644
index 0000000000..3d5b81cea9
--- /dev/null
+++ b/plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/text/StyledTextPrint.java
@@ -0,0 +1,295 @@
+/*
+ * Copyright (c) 2007 Matthew Hall 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:
+ * Matthew Hall - initial API and implementation
+ */
+package org.eclipse.sirius.table.ui.tools.internal.paperclips.text;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.eclipse.sirius.table.ui.tools.internal.paperclips.CompositeEntry;
+import org.eclipse.sirius.table.ui.tools.internal.paperclips.CompositePiece;
+import org.eclipse.sirius.table.ui.tools.internal.paperclips.PaperClips;
+import org.eclipse.sirius.table.ui.tools.internal.paperclips.Print;
+import org.eclipse.sirius.table.ui.tools.internal.paperclips.PrintIterator;
+import org.eclipse.sirius.table.ui.tools.internal.paperclips.PrintPiece;
+import org.eclipse.sirius.table.ui.tools.internal.paperclips.internal.util.PrintSizeStrategy;
+import org.eclipse.sirius.table.ui.tools.internal.paperclips.internal.util.Util;
+import org.eclipse.sirius.table.ui.tools.internal.paperclips.text.internal.TextPrintPiece;
+import org.eclipse.swt.graphics.Device;
+import org.eclipse.swt.graphics.GC;
+import org.eclipse.swt.graphics.Point;
+
+/**
+ * A class for printing styled text. Text of varying size and style are aligned
+ * along the baseline.
+ *
+ * @author Matthew Hall
+ */
+public class StyledTextPrint implements Print {
+ private final List elements = new ArrayList();
+ private TextStyle style = new TextStyle();
+
+ /**
+ * Constructs a new StyledTextPrint.
+ */
+ public StyledTextPrint() {
+ }
+
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result
+ + ((elements == null) ? 0 : elements.hashCode());
+ result = prime * result + ((style == null) ? 0 : style.hashCode());
+ return result;
+ }
+
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (obj == null)
+ return false;
+ if (getClass() != obj.getClass())
+ return false;
+ StyledTextPrint other = (StyledTextPrint) obj;
+ if (elements == null) {
+ if (other.elements != null)
+ return false;
+ } else if (!elements.equals(other.elements))
+ return false;
+ if (style == null) {
+ if (other.style != null)
+ return false;
+ } else if (!style.equals(other.style))
+ return false;
+ return true;
+ }
+
+ /**
+ * Sets the text style that will be applied to text added through the
+ * {@link #append(String)}
+ *
+ * @param style
+ * the new text style.
+ * @return this StyledTextPrint, for chaining method calls.
+ */
+ public StyledTextPrint setStyle(TextStyle style) {
+ Util.notNull(style);
+ this.style = style;
+ return this;
+ }
+
+ /**
+ * Appends the given text to the end of the document, using the default
+ * style. This method is equivalent to calling append(text, getStyle()).
+ *
+ * @param text
+ * the text to append.
+ * @return this StyledTextPrint, for chaining method calls.
+ */
+ public StyledTextPrint append(String text) {
+ return append(text, style);
+ }
+
+ /**
+ * Appends the given text to the end of the document, using the given style.
+ *
+ * @param text
+ * the text to append.
+ * @param style
+ * the text style.
+ * @return this StyledTextPrint, for chaining method calls.
+ */
+ public StyledTextPrint append(String text, TextStyle style) {
+ TextPrint textPrint = new TextPrint(text, style);
+ textPrint.setWordSplitting(false);
+ return append(textPrint);
+ }
+
+ /**
+ * Appends a line break to the document. If a line break produces a blank
+ * line, that line will take the height of the font in the default text
+ * style.
+ *
+ * @return this StyledTextPrint, for chaining method calls.
+ */
+ public StyledTextPrint newline() {
+ return append(new LineBreakPrint(style.getFontData()));
+ }
+
+ /**
+ * Appends the given element to the document.
+ *
+ * @param element
+ * the element to append.
+ * @return this StyledTextPrint, for chaining method calls.
+ */
+ public StyledTextPrint append(Print element) {
+ elements.add(element);
+ return this;
+ }
+
+ public PrintIterator iterator(Device device, GC gc) {
+ return new StyledTextIterator((Print[]) elements
+ .toArray(new Print[elements.size()]), device, gc);
+ }
+}
+
+class StyledTextIterator implements PrintIterator {
+ private final PrintIterator[] elements;
+ private final Point minimumSize;
+ private final Point preferredSize;
+
+ private int cursor = 0;
+
+ StyledTextIterator(Print[] elements, Device device, GC gc) {
+ this.elements = new PrintIterator[elements.length];
+ for (int i = 0; i < elements.length; i++)
+ this.elements[i] = elements[i].iterator(device, gc);
+ minimumSize = computeSize(PrintSizeStrategy.MINIMUM);
+ preferredSize = computeSize(PrintSizeStrategy.PREFERRED);
+ }
+
+ private StyledTextIterator(StyledTextIterator that) {
+ elements = new PrintIterator[that.elements.length - that.cursor];
+ minimumSize = that.minimumSize;
+ preferredSize = that.preferredSize;
+ for (int i = 0; i < elements.length; i++)
+ elements[i] = that.elements[that.cursor + i].copy();
+ }
+
+ private Point computeSize(PrintSizeStrategy strategy) {
+ Point result = new Point(0, 0);
+ for (int i = 0; i < elements.length; i++) {
+ Point current = strategy.computeSize(elements[i]);
+ result.x = Math.max(result.x, current.x);
+ result.y = Math.max(result.y, current.y);
+ }
+ return result;
+ }
+
+ public Point minimumSize() {
+ return new Point(minimumSize.x, minimumSize.y);
+ }
+
+ public Point preferredSize() {
+ return new Point(preferredSize.x, preferredSize.y);
+ }
+
+ public boolean hasNext() {
+ advanceCursor();
+ return cursor < elements.length;
+ }
+
+ public PrintPiece next(int width, int height) {
+ if (width < 0 || height < 0)
+ return null;
+
+ int y = 0;
+
+ List rows = new ArrayList();
+ while (y < height) {
+ PrintPiece row = nextRow(width, height - y);
+ if (row == null)
+ break;
+ rows.add(new CompositeEntry(row, new Point(0, y)));
+ y += row.getSize().y;
+ }
+
+ if (rows.size() == 0)
+ return null;
+
+ return new CompositePiece(rows);
+ }
+
+ private PrintPiece nextRow(int width, int height) {
+ int x = 0;
+ int maxAscent = 0;
+ int maxDescent = 0;
+
+ final int backupCursor = cursor;
+ final List backup = new ArrayList();
+
+ List rowElements = new ArrayList();
+ while (hasNext()) { // hasNext advances cursor internally
+ PrintIterator element = elements[cursor];
+ Point preferredSize = element.preferredSize();
+ if (preferredSize.y > height)
+ break;
+
+ PrintIterator elementBackup = element.copy();
+ PrintPiece piece = PaperClips.next(element, width - x,
+ preferredSize.y);
+ if (piece == null)
+ break;
+
+ rowElements.add(piece);
+ backup.add(elementBackup);
+
+ maxAscent = Math.max(maxAscent, getAscent(piece));
+ maxDescent = Math.max(maxDescent, getDescent(piece));
+ if (maxAscent + maxDescent > height) {
+ restoreBackup(backupCursor, backup);
+ return null;
+ }
+
+ if (element.hasNext())
+ break;
+
+ x += piece.getSize().x;
+ }
+
+ return createRowResult(maxAscent, rowElements);
+ }
+
+ private PrintPiece createRowResult(int rowAscent, List rowElements) {
+ if (rowElements.size() == 0)
+ return null;
+
+ List entries = new ArrayList();
+ int x = 0;
+ for (int i = 0; i < rowElements.size(); i++) {
+ PrintPiece piece = (PrintPiece) rowElements.get(i);
+ int ascent = getAscent(piece);
+ entries.add(new CompositeEntry(piece, new Point(x, rowAscent
+ - ascent)));
+ x += piece.getSize().x;
+ }
+
+ return new CompositePiece(entries);
+ }
+
+ private void restoreBackup(final int backupCursor, final List backup) {
+ for (int i = 0; i < backup.size(); i++)
+ elements[backupCursor + i] = (PrintIterator) backup.get(i);
+ cursor = backupCursor;
+ }
+
+ private int getAscent(PrintPiece piece) {
+ if (piece instanceof TextPrintPiece)
+ return ((TextPrintPiece) piece).getAscent();
+ return piece.getSize().y;
+ }
+
+ private int getDescent(PrintPiece piece) {
+ if (piece instanceof TextPrintPiece)
+ return piece.getSize().y - ((TextPrintPiece) piece).getAscent();
+ return 0;
+ }
+
+ private void advanceCursor() {
+ while (cursor < elements.length && !elements[cursor].hasNext())
+ cursor++;
+ }
+
+ public PrintIterator copy() {
+ return new StyledTextIterator(this);
+ }
+} \ No newline at end of file
diff --git a/plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/text/TextPrint.java b/plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/text/TextPrint.java
new file mode 100644
index 0000000000..6c931e90ca
--- /dev/null
+++ b/plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/text/TextPrint.java
@@ -0,0 +1,562 @@
+/*
+ * Copyright (c) 2005 Matthew Hall 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:
+ * Matthew Hall - initial API and implementation
+ */
+package org.eclipse.sirius.table.ui.tools.internal.paperclips.text;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.eclipse.sirius.table.ui.tools.internal.paperclips.PaperClips;
+import org.eclipse.sirius.table.ui.tools.internal.paperclips.Print;
+import org.eclipse.sirius.table.ui.tools.internal.paperclips.PrintIterator;
+import org.eclipse.sirius.table.ui.tools.internal.paperclips.PrintPiece;
+import org.eclipse.sirius.table.ui.tools.internal.paperclips.internal.util.ResourcePool;
+import org.eclipse.sirius.table.ui.tools.internal.paperclips.internal.util.Util;
+import org.eclipse.sirius.table.ui.tools.internal.paperclips.text.internal.TextPiece;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.graphics.Device;
+import org.eclipse.swt.graphics.Font;
+import org.eclipse.swt.graphics.FontData;
+import org.eclipse.swt.graphics.FontMetrics;
+import org.eclipse.swt.graphics.GC;
+import org.eclipse.swt.graphics.Point;
+import org.eclipse.swt.graphics.RGB;
+
+/**
+ * A Print for displaying text.
+ * <p>
+ * TextPrints are never greedy with layout space, even with center- or
+ * right-alignment. (Greedy prints take up all the available space on the page.)
+ * Therefore, when center- or right-alignment is required, it is necessary to
+ * wrap the text in a Print which will enforce the same alignment. Usually this
+ * is a center:default:grow or right:default:grow column in a GridPrint.
+ *
+ * @author Matthew Hall
+ */
+public class TextPrint implements Print {
+ /** The default text for a TextPrint. Value is "". */
+ public static final String DEFAULT_TEXT = ""; //$NON-NLS-1$
+
+ /** The default font data for a TextPrint. Value is device-dependent. */
+ public static final FontData DEFAULT_FONT_DATA = new FontData();
+
+ /** The default alignment for TextPrint. Value is SWT.LEFT. */
+ public static final int DEFAULT_ALIGN = SWT.LEFT;
+
+ private static final TextStyle DEFAULT_STYLE = new TextStyle();
+
+ String text;
+ TextStyle style;
+ boolean wordSplitting;
+
+ /**
+ * Constructs a TextPrint with the default properties.
+ */
+ public TextPrint() {
+ this(DEFAULT_TEXT);
+ }
+
+ /**
+ * Constructs a TextPrint with the given text.
+ *
+ * @param text
+ * the text to print.
+ */
+ public TextPrint(String text) {
+ this(text, DEFAULT_STYLE);
+ }
+
+ /**
+ * Constructs a TextPrint with the given text and font data.
+ *
+ * @param text
+ * the text to print.
+ * @param fontData
+ * the font that will be used to print the text.
+ */
+ public TextPrint(String text, FontData fontData) {
+ this(text, DEFAULT_STYLE.font(fontData));
+ }
+
+ /**
+ * Constructs a TextPrint with the give text and alignment.
+ *
+ * @param text
+ * the text to print.
+ * @param align
+ * the horizontal text alignment. Must be one of {@link SWT#LEFT}
+ * , {@link SWT#CENTER} or {@link SWT#RIGHT}.
+ */
+ public TextPrint(String text, int align) {
+ this(text, DEFAULT_STYLE.align(align));
+ }
+
+ /**
+ * Constructs a TextPrint with the given text, font data, and alignment.
+ *
+ * @param text
+ * the text to print.
+ * @param fontData
+ * the font that will be used to print the text.
+ * @param align
+ * the horizontal text alignment. Must be one of {@link SWT#LEFT}
+ * , {@link SWT#CENTER} or {@link SWT#RIGHT}.
+ */
+ public TextPrint(String text, FontData fontData, int align) {
+ this(text, DEFAULT_STYLE.font(fontData).align(align));
+ }
+
+ /**
+ * Constructs a TextPrint with the given text and style.
+ *
+ * @param text
+ * the text to print.
+ * @param style
+ * the style to apply to the text.
+ */
+ public TextPrint(String text, TextStyle style) {
+ Util.notNull(text, style);
+ this.text = text;
+ this.style = style;
+ this.wordSplitting = true;
+ }
+
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + ((style == null) ? 0 : style.hashCode());
+ result = prime * result + ((text == null) ? 0 : text.hashCode());
+ result = prime * result + (wordSplitting ? 1231 : 1237);
+ return result;
+ }
+
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (obj == null)
+ return false;
+ if (getClass() != obj.getClass())
+ return false;
+ TextPrint other = (TextPrint) obj;
+ if (style == null) {
+ if (other.style != null)
+ return false;
+ } else if (!style.equals(other.style))
+ return false;
+ if (text == null) {
+ if (other.text != null)
+ return false;
+ } else if (!text.equals(other.text))
+ return false;
+ if (wordSplitting != other.wordSplitting)
+ return false;
+ return true;
+ }
+
+ /**
+ * Returns the text that will be printed.
+ *
+ * @return the text that will be printed.
+ */
+ public String getText() {
+ return text;
+ }
+
+ /**
+ * Sets the text that will be printed.
+ *
+ * @param text
+ * the text to print.
+ */
+ public void setText(String text) {
+ Util.notNull(text);
+ this.text = text;
+ }
+
+ /**
+ * Returns the text style.
+ *
+ * @return the text style.
+ */
+ public TextStyle getStyle() {
+ return style;
+ }
+
+ /**
+ * Sets the text style to the argument.
+ *
+ * @param style
+ * the new text style.
+ */
+ public void setStyle(TextStyle style) {
+ Util.notNull(style);
+ this.style = style;
+ }
+
+ /**
+ * Returns the font that will be used to print the text.
+ *
+ * @return the font that will be used to print the text.
+ */
+ public FontData getFontData() {
+ return style.getFontData();
+ }
+
+ /**
+ * Sets the font that will be used to print the text.
+ *
+ * @param fontData
+ * the font that will be used to print the text.
+ */
+ public void setFontData(FontData fontData) {
+ setStyle(style.font(fontData));
+ }
+
+ /**
+ * Returns the horizontal text alignment. Possible values include
+ * {@link SWT#LEFT}, {@link SWT#CENTER} or {@link SWT#RIGHT}.
+ *
+ * @return the horizontal text alignment.
+ */
+ public int getAlignment() {
+ return style.getAlignment();
+ }
+
+ /**
+ * Sets the horizontal text alignment.
+ *
+ * @param alignment
+ * the horizontal text alignment. Must be one of {@link SWT#LEFT}
+ * , {@link SWT#CENTER} or {@link SWT#RIGHT}.
+ */
+ public void setAlignment(int alignment) {
+ setStyle(style.align(alignment));
+ }
+
+ /**
+ * Returns the foreground color. A null value indicates that the foreground
+ * color is inherited.
+ *
+ * @return the foreground color.
+ */
+ public RGB getForeground() {
+ return style.getForeground();
+ }
+
+ /**
+ * Sets the foreground color to the argument.
+ *
+ * @param foreground
+ * the new foreground color. A null value causes the foreground
+ * color to be inherited.
+ */
+ public void setForeground(RGB foreground) {
+ setStyle(style.foreground(foreground));
+ }
+
+ /**
+ * Returns the background color. A null value indicates that the background
+ * is transparent.
+ *
+ * @return the background color.
+ */
+ public RGB getBackground() {
+ return style.getBackground();
+ }
+
+ /**
+ * Sets the background color to the argument.
+ *
+ * @param background
+ * the new background color. A null value causes the background
+ * to be transparent.
+ */
+ public void setBackground(RGB background) {
+ style = style.background(background);
+ }
+
+ /**
+ * Returns the underline flag.
+ *
+ * @return the underline flag.
+ */
+ public boolean getUnderline() {
+ return style.getUnderline();
+ }
+
+ /**
+ * Sets the underline flag to the argument.
+ *
+ * @param underline
+ * the underline flag.
+ */
+ public void setUnderline(boolean underline) {
+ style = style.underline(underline);
+ }
+
+ /**
+ * Returns the strikout flag.
+ *
+ * @return the strikout flag.
+ */
+ public boolean getStrikeout() {
+ return style.getStrikeout();
+ }
+
+ /**
+ * Sets the strikeout flag to the argument.
+ *
+ * @param strikeout
+ * the strikeout flag.
+ */
+ public void setStrikeout(boolean strikeout) {
+ style = style.strikeout(strikeout);
+ }
+
+ /**
+ * Returns whether word splitting is enabled. Default is true.
+ *
+ * @return whether word splitting is enabled.
+ */
+ public boolean getWordSplitting() {
+ return wordSplitting;
+ }
+
+ /**
+ * Sets whether word splitting is enabled.
+ *
+ * @param wordBreaking
+ * whether to allow word splitting.
+ */
+ public void setWordSplitting(boolean wordBreaking) {
+ this.wordSplitting = wordBreaking;
+ }
+
+ public PrintIterator iterator(Device device, GC gc) {
+ return new TextIterator(this, device, gc);
+ }
+}
+
+class TextIterator implements PrintIterator {
+ private final Device device;
+ private final GC gc;
+
+ final String text;
+ final String[] lines;
+ final TextStyle style;
+ final boolean wordSplitting;
+ final Point minimumSize;
+ final Point preferredSize;
+
+ int row;
+ int col;
+
+ TextIterator(TextPrint print, Device device, GC gc) {
+ this.device = device;
+ this.gc = gc;
+
+ this.text = print.text;
+ this.lines = print.text.split("(\r)?\n"); //$NON-NLS-1$
+ this.style = print.style;
+ this.wordSplitting = print.wordSplitting;
+ this.minimumSize = maxExtent(text.split("\\s")); //$NON-NLS-1$
+ this.preferredSize = maxExtent(lines);
+
+ this.row = 0;
+ this.col = 0;
+ }
+
+ TextIterator(TextIterator that) {
+ this.device = that.device;
+ this.gc = that.gc;
+
+ this.text = that.text;
+ this.lines = that.lines;
+ this.style = that.style;
+ this.wordSplitting = that.wordSplitting;
+ this.minimumSize = that.minimumSize;
+ this.preferredSize = that.preferredSize;
+
+ this.row = that.row;
+ this.col = that.col;
+ }
+
+ public boolean hasNext() {
+ return row < lines.length;
+ }
+
+ public PrintPiece next(int width, int height) {
+ if (!hasNext())
+ PaperClips.error("No more content."); //$NON-NLS-1$
+
+ Font oldFont = initGC();
+ PrintPiece result = internalNext(width, height);
+ restoreGC(oldFont);
+
+ return result;
+ }
+
+ private PrintPiece internalNext(int width, int height) {
+ FontMetrics fm = gc.getFontMetrics();
+
+ final int lineHeight = fm.getHeight();
+ if (height < lineHeight)
+ return null;
+
+ final int maxLines = height / lineHeight;
+ String[] nextLines = nextLines(width, maxLines);
+ if (nextLines.length == 0)
+ return null;
+
+ int maxWidth = maxExtent(nextLines).x;
+ Point size = new Point(maxWidth, nextLines.length * lineHeight);
+ int ascent = fm.getAscent() + fm.getLeading();
+
+ return new TextPiece(device, style, nextLines, size, ascent);
+ }
+
+ private Font initGC() {
+ Font oldFont = gc.getFont();
+ FontData fontData = style.getFontData();
+ if (fontData != null)
+ gc.setFont(ResourcePool.forDevice(device).getFont(fontData));
+ return oldFont;
+ }
+
+ private void restoreGC(Font oldFont) {
+ gc.setFont(oldFont);
+ }
+
+ private String[] nextLines(final int width, final int maxLines) {
+ List nextLines = new ArrayList(Math.min(lines.length, maxLines));
+
+ while ((nextLines.size() < maxLines) && (row < lines.length)) {
+ String line = lines[row].substring(col);
+
+ // Find out how much text will fit on one line.
+ int charCount = findLineBreak(gc, line, width);
+
+ // If none of the text could fit in the current line, terminate this
+ // iteration.
+ if (line.length() > 0 && charCount == 0)
+ break;
+
+ // Get the text that fits on this line.
+ String thisLine = line.substring(0, charCount);
+ nextLines.add(thisLine);
+
+ // Move cursor past the text we just consumed.
+ col += charCount;
+
+ skipWhitespace();
+
+ advanceToNextRowIfCurrentRowCompleted();
+ }
+
+ return (String[]) nextLines.toArray(new String[nextLines.size()]);
+ }
+
+ private void skipWhitespace() {
+ while (col < lines[row].length()
+ && Character.isWhitespace(lines[row].charAt(col)))
+ col++;
+ }
+
+ private void advanceToNextRowIfCurrentRowCompleted() {
+ if (col >= lines[row].length()) {
+ row++;
+ col = 0;
+ }
+ }
+
+ public Point minimumSize() {
+ return new Point(minimumSize.x, minimumSize.y);
+ }
+
+ public Point preferredSize() {
+ return new Point(preferredSize.x, preferredSize.y);
+ }
+
+ private Point maxExtent(String[] text) {
+ Font oldFont = gc.getFont();
+ try {
+ initGC();
+
+ FontMetrics fm = gc.getFontMetrics();
+ int maxWidth = 0;
+
+ for (int i = 0; i < text.length; i++) {
+ String textPiece = text[i];
+ maxWidth = Math.max(maxWidth, gc.stringExtent(textPiece).x);
+ }
+
+ return new Point(maxWidth, fm.getHeight());
+ } finally {
+ restoreGC(oldFont);
+ }
+ }
+
+ private int findLineBreak(GC gc, String text, int width) {
+ // Offsets within the string
+ int loIndex = 0;
+ int hiIndex = text.length();
+
+ // Pixel width of entire string
+ int pixelWidth = gc.stringExtent(text).x;
+
+ // Does the whole string fit?
+ if (pixelWidth <= width)
+ // I'll take it
+ return hiIndex;
+
+ // Do a binary search to find the maximum characters that will fit
+ // within the given width.
+ while (loIndex < hiIndex) {
+ int midIndex = (loIndex + hiIndex + 1) / 2;
+ int midWidth = gc.stringExtent(text.substring(0, midIndex)).x;
+
+ if (midWidth < width)
+ // don't add 1, the next character could make it too big
+ loIndex = midIndex;
+ else if (midWidth > width)
+ // subtract 1, we already know midIndex makes it too big
+ hiIndex = midIndex - 1;
+ else {
+ // perfect fit
+ loIndex = hiIndex = midIndex;
+ }
+ }
+
+ return findWordBreak(text, loIndex);
+ }
+
+ int findWordBreak(String text, int maxLength) {
+ // If the max length is the string length, no break
+ // (we mainly check this to avoid an exception in for-loop)
+ if (maxLength == text.length())
+ return maxLength;
+
+ // Otherwise, break string at the last whitespace at or before
+ // maxLength.
+ for (int i = maxLength; i >= 0; i--)
+ if (Character.isWhitespace(text.charAt(i)))
+ return i;
+
+ // No whitespace? Break at max length (if word breaking is allowed)
+ if (wordSplitting)
+ return maxLength;
+
+ return 0;
+ }
+
+ public PrintIterator copy() {
+ return new TextIterator(this);
+ }
+} \ No newline at end of file
diff --git a/plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/text/TextStyle.java b/plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/text/TextStyle.java
new file mode 100644
index 0000000000..d655d2e344
--- /dev/null
+++ b/plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/text/TextStyle.java
@@ -0,0 +1,416 @@
+/*
+ * Copyright (c) 2007 Matthew Hall 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:
+ * Matthew Hall - initial API and implementation
+ */
+package org.eclipse.sirius.table.ui.tools.internal.paperclips.text;
+
+import org.eclipse.sirius.table.ui.tools.internal.paperclips.internal.util.PaperClipsUtil;
+import org.eclipse.sirius.table.ui.tools.internal.paperclips.internal.util.SWTUtil;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.graphics.FontData;
+import org.eclipse.swt.graphics.RGB;
+
+/**
+ * Defines a set of styles that can be applied to text. Instances of this class
+ * are immutable.
+ *
+ * @author Matthew Hall
+ */
+public class TextStyle {
+ private FontData fontData;
+ private RGB foreground;
+ private RGB background;
+ private int alignment;
+ private boolean underline;
+ private boolean strikeout;
+
+ /**
+ * Constructs a new TextStyle with default font (device-dependent), black
+ * foreground, transparent background, default alignment, and the strikeout
+ * and underline flags set to false.
+ */
+ public TextStyle() {
+ fontData = SWTUtil.copy(TextPrint.DEFAULT_FONT_DATA);
+ foreground = new RGB(0, 0, 0);
+ background = null;
+ alignment = TextPrint.DEFAULT_ALIGN;
+ underline = false;
+ strikeout = false;
+ }
+
+ private TextStyle(TextStyle that) {
+ this.fontData = that.fontData;
+ this.foreground = that.foreground;
+ this.background = that.background;
+ this.alignment = that.alignment;
+ this.underline = that.underline;
+ this.strikeout = that.strikeout;
+ }
+
+ private TextStyle internalFont(FontData fontData) {
+ TextStyle result = new TextStyle(this);
+ result.fontData = fontData;
+ return result;
+ }
+
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + alignment;
+ result = prime * result
+ + ((background == null) ? 0 : background.hashCode());
+ result = prime * result
+ + ((fontData == null) ? 0 : fontData.hashCode());
+ result = prime * result
+ + ((foreground == null) ? 0 : foreground.hashCode());
+ result = prime * result + (strikeout ? 1231 : 1237);
+ result = prime * result + (underline ? 1231 : 1237);
+ return result;
+ }
+
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (obj == null)
+ return false;
+ if (getClass() != obj.getClass())
+ return false;
+ TextStyle other = (TextStyle) obj;
+ if (alignment != other.alignment)
+ return false;
+ if (background == null) {
+ if (other.background != null)
+ return false;
+ } else if (!background.equals(other.background))
+ return false;
+ if (fontData == null) {
+ if (other.fontData != null)
+ return false;
+ } else if (!fontData.equals(other.fontData))
+ return false;
+ if (foreground == null) {
+ if (other.foreground != null)
+ return false;
+ } else if (!foreground.equals(other.foreground))
+ return false;
+ if (strikeout != other.strikeout)
+ return false;
+ if (underline != other.underline)
+ return false;
+ return true;
+ }
+
+ /**
+ * Returns a copy of this TextStyle, with the font changed to the font
+ * described by the arguments. This method is equivalent to calling font(
+ * new FontData( name, height, style ) ).
+ *
+ * @param name
+ * the name of the font (must not be null)
+ * @param height
+ * the font height in points
+ * @param style
+ * a bit or combination of NORMAL, BOLD, ITALIC
+ * @return a copy of this TextStyle, with the font changed to the font
+ * described by the arguments.
+ */
+ public TextStyle font(String name, int height, int style) {
+ return internalFont(new FontData(name, height, style));
+ }
+
+ /**
+ * Returns a copy of this TextStyle, with the font changed to the argument.
+ *
+ * @param fontData
+ * the new font. A null value causes the font to be inherited
+ * from the enclosing elements of the document.
+ * @return a copy of this TextStyle, with the font changed to the argument.
+ */
+ public TextStyle font(FontData fontData) {
+ return internalFont(SWTUtil.copy(fontData));
+ }
+
+ /**
+ * Returns a copy of this TextStyle, with the font name changed to the
+ * argument.
+ *
+ * @param name
+ * the new font name (must not be null)
+ * @return a copy of this TextStyle, with the font name changed to the
+ * argument.
+ */
+ public TextStyle fontName(String name) {
+ return font(name, fontData.getHeight(), fontData.getStyle());
+ }
+
+ /**
+ * Returns a copy of this TextStyle, with the font height changed to the
+ * argument.
+ *
+ * @param height
+ * the new font height in points
+ * @return a copy of this TextStyle, with the font height changed to the
+ * argument.
+ */
+ public TextStyle fontHeight(int height) {
+ return font(fontData.getName(), height, fontData.getStyle());
+ }
+
+ /**
+ * Returns a copy of this TextStyle, with the font style changed to the
+ * argument.
+ *
+ * @param style
+ * a bit or combination of NORMAL, BOLD, ITALIC
+ * @return a copy of this TextStyle, with the font style changed to the
+ * argument.
+ */
+ public TextStyle fontStyle(int style) {
+ return font(fontData.getName(), fontData.getHeight(), style);
+ }
+
+ private TextStyle internalForeground(RGB foreground) {
+ TextStyle result = new TextStyle(this);
+ result.foreground = foreground;
+ return result;
+ }
+
+ /**
+ * Returns a copy of this TextStyle, with the foreground changed to the
+ * argument.
+ *
+ * @param foreground
+ * the new foreground. A null value causes the foreground to be
+ * inherited from the enclosing elements of the document.
+ * @return a copy of this TextStyle, with the foreground changed to the
+ * argument.
+ */
+ public TextStyle foreground(RGB foreground) {
+ return internalForeground(SWTUtil.copy(foreground));
+ }
+
+ /**
+ * Returns a copy of this TextStyle, with the foreground changed to the
+ * color described by the arguments. This method is equivalent to calling
+ * foreground(new RGB(red, green, blue)).
+ *
+ * @param red
+ * the red component of the new foreground color
+ * @param green
+ * the green component of the new foreground color
+ * @param blue
+ * the blue component of the new foreground color
+ * @return a copy of this TextStyle, with the foreground changed to the
+ * color described by the arguments.
+ */
+ public TextStyle foreground(int red, int green, int blue) {
+ return internalForeground(new RGB(red, green, blue));
+ }
+
+ /**
+ * Returns a copy of this TextStyle, with the foreground changed to the
+ * color described by the argument.
+ *
+ * @param rgb
+ * an integer containing the red, green and blue components in
+ * the 0xFF0000, 0x00FF00, and 0x0000FF positions, respectively.
+ * @return a copy of this TextStyle, with the foreground changed to the
+ * color described by the argument.
+ */
+ public TextStyle foreground(int rgb) {
+ return internalForeground(SWTUtil.deriveRGB(rgb));
+ }
+
+ private TextStyle internalBackground(RGB background) {
+ TextStyle result = new TextStyle(this);
+ result.background = background;
+ return result;
+ }
+
+ /**
+ * Returns a copy of this TextStyle, with the background changed to the
+ * argument.
+ *
+ * @param background
+ * the new background. A null value causes the text background to
+ * be transparent.
+ * @return a copy of this TextStyle, with the background changed to the
+ * argument.
+ */
+ public TextStyle background(RGB background) {
+ return internalBackground(SWTUtil.copy(background));
+ }
+
+ /**
+ * Returns a copy of this TextStyle, with the background changed to the
+ * color described by the arguments. This method is equivalent to calling
+ * background(new RGB(red, green, blue)
+ *
+ * @param red
+ * the red component of the new background color
+ * @param green
+ * the green component of the new background color
+ * @param blue
+ * the blue component of the new background color
+ * @return a copy of this TextStyle, with the background changed to the
+ * color described by the arguments.
+ */
+ public TextStyle background(int red, int green, int blue) {
+ return internalBackground(new RGB(red, green, blue));
+ }
+
+ /**
+ * Returns a copy of this TextStyle, with the background changed to the
+ * color described by the argument.
+ *
+ * @param rgb
+ * an integer containing the red, green and blue components in
+ * the 0xFF0000, 0x00FF00, and 0x0000FF positions, respectively.
+ * @return a copy of this TextStyle, with the background changed to the
+ * color described by the argument.
+ */
+ public TextStyle background(int rgb) {
+ return internalBackground(SWTUtil.deriveRGB(rgb));
+ }
+
+ /**
+ * Returns a copy of this TextStyle, with the alignment changed to the
+ * argument.
+ *
+ * @param alignment
+ * the new alignment. Must be one of SWT.LEFT, SWT.CENTER, or
+ * SWT.RIGHT. Invalid values will be changed to SWT.LEFT.
+ * @return a copy of this TextStyle, with the alignment changed to the
+ * argument.
+ */
+ public TextStyle align(int alignment) {
+ TextStyle result = new TextStyle(this);
+ result.alignment = PaperClipsUtil.firstMatch(alignment, new int[] {
+ SWT.LEFT, SWT.CENTER, SWT.RIGHT }, SWT.LEFT);
+ return result;
+ }
+
+ /**
+ * Returns a copy of this TextStyle, with the underline flag set to true.
+ *
+ * @return a copy of this TextStyle, with the underline flag set to true.
+ */
+ public TextStyle underline() {
+ return underline(true);
+ }
+
+ /**
+ * Returns a copy of this TextStyle, with the underline flag set to the
+ * argument.
+ *
+ * @param underline
+ * the new underline flag.
+ * @return a copy of this TextStyle, with the underline flag set to the
+ * argument.
+ */
+ public TextStyle underline(boolean underline) {
+ TextStyle result = new TextStyle(this);
+ result.underline = underline;
+ return result;
+ }
+
+ /**
+ * Returns a copy of this TextStyle, with the strikeout flag set to true.
+ *
+ * @return a copy of this TextStyle, with the strikeout flag set to true.
+ */
+ public TextStyle strikeout() {
+ return strikeout(true);
+ }
+
+ /**
+ * Returns a copy of this TextStyle, with the strikeout flag set to the
+ * argument.
+ *
+ * @param strikeout
+ * the new strikeout flag.
+ * @return a copy of this TextStyle, with the strikeout flag set to the
+ * argument.
+ */
+ public TextStyle strikeout(boolean strikeout) {
+ TextStyle result = new TextStyle(this);
+ result.strikeout = strikeout;
+ return result;
+ }
+
+ /**
+ * Returns the font applied to the text.
+ *
+ * @return the font applied to the text.
+ */
+ public FontData getFontData() {
+ return SWTUtil.copy(fontData);
+ }
+
+ /**
+ * Returns the text foreground color. A null value indicates that the
+ * foreground color will be inherited from the enclosing elements of the
+ * document.
+ *
+ * @return the text foreground color.
+ */
+ public RGB getForeground() {
+ return SWTUtil.copy(foreground);
+ }
+
+ /**
+ * Returns the text background color. A null value indicates that the
+ * background will be transparent.
+ *
+ * @return the text background color. A null value indicates that the
+ * background will be transparent.
+ */
+ public RGB getBackground() {
+ return SWTUtil.copy(background);
+ }
+
+ /**
+ * Returns the text alignment. Possible values include SWT.LEFT, SWT.CENTER,
+ * or SWT.RIGHT.
+ *
+ * @return the text alignment.
+ */
+ public int getAlignment() {
+ return alignment;
+ }
+
+ /**
+ * Returns the underline flag.
+ *
+ * @return the underline flag.
+ */
+ public boolean getUnderline() {
+ return underline;
+ }
+
+ /**
+ * Returns the strikeout flag.
+ *
+ * @return the strikeout flag.
+ */
+ public boolean getStrikeout() {
+ return strikeout;
+ }
+
+ /**
+ * Returns a TextPrint of the given text in this text style
+ *
+ * @param text
+ * the text
+ * @return a TextPrint of the given text in this text style
+ */
+ public TextPrint create(String text) {
+ return new TextPrint(text, this);
+ }
+} \ No newline at end of file
diff --git a/plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/text/internal/TextPiece.java b/plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/text/internal/TextPiece.java
new file mode 100644
index 0000000000..b584423bbc
--- /dev/null
+++ b/plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/text/internal/TextPiece.java
@@ -0,0 +1,140 @@
+/*
+ * Copyright (c) 2005 Matthew Hall 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:
+ * Matthew Hall - initial API and implementation
+ */
+package org.eclipse.sirius.table.ui.tools.internal.paperclips.text.internal;
+
+import org.eclipse.sirius.table.ui.tools.internal.paperclips.internal.util.ResourcePool;
+import org.eclipse.sirius.table.ui.tools.internal.paperclips.internal.util.Util;
+import org.eclipse.sirius.table.ui.tools.internal.paperclips.text.TextStyle;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.graphics.Color;
+import org.eclipse.swt.graphics.Device;
+import org.eclipse.swt.graphics.Font;
+import org.eclipse.swt.graphics.FontMetrics;
+import org.eclipse.swt.graphics.GC;
+import org.eclipse.swt.graphics.Point;
+
+public class TextPiece implements TextPrintPiece {
+ private final Point size;
+ private final String[] lines;
+ private final TextStyle style;
+ private final int ascent;
+
+ private final ResourcePool resources;
+
+ public TextPiece(Device device, TextStyle style, String[] text, Point size,
+ int ascent) {
+ Util.notNull(device, size, style);
+ Util.noNulls(text);
+ this.size = size;
+ this.lines = text;
+ this.style = style;
+ this.ascent = ascent;
+
+ this.resources = ResourcePool.forDevice(device);
+ }
+
+ public Point getSize() {
+ return new Point(size.x, size.y);
+ }
+
+ public int getAscent() {
+ return ascent;
+ }
+
+ public void paint(final GC gc, final int x, final int y) {
+ Font oldFont = gc.getFont();
+ Color oldForeground = gc.getForeground();
+ Color oldBackground = gc.getBackground();
+
+ final int width = getSize().x;
+ final int align = style.getAlignment();
+
+ try {
+ boolean transparent = initGC(gc);
+
+ FontMetrics fm = gc.getFontMetrics();
+ int lineHeight = fm.getHeight();
+
+ boolean strikeout = style.getStrikeout();
+ boolean underline = style.getUnderline();
+ int lineThickness = Math.max(1, fm.getDescent() / 3);
+ int strikeoutOffset = fm.getLeading() + fm.getAscent() / 2;
+ int underlineOffset = ascent + lineThickness;
+
+ for (int i = 0; i < lines.length; i++) {
+ String line = lines[i];
+ int lineWidth = gc.stringExtent(line).x;
+ int offset = getHorzAlignmentOffset(align, lineWidth, width);
+
+ gc.drawString(lines[i], x + offset, y + lineHeight * i,
+ transparent);
+ if (strikeout || underline) {
+ Color saveBackground = gc.getBackground();
+ gc.setBackground(gc.getForeground());
+ if (strikeout)
+ gc.fillRectangle(x + offset, y + lineHeight * i
+ + strikeoutOffset, lineWidth, lineThickness);
+ if (underline)
+ gc.fillRectangle(x + offset, y + lineHeight * i
+ + underlineOffset, lineWidth, lineThickness);
+ gc.setBackground(saveBackground);
+ }
+ }
+ } finally {
+ restoreGC(gc, oldFont, oldForeground, oldBackground);
+ }
+ }
+
+ private boolean initGC(final GC gc) {
+ initGCFont(gc);
+ initGCForeground(gc);
+ boolean transparent = initGCBackground(gc);
+ return transparent;
+ }
+
+ private void restoreGC(final GC gc, Font font, Color foreground,
+ Color background) {
+ gc.setFont(font);
+ gc.setForeground(foreground);
+ gc.setBackground(background);
+ }
+
+ private int getHorzAlignmentOffset(int align, int lineWidth, int totalWidth) {
+ if (align == SWT.CENTER)
+ return (totalWidth - lineWidth) / 2;
+ else if (align == SWT.RIGHT)
+ return totalWidth - lineWidth;
+ return 0;
+ }
+
+ private boolean initGCBackground(GC gc) {
+ Color background = resources.getColor(style.getBackground());
+ boolean transparent = (background == null);
+ if (!transparent)
+ gc.setBackground(background);
+ return transparent;
+ }
+
+ private void initGCForeground(GC gc) {
+ Color foreground = resources.getColor(style.getForeground());
+ if (foreground != null)
+ gc.setForeground(foreground);
+ }
+
+ private void initGCFont(GC gc) {
+ Font font = resources.getFont(style.getFontData());
+ if (font != null)
+ gc.setFont(font);
+ }
+
+ public void dispose() {
+ } // Shared resources, nothing to dispose.
+} \ No newline at end of file
diff --git a/plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/text/internal/TextPrintPiece.java b/plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/text/internal/TextPrintPiece.java
new file mode 100644
index 0000000000..74cc0b08f4
--- /dev/null
+++ b/plugins/org.eclipse.sirius.table.ui/src/org/eclipse/sirius/table/ui/tools/internal/paperclips/text/internal/TextPrintPiece.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright (c) 2007 Matthew Hall 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:
+ * Matthew Hall - initial API and implementation
+ */
+package org.eclipse.sirius.table.ui.tools.internal.paperclips.text.internal;
+
+import org.eclipse.sirius.table.ui.tools.internal.paperclips.PrintPiece;
+
+public interface TextPrintPiece extends PrintPiece {
+ /**
+ * Returns the ascent of the first line of text, in pixels.
+ *
+ * @return the ascent of the first line of text, in pixels.
+ */
+ public int getAscent();
+}

Back to the top