diff options
| author | Pierre-Charles David | 2015-03-18 09:09:26 +0000 |
|---|---|---|
| committer | Pierre-Charles David | 2015-03-18 09:49:33 +0000 |
| commit | 62d40e3ba733f3f3e5ee408640460d9bdf2ca71c (patch) | |
| tree | e3c54d53aea7beaa9ea4f0c5c3dfae082924d976 | |
| parent | 70a0644d0f65d135d14b7b0712e5c93e3564f71f (diff) | |
| download | org.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>
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 «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.</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") 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") + * </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(); +} |
