GSoC 2016 Work Report – Better CSS Style Sheet Support

Organization: Inkscape
Project: Better CSS Style Sheet Support

The trunk of Inkscape can be found at https://code.launchpad.net/inkscape. My branch name is css-support and this branch can be located at https://code.launchpad.net/~grewalkamal005/inkscape/css-support.

The list to commits I made along with a brief description for them can be seen below:

  1. Revision 14950: This registers Style dialog in Inkscape. Simple Dialog with just a ‘+’ is created allowing to add selectors in the dialog. (More details)
  2. Revision 14952: This sets the class attribute of the objects selected equal to the text in entrybox. However, this work was ignored.
  3. Revision 14954: This adds the selector added in Style Dialog to style element in XML representation. Also the work ignored in previous commit was replaced by the expected implementation of adding selectors.
  4. Revision 14956: If any selectors already exist in the style element, the style dialog is populated with all those selectors, one in each row of the dialog.
  5. Revision 14958: A patch by Tavmjong Bah for broken automake was applied in this revision.
  6. Revision 14961: Deletion of a selector from the Style Dialog by clicking ‘-‘ at bottom of Style Dialog has been implemented in this. Nothing is removed from the style element in XML repr.
  7. Revision 14963: This removes unnecessary spaces that surround selector labels in row as well as in XML style element.
  8. Revision 14964: A crash occurs when an object without style attribute is added to selector. This commit resolves this crash.
  9. Revision 14965: This commit sets the class attribute of the objects added to selector. If the selector is a class selector, then the ‘.’ preceding the class selector row label is removed and is set as class attribute of the objects in selector.
  10. Revision 14967: The map being used to save pair of selector and the associated objects is replaced by a vector in this revision.
  11. Revision 14968: Deletion of selector by clicking ‘-‘ at bottom of Style Dialog now removes the selector both from the dialog as well as the style element.
  12. Revision 14970: Unwanted space at the top of Style Dialog is removed via this.
  13. Revision 14972: Deletion works well except when there is only one selector left. Deleting this only selector that remains causes a crash. This is fixed in this commit.
  14. Revision 14973: In case, an empty string is added for the selector name via the entry box, a crash occurs which is resolved by this revision.
  15. Revision 14976: This adds and removes objects to and from selectors and thus child rows are added to parent rows (selectors). XML’s style element is not updated yet.
  16. Revision 14978: Another crash when deleting selectors is fixed in this along with some coding style corrections.
  17. Revision 14979: This enables selection of matching objects when a selector row is selected in the Style Dialog.
  18. Revision 14980: When objects are added to selector i.e. child rows are formed, style element is updated. This implementation is done by this commit.
  19. Revision 14982: When objects are removed from selector i.e. child row is deleted, then the style element is updated accordingly. Also multiple style elements do not exist anymore. If a style element is already there, its content is updated accordingly when adding and deleting selectors.
  20. Revision 14983: This solves some compiler errors.
  21. Revision 14984: If an existing drawing with selectors is opened, the buttons at the bottom and in front of selector rows are made functional correctly.
  22. Revision 14985: This commit adds support to select corresponding treeview rows when objects are selected.
  23. Revision 14988: This fixes deletion and selection of objects when clicking on styledialog rows and vice-versa.
  24. Revision 14989: This fixes issues related to updating style content and selection of rows in treeview.
  25. Revision 14991: This adds support for selection of matching objects when selector row is single-clicked as well as double-clicked.
  26. Revision 14993: Support to delete objects from selector by clicking ‘-‘ in front of the child object row is added by this revision.
  27. Revision 14995: This is a patch by Tavmjong Bah to propagate changes to object tree with changes in style element.
  28. Revision 14997: A small replacement of a string s1 with matchSelector.
  29. Revision 14998: This corrects deletion of child rows when parent selector is deleted.
  30. Revision 14999: Initially the expander column was the one with ‘+’ symbol in front of every row. This changes the expander column of treeview and sets indentation of child rows.
  31. Revision 15001: This adds CSS panel with editing support with no changes reflected yet in the drawing.
  32. Revision 15003: This updates XML’s style element and hence drawing when properties are edited in CSS panel.
  33. Revision 15004: This corrects the updating of XML correctly when editing properties in CSS panel.
  34. Revision 15005: This separates the CSS dialog from Style Dialog visually, thus allowing it to be resized.
  35. Revision 15007: This replaces the storage vector with InkSelector class and some code improvements.
  36. Revision 15009: A robust implementation of _getSelectorVec() is added here.
  37. Revision 15010: This add functionality to add and delete CSS property in CSS panel. Clicking ‘+’ at bottom of CSS panel adds a CSS property and clicking ‘-‘ in front of CSS property row deletes the CSS property selected.
  38. Revision 15012: A crash occurs when when deleting CSS property which is resolved in this revision.
  39. Revision 15014: Another patch by Tavmjong Bah which adds changes for compilation with latest trunk.
  40. Revision 15016: An empty CSS property row gets added sometimes. This is fixed via this commit.
  41. Revision 15017: Some tests while adding & removing objects from class selectors failed. Those tests are passed by code in this commit.
  42. Revision 15019: The tests to add ID selectors are all passed by changes in this commit.
  43. Revision 15020: A minor addition of a condition to correctly add CSS properties.
  44. Revision 15022: Some tests still failed to pass ID selector tests. The complete passing of tests to add ID selectors is done in this.
  45. Revision 15023: The remaining simple class selector tests are passed by changes in this revision.
  46. Revision 15024: Some comments are added to improve the understanding of code.

This all has been achieved so far.

Further work to be done:

  1. Undo Redo
  2. Dragging of Selectors

Selecting Matching Selectors when Objects are Selected

This post explains selection of treeview rows when objects belonging to that selector row are selected.

First, the objects in the desktop selection are obtained. Next the treeview’s rows are iterated and each row’s column _colObj is obtained as:
std::vector<spobject *> objVec = row[_mColumns._colObj];

The selected object’s ID is compared with each object’s ID in objVec. If the comparison returns true, then the row whose objVec had matching objects is added to treeview selection as:
_treeView.get_selection()->select(row);

By default, only the parent rows are iterated. So a check is also performed on child rows to see if IDs match with them. Also if there is a prior selection and the current object selected does not belong to any selector, then the treeview selection is cleared using _unselect_all() function of Gtk::TreeSelection.

Screenshot from 2016-07-27 13:50:02
In the above screenshot, the object associated with second child row is selected which in turn selects the row in treeview.

Selection of Objects when Style Dialog Row is Selected

Whenever any row in Style Dialog is selected, the corresponding objects in the drawing must be selected. This was implemented quickly. First the selection prior to selecting any row is cleared. Next it is made sure that the objects are selected only when the tree column corresponding to selector label is clicked. First column with ‘+’ is avoided because it is used to add objects to selector.

Next _colObj, i.e. the column referring to corresponding objects of the selected row is used to get the objects to be added to selection.

_desktop->selection->add(obj);

obj is the object in the objVec returned by _colObj. Thus objects are added to selection. Besides it is also checked, if the row selected in the Style Dialog is a parent row or a child row. For the former case, its child rows are also iterated and those objects are also added to selection.

Case 1: When parent row is selected and it has children
Screenshot from 2016-07-27 12:58:52

Case 2: When child row is selected
Screenshot from 2016-07-27 12:59:09

There will be another case in selection when matching selector rows should be seleted when objects in the drawing are selected, which will be discussed in next post.

Adding Objects to Selector (Part 2)

Any changes in the Style Dialog like adding an object to a selector must be updated in XML representation of the drawing. Clicking ‘+’ in front of a selector adds objects to that selector.

If the selector to which object is added is a class selector, then the class attribute of the object is set to the selector name. The screenshot below shows this:

Screenshot from 2016-07-27 12:10:58

If the object is deleted by clicking ‘-‘ in front of its row, then the class attribute that was set due to the selector is unset.

Screenshot from 2016-07-27 12:11:19

This is implemented in _handleButtonEvent(GdkEventButton event). Whenever first column of the selector row that is a ‘+’ is clicked, the _desktop is checked for selection. If it is not empty, those objects are added to selector. Rows are added with their content set as:

childrow =
(_store->append(row->children()));
childrow[_mColumns._selectorLabel] = "#" + std::string(obj->getId());
childrow[_mColumns._colAddRemove] = false;
childrow[_mColumns._colObj] = _desktop->selection->list();

Finally, _styleChild i.e. the style element is obtained and _updateStyleContent() is called to update the XML representation.

Progress So Far

Not going into details in this post, I will just mention about what progress I have made in the past two weeks.

  1. Adding Objects to Selector, in XML repr too
  2. Deleting objects from Style Dialog as well as from repr (this was a tough one)
  3. Selecting all matching objects when any row(selector) in Style Dialog
  4. Selecting matching selectors when objects in the drawing are selected
  5. Opening CSS dialog on double clicking selector
  6. Populating CSS dialog with properties of selector selected in Style Dialog
  7. Make properties editable in CSS dialog and update XML repr so changes are reflected in the drawing

Besides polishing the last two above, dragging selectors along with undo-redo support is left. It has been a pleasure so far! 😀

I will dig the details on each of the above in coming posts.

Adding Objects to Selector (Part 1)

Adding objects to selector has been a long process. So far, there has been a list of selectors in the style dialog. Adding objects to selectors will from a hierarchical structure. So Gtk::ListStore is replaced by Gtk::TreeStore which supports hierarchical representation of elements in the treeview.

There are basically two things to be updated:

  1. Add objects to selectors in treeview of the dialog
  2. Updating the XML representation whenever any child was added to a selector

Point to be noted is that parent-child relationship has to be actually maintained in the former one only to depict the representation of treeview but no such thing has to be done in the latter case. I initially considered this relationship to be existing in both cases.

Well moving to the implementation part, if any object is selected in the drawing, clicking on ‘+’ button in any row should add it as a child in the row whose button was clicked. The button event in the first column of every row which is ‘+’ button is detected first via _handleButtonEvent(GdkEventButton event). The selected row whose ‘+’ is obtained as:

Gtk::TreeModel::iterator iter = refTreeSelection->get_selected();
if ( iter ) {
Gtk::TreeModel::Row row =
iter;
....
}

Also I considered the objects in each selector had to be unique which I observed from Selection Sets. Style dialog is inspired from it and I followed similar steps. I worked as per this consideration but doing so had unexpected behavior. It was sort of haphazard, objects were added to selector random number of times, irrespective of the number of clicks on ‘+’.

The ultimate goal is to replace Selections sets with CSS selectors which can not only be used for selections sets but also for CSS styling. –Tavmjong Bah

So discarding the assumption I had made, I added children to rows successfully. Another Gtk::TreeModelColumn column has been added which stores the object corresponding to the selector. The other column only stores some information associated to the object like its id which can be a selector label. Having object associated is helpful.

The row obtained above is used to append children to it. The various columns of this childrow are assigned values as shown in code snippet below:

childrow = *(_store->append(row.children()));
childrow[_mColumns._selectorLabel] = obj->getId();
childrow[_mColumns._colAddRemove] = false;
childrow[_mColumns._colObj] = obj;

Screenshot from 2016-06-30 20:19:19

Looks good. 😀

This was just end of part 1 which is adding objects to selectors in treeview of the dialog. The second part which is updating XML representation will be discussed in next post.

A Lot to be Done Yet

Undo redo support has not been added yet. Since yesterday, I have been exploring the same. Implemented a part of it by looking at undo redo implementation in Selection Sets. Some things look clear, others fuzzy. Since mid-term is near, I considered starting my last milestone before it. Little late for it. It is adding and removing objects from selectors via use of symbols in front of each row of treeview. Adding this new feature is huge.

And meanwhile today, I found another issue in implementation done so far. Yep some trivial but significant cases always hideous. Remove text from the entrybox which is used to set the name of selector and a crash. In this case, I set default text .Class1 for the selector name.

Knocking Down Two Issues

The first noticeable thing whenever the style dialog is opened is some empty space at its top.

Screenshot from 2016-06-04 22:13:55

Solved it using a function intrinsic to Inkscape’s Panel class. Just packing the _mainbox that contains all the elements of style dialog to get_contents, the unwanted space issue was solved. Well it took quite a time to figure it out!

_getContents()->pack_start(_mainBox, Gtk::PACK_EXPAND_WIDGET);

Coming next to recent implementation of deleting a selector, it worked well except in the case when the last and only remaining selector was deleted. This was because _sValue was empty and using the following statement:

_sValue.append(selValue.c_str());

actually had NULL as argument to append() function. So after clearing the _sValue, content was only appended to it if some selectors were still present in the document.

Deletion from XML Tree Done

The previous implementation of deletion from treeview was insignificant since a selector erased from treeview was saved to document because it was not removed from XML representation of the tree too.

Modification of content in the style node of XML tree is a complicated process. Tried it but not very efficient and accurate. Instead of modifying the content, I cleared the node’s content and set its value corresponding to the selectors left in the selectorVec. The vector and content are cleared using following lines:

selVec.erase(selVec.begin()+i);
_sValue.clear();

Next I will be moving to adding and deleting objects to selectors using buttons at the beginning of every row in the treeview.

From Map to Vector

Opening an existing drawing populated the style dialog but the selectors were not added to single style element. To achieve this, I played around with the map that contained selector names mapped to selector values (style attributes) of the object.

For every element that existed in the drawing already, I iterated the map of selectors. Appending each name and value to a string, the values of the resulting string were not in the order of insertion of selectors to this map. And then I learnt that maps contain key-value pairs in an ordered manner. The ordering occurs in a lexicographic manner irrespective of order of insertion of elements to it. Remember binary tree search? Well maps operate on the basis of this search algorithm.

So instead of using this map, I created a vector since order of insertion and retrieving elements has to be same.
std::map&ltstd::string, std::string>_selectorMap; std::vector<std::pair<std::string, std::string> >_selectorVec;

Modification to move from map to vector was done at all places needed. Later I obtained nodes from XML representation of the document and added the content retrieved from the vector to a new child in the XML tree.