GSoC 2011 Tables Mid-term Report
Tables GSoC - Mid-term Report
This is a brief summary report of my work with implementing support for tables in Scribus as part of Summer of Code 2011. The report covers the works up until the mid-term evaluation (July 15) and is divided into two parts: one activity part, where I summarize the things I've done in chronological order and one documentary part, where I've tried to document how what I have now is designed/implemented.
Pre-Project Start / Week 1
Before the project started, I did some experimenting with adding a new table style, to see what work was involved to do that and to familiarize myself a bit with the Scribus code base. This involved the new class TableStyle, and SMTableStyle for StyleManager support.
In the first week of the project I added the new table page item, PageItem_Table, and made sure the new item could be inserted in the same way the old one could.
I then went on and added API for the basic operations of inserting, removing and resizing rows and columns as well as merging of table cells (row/column span). In the process, a helper class CellArea was written for representing areas of merged cells. I also added some very basic painting of the table, to be able to see it while experimenting.
Lastly, I added an initial set of scripter functions that exercised the new API.
The first week was quite a jump start and I overshot my quite modest goals of just doing basic data structures for the tables.
In the second week, coming to like the way of working with the scripter API to test my work, I added a poor mans unit testing "framework" in the form of a Python script that automatically run a set of test methods and reports any regressions.
I also added a new explicitly shared class TableCell for representing cells in the table, along with a cell style similar to the table style, and fixed some bugs.
As I had my last exam this week, the progress wasn't as fast as in the first week, but I was still ahead of schedule by quite a lot.
During the third week I worked with integrating the new TableCell class into PageItem_Table, making sure the table correctly invalidates cells that have been removed and adjusts their rows/columns/spans when rows and columns are inserted and removed.
Other things I worked on include extending the set of test methods in my test script, adding new scripter methods, adding table/cell style related API to ScribusDoc as well as initial support for border width, border color and background color.
It was during this third week that my schedule finally caught up with me, and though my intentions for this week was to start working on proper painting of cell borders, the work was mostly extending and improving what I already had.
In the fourth week of the project, in addition to doing some cleanup of the code, I finally started working on border painting.
I drafted a design for painting of borders involving "border collectors" inheriting from a common base class. These collectors would be responsible for collecting the borders around the table cells according to the currently used border model (collapsing or separated). I also added an primitive border "joining" algorithm that only supported the two modes "horizontal on top" and "vertical on top". I thought the design was sound. But in the end it turned out I was wrong, and a big chunk of the work during this week was dropped.
During this week my project also suffered a bit from bad time management on my part, since me and Hanna had visitors from Singapore coming in, followed by a trip up to a cabin in the northwest to celebrate Midsummer, with little or no Internet connectivity.
Week five saw me taking another approach to the painting problem. The painting code was moved out of PageItem_Table and TableCell and into a new CollapsedTablePainter inheriting from an abstract base class TablePainter. The CollapseTablePainter takes a PageItem_Table and an ScPainter and is entirely responsible for painting the table. If in the future we want to support the separated border model, it should be possible by simply subclassing TablePainter and implementing its pure virtual paintTable(...) function.
The heavy work turned out to be the correct joining and collapsing of multi-line borders, and after using up numerous sketch pads trying to find a good solution I almost gave up. But I then sat down and categorized all the different cases of joins that can be encountered in a table and systematically wrote the algorithm that is in use right now. It's not perfect, but it does cover all cases and it's easy to make aesthetic adjustments for the corner cases that might occur. The new code includes TableBorder, TableBorderLine as well as the algorithms themselves which are kept as helper methods in a separate namespace TableUtils to facilitate unit testing.
During week six I polished the joining algorithm further, fixed some bugs and started working on UI.
I added a new custom widget TableSideSelector for selecting sides of a table/cell when changing border properties and added support for configuring the borders, including multi-line borders, to the Table palette in the Properties Palette. This UI still needs improvement, but it's part of what I'm working on right now.
The current design of the tables is quite simple. We have PageItem_Table, which is the item representing a table. Internally this class keeps a few lists with row heights, columns widths as well as positions of the rows and columns in the table. It also has a QList<QList<TableCell>> for the cells of the table.
The table also keeps a list of CellAreas. These are simply rectangular areas of merged cells. This is so the table can easily answer if a cell is covered by another cell or not, without the code getting too complicated. These areas are updated when rows or columns are inserted or removed from the table.
Table cells may be requested from the table using cellAt(int row, int column). Cells retrieved from the table may be marked as invalid by the table if the row or column the cell is in is removed. This is made possibly by TableCell being an explicitly shared class. A cell may be queried for its validity using isValid().
If a covered cell is requested using the cellAt(...) API, the table will return the covering cell. This is similar to how the analog API works in e.g. QTextTable. This behavior is relied upon heavily throughout the code, especially in the painting.
TableCell itself is simply an interface for setting and getting cell properties. TableCells also know which row and column they are in, and their row/column span.
As previously mentioned, the table paints itself completely using the current TablePainter. There is currently only one TablePainter: the CollapsedTablePainter.
A recent addition to painting is that the painter will paint the table offset in the Y direction by half the width of the widest border found along its top edge, and in the X direction by half the width of the widest border along its left edge. This is so that the Table will stay inside the frame at the top and left side at all times. The internal table grid, represented by the lists of row and column positions, is still unaffected though; the offsets are only used during painting and when calculating the table width/height (tableWidth() / tableHeight()).
Both PageItem_Table and TableCell use a locally stored style for their properties, and setting a style using setStyle(...) on a table or cell results in the parent style of this internal style being set. This allows certain properties to be set locally, while unset ones are taken from the style. Each property has a get function, a set function and an unset function.
I think this pretty much covers the gist of it. Things are of course subject to change.
Getting the Code
The code for my project is kept in a separate Git repository at SourceForge. To clone it, simply run
$ git clone git://scribus.git.sourceforge.net/gitroot/scribus/gsoc11tables
and follow the normal instructions for building Scribus.