Issue Details (XML | Word | Printable)

Key: NTBL-392
Type: Technical Story Technical Story
Status: Closed Closed
Resolution: Fixed
Priority: Major Major
Assignee: admin
Reporter: baljeet.sandhu
Votes: 0
Watchers: 0
Operations

If you were logged in you would be able to see more operations.
NatTable

Spike the approach to priting Nattable.

Created: 20/May/09 09:37 PM   Updated: 02/Jun/09 01:50 PM   Resolved: 22/May/09 06:08 PM
Component/s: None
Affects Version/s: None
Fix Version/s: 2.0

Time Tracking:
Not Specified

Issue Links:
Require
 

Acceptance Criteria:
Provide a concrete answer to the tools/implementation approach suitable for meeting NatTable's printing requirements.

NOTE: Actual feature implementation is not a part of this story.
Test coverage: Not required.


 Description  « Hide

See acceptance criteria.

Additional information:

Andy commented on the original issue:
Can take a look of this project
http://paperclips.sourceforge.net/

The paperclips project is now hosted at: http://code.google.com/p/swt-paperclips/




As we already needed this a while back I have an implementation using PaperClips, I offer you the code I wrote. I'm not including all classes we use here, just the raw print class, I think you can figure out the missing methods etc from our wrapping "GlazedNatGridViewer", otherwise let me know.

This code also deals with printing the grid exactly as is, which means that colum widths in printed doc are same as in the table. This can be removed of course if not wanted.

Hope it's useful, I'm posting the code as it'd be nice to have it inside Nat, assuming it's still customizable enough for users to add their own tweaks.. let me know!

GridPrinter.java
public class GridPrinter {

    private static final String EXPANDABLE_COLUMN      = "d:g";
    private static final String DOT                    = "...";
    private static final String EMPTY                  = "";

    public static final int     PRINT_OK               = 0;
    public static final int     ERROR_NO_DATA_TO_PRINT = 1;
    public static final int     ERROR_PRINTING         = 2;

    // custom print images get disposed when we're finished or we'll have a memory leak
    private Image               _DisposeImage;
    private boolean             _DisposePrintImage     = false;

    private RGB                 _EvenRows;
    private RGB                 _OddRows;
    private RGB                 _HeaderBgColor;

    private boolean             _PrintColHeaders;
    //private boolean             _PrintRowHeaders;              // unused right now, hence eclipse saying "yellow"
    private boolean             _PrintVerticalLines;
    private boolean             _PrintHorizontalLines;

    private String              _footerText;

    private FontData            _headerFontData;
    private FontData            _cellFontData;

    private IPrintHandler       _printHandler;
    
    public GridPrinter(IPrintHandler handler) {
        _printHandler = handler;
        _PrintColHeaders = handler.isPrintColumnHeaders();
        //_PrintRowHeaders = handler.isPrintRowHeaders();
        _PrintVerticalLines = handler.isPrintVerticalLines();
        _PrintHorizontalLines = handler.isPrintHorizontalLines();
        _footerText = handler.getFooterText();
        _headerFontData = handler.getHeaderFontData();
        _cellFontData = handler.getCellFontData();

        _HeaderBgColor = new RGB(240, 240, 240);
        _OddRows = new RGB(230, 230, 230);
        _EvenRows = new RGB(255, 255, 255);
    }

    public int printNatTable(GlazedNatGridViewer viewer, boolean printSelectionOnly, boolean showPreview, String docTitle, final SmallProgressWindow spw, final Shell parentShell) {
        try {
            int columnCount = viewer.getColumnCount();

            boolean useFullColumnWidths = false;

            StringBuffer buf = new StringBuffer();

            List<Integer> cols = null;
            List<Integer> widths = new ArrayList<Integer>();
            Map<Integer, Integer> widthMap = new HashMap<Integer, Integer>();


            // figure out size of grid (how many columns it has)
            if (printSelectionOnly) {
                if (viewer.getCellSelectionCount() == 0) { return ERROR_NO_DATA_TO_PRINT; }

                Point[] points = viewer.getCellSelection();

                cols = new ArrayList<Integer>();
                for (Point p : points) {
                    if (!cols.contains(p.x)) {
                        cols.add(p.x);
                    }
                }

                columnCount = cols.size();
            } else {
                cols = viewer.getVisibleColumnOrder();
                columnCount = cols.size();
            }

            // order columns that have selection in same order as table order
            // we do this as we can have a selection like this:
            //
            // - 0 1 2 3 4
            // 0 x x x   x
            // 1 x   x x 
            // 2 x x x   x
            //
            // and if we didn't know 3 came before 4 even though it doesn't have a selection
            // until row #2 we'd have an order of 0 1 2 4 3 instead of the correct 0 1 2 3 4

            // get the full order regardless of column visibility, we need the real order
            List<Integer> visibleOrder = viewer.getColumnOrder();
            List<Integer> correctOrderedCols = new ArrayList<Integer>();
            for (Integer correct : visibleOrder) {
                if (cols.contains(correct)) {
                    correctOrderedCols.add(correct);
                }
            }
            cols = correctOrderedCols;

            // create grid
            for (int i = 0; i < cols.size(); i++) {
                int column = cols.get(i);
                widths.add(viewer.getModel().getBodyColumnWidth(column));
                widthMap.put(column, viewer.getModel().getBodyColumnWidth(column));
                if (useFullColumnWidths) {
                    buf.append(EXPANDABLE_COLUMN);
                } else {
                    int colWidthPix = viewer.getModel().getBodyColumnWidth(column);
                    float pts = convertToPoints(colWidthPix);
                    buf.append("L:");
                    buf.append((int) pts);
                    buf.append(":N");
                }

                if (i != columnCount - 1) {
                    buf.append(", ");
                }
            }

            // create the printable grid
            GridPrint print = null;

            GC concatGc = new GC(parentShell);
            Font headerFont = new Font(Display.getDefault(), _headerFontData);
            Font cellFont = new Font(Display.getDefault(), _cellFontData);

            DefaultGridLook look = new DefaultGridLook(0, 0);
            if (_PrintHorizontalLines && _PrintVerticalLines) {
                look.setCellBorder(new LineBorder());
            } else if (_PrintHorizontalLines) {
                look.setCellBorder(new CustomLineBorder(true));
            } else if (_PrintVerticalLines) {
                look.setCellBorder(new CustomLineBorder(false));
            }

            print = new GridPrint(buf.toString(), look);

            look.setHeaderBackgroundProvider(new CellBackgroundProvider() {
                @Override
                public RGB getCellBackground(int row, int column, int colspan) {
                    return _HeaderBgColor;
                }
            });

            // Alternate between light yellow and light blue every 5 rows
            look.setBodyBackgroundProvider(new CellBackgroundProvider() {
                @Override
                public RGB getCellBackground(int row, int col, int colspan) {
                    return row % 2 == 0 ? _EvenRows : _OddRows;
                }
            });

            // fill the grid
            if (printSelectionOnly) {
                Point[] points = viewer.getCellSelection();
                spw.setMax(points.length * 2);

                if (_PrintColHeaders) {
                    for (int i = 0; i < cols.size(); i++) {
                        String name = concat(viewer.getColumn(cols.get(i)).getName(), widths.get(i), concatGc, headerFont);
                        print.addHeader(new TextPrint(name, _headerFontData));
                    }
                }

                // make a map out of what is selected on each row
                Map<Integer, List<Integer>> selMap = new HashMap<Integer, List<Integer>>();

                for (int i = 0; i < points.length; i++) {
                    spw.increase();
                    Point p = points[i];

                    boolean newEntry = !selMap.containsKey(p.y);
                    List<Integer> toUse = newEntry ? new ArrayList<Integer>() : selMap.get(p.y);

                    if (!toUse.contains(p.x)) {
                        toUse.add(p.x);
                    }

                    selMap.put(p.y, toUse);
                }

                List<Integer> printedRows = new ArrayList<Integer>();

                // fill in the cells
                for (int i = 0; i < points.length; i++) {
                    spw.increase();
                    Point p = points[i];

                    if (printedRows.contains(p.y)) {
                        continue;
                    }

                    for (int col : cols) {
                        // this cell is not selected, but we need to print an empty cell
                        // or alignment gets completely crazy later
                        List<Integer> selColsForRow = selMap.get(p.y);
                        if (!selColsForRow.contains(col)) {
                            print.add(tp(" "));
                            continue;
                        }

                        IDataObject gi = viewer.getItem(p.y);
                        String value = concat(gi.getColumnText(col), widthMap.get(col), concatGc, cellFont);
                        Image image = gi.getColumnImage(col);
                        if (image != null) {
                            print.add(new ImagePrint(image.getImageData(), Display.getDefault().getDPI()));
                        }
                        else {
                            print.add(tp(value, _cellFontData));
                        }
                    }

                    printedRows.add(p.y);
                }
            } else {
                List<IDataObject> all = viewer.getAllItems();
                spw.setMax(all.size());

                IDataObject[] sel = new IDataObject[all.size()];
                sel = new IDataObject[all.size()];
                for (int i = 0; i < all.size(); i++)
                    sel[i] = all.get(i);

                if (_PrintColHeaders) {
                    for (int i = 0; i < cols.size(); i++) {
                        String name = concat(viewer.getColumn(cols.get(i)).getName(), widths.get(i), concatGc, headerFont);
                        print.addHeader(new TextPrint(name, _headerFontData));
                    }
                }

                for (IDataObject gi : sel) {
                    spw.increase();
                    for (int i = 0; i < cols.size(); i++) {
                        String colText = gi.getColumnText(cols.get(i));
                        if (colText == null) {
                            colText = "";
                        }
                        String text = concat(colText, widths.get(i), concatGc, cellFont);
                        print.add(tp(text, _cellFontData));
                    }
                }
            }

            // dispose resources
            concatGc.dispose();
            headerFont.dispose();
            cellFont.dispose();

            // we need to space things out, it's too tight usually, so we create a wrapper to
            // force the printout over multiple pages when it doesn't fit
            BigPrint big = new BigPrint(print);

            // add header, footer, spacing
            PagePrint pp = new PagePrint(new PrintHeader(_printHandler, docTitle), big, new PrintFooter(getFooterText()));
            pp.setHeaderGap(9);
            pp.setFooterGap(18);

            if (showPreview) {
                spw.setDescription("Creating Preview...");
                // show preview dialog, user prints from there
                PrintPreviewDialog ppd = new PrintPreviewDialog(pp, parentShell, _printHandler);
                ppd.open();
                spw.dispose();
            } else {
                spw.setDescription("Creating Print...");
                spw.dispose();
                PrintDialog dialog = new PrintDialog(parentShell, SWT.NONE);
                PrinterData printerData = dialog.open();

                if (printerData != null) {
                    PaperClips.print(new PrintJob("Printing", pp).setMargins(36), printerData);
                }
            }

            if (_DisposePrintImage) {
                _DisposeImage.dispose();
            }

            return PRINT_OK;
        } catch (Exception err) {
            err.printStackTrace();
        }

        return ERROR_PRINTING;
    }

    // checks whether text needs concatenation or not
    private String concat(String str, int pixWidth, GC gc, Font f) {
        if (str == null || str.length() == 0) {
            return str;
        }
        
        gc.setFont(f);
        Point size = gc.stringExtent(str);
        // text fits, dispose & return
        if (size.x <= pixWidth) {
            return str;
        } else {
            // text doesn't fit, concatenate with ... and return
            String ret = getAvailableTextToDisplay(gc, new Rectangle(0, 0, pixWidth, 20), str);
            return ret;
        }
    }

    private String getAvailableTextToDisplay(final GC gc, final Rectangle rectangle, final String text) {
        int width = gc.textExtent(text).x;
        boolean displayDot = width > rectangle.width;

        String ret = null;

        try {
            if (displayDot) {
                BufferedReader bufferedReader = new BufferedReader(new StringReader(text));
                StringBuffer output = new StringBuffer();
                String line = "";
                while ((line = bufferedReader.readLine()) != null) {
                    width = gc.textExtent(line).x;
                    if (width > rectangle.width) {
                        int textLen = line.length();
                        for (int i = textLen - 1; i >= 0; i--) {
                            String temp = line.substring(0, i) + DOT;
                            width = gc.textExtent(temp).x;
                            if (width < rectangle.width) {
                                line = temp;
                                break;
                            } else if (i == 0) {
                                line = EMPTY;
                            }
                        }
                    }
                    output.append(line);
                    output.append('\n');
                }
                ret = output.toString();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }

        return ret;
    }

    private int convertToPoints(int pixels) {
        return 72 * pixels / Display.getDefault().getDPI().x;
    }

    private TextPrint tp(String str) {
        return new TextPrint(str == null ? "" : (str.equals("") ? "-" : str));
    }

    private TextPrint tp(String str, FontData fd) {
        return new TextPrint(str == null ? "" : (str.equals("") ? "-" : str), fd);
    }

    public String getFooterText() {
        return _footerText;
    }
}

Emil


Oh, you might need this too:

CustomLineBorder.java
import net.sf.paperclips.AbstractBorderPainter;
import net.sf.paperclips.Border;
import net.sf.paperclips.BorderPainter;
import net.sf.paperclips.LineBorder;
import net.sf.paperclips.internal.ResourcePool;
import net.sf.paperclips.internal.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;

public class CustomLineBorder implements Border {

    RGB     rgb;
    int     lineWidth   = 1;    // in points
    int     gapSize     = 5;    // in points
    boolean _horizontal = false;

    /**
     * Constructs a LineBorder with a black border and 5-pt insets. (72 pts = 1")
     */
    public CustomLineBorder(boolean horizontal) {
        this(new RGB(0, 0, 0), horizontal); // black
    }

    /**
     * Constructs a LineBorder with 5-pt insets. (72 pts = 1")
     * 
     * @param rgb
     *            the color to use for the border.
     */
    public CustomLineBorder(RGB rgb, boolean horizontal) {
        setRGB(rgb);
        _horizontal = horizontal;
    }

    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.getGapSize()) return false;
        if (lineWidth != other.getLineWidth()) return false;
        if (rgb == null) {
            if (other.getRGB() != null) return false;
        } else if (!rgb.equals(other.getRGB())) 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, _horizontal);
    }
}

class LineBorderPainter extends AbstractBorderPainter {
    private final Device device;
    private final RGB    rgb;
    private final Point  lineWidth;
    private final Point  borderWidth;
    private final boolean _horizontalOnly;

    LineBorderPainter(CustomLineBorder border, Device device, GC gc, boolean horizontalOnly) {
        Util.notNull(border, device, gc);
        this.rgb = border.rgb;
        this.device = device;
        this._horizontalOnly = horizontalOnly;

        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 (vertical)
            if (!_horizontalOnly) {
                gc.fillRectangle(x, y, lineWidth.x, height);
                gc.fillRectangle(x + width - lineWidth.x, y, lineWidth.x, height);
            }

            if (_horizontalOnly) {
                // Top & bottom (horizontal)
                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

}