In my ongoing quest to get a Grantlee release out the door, I recently imported the KRichTextBuilders from kdepimlibs and renamed them Grantlee::RichTextBuilders.
I wrote those classes in 2008 but they were never used yet, and their headers were never installed. The reason for that is mainly that I was not happy with the API. The intention of the builders classes was to make it easier to create “cleaner” html from a QTextDocument that what is returned from QTextDocument::toHtml().
Users seem to have a lot of complaints about it taking more space than it needs to, but on a more relevant level, the toHtml call returns a html document complete with metadata such as a element which needs to be removed if data from multiple QTextDocuments is to be combined into one with an external theme, as is the case with KJots.
Up to now the way KJots has done that is to have an aggregation QTextDocument and do something like
foreach( QTextDocument *page, book ) aggregationDocument->insertHtml( page.toHtml() );
There is some other processing done to put things like breadcrumbs and table of contents into the aggregation document. That works, but it ties the content of the aggregation document into the C++ code, which is exactly what I want to avoid in the bright future with Grantlee. All of that stuff, including the placement and content of the table of contents, and even whether it should exist at all should be externalized to Grantlee themes. I recently wrote about that with screenshots on the dot.
Additionally, the toHtml() does not know what to create in the html output when it comes across the custom QTextObject you have inserted into your document.
Finally, maybe you don’t want to output html from your QTextDocument at all, but you want the contents output as MediaWiki markup or Markdown. What then?
The more flexible way to get the data out of a QTextDocument is then to iterate over the content of it, which is made up of QTextFrames and QTextBlocks.
That is exactly what the RichTextBuilders written back in 2008 do. I had just finished reading the GoF book (that is , Gang of Four🙂 ) about design patterns, which has an example of exactly this problem, and so I used an interpretation of the Builder pattern to implement them.
The problem is that was not extensible in all the ways QTextDocument is. Custom objects may be inserted into a QTextDocument to do anything you want. For example, maybe you want to drop sound files into your notes application and get a nice widget for playing it. When you export the file to html you want the sound file to still be usable in the exported version. The way this is done in the application is to implement QTextObjectInterface, insert the custom object as a UnicodeReplacementCharacter with a specific QTextCharFormat, and that can be intercepted and drawn in a custom way later when laying out the document in a QTextEdit.
The problem is that adding that kind of functionality to the RichTextBuilders would require adding a new virtual function in the AbstractMarkupBuilder, and adding calls to that in the MarkupDirector. Adding new virtual functions is not really an option if binary compatibility is to be maintained. I realized though that the use of virtual inheritance would allow me to extend the Builder interface to output custom markup, and to implement the code in the MarkupDirector to handle the custom QTextObjects, or to handle custom QTextBlockUserData.
That is exactly what the textedit example in the Grantlee source does. The AbstractAudioBuilder interface defines an extra method for creating markup for representing audio. That interface is implemented by AudioTextHtmlBuilder, which also inherits the rest of Grantlee::TextHTMLMarkupBuilder, and by AudioPlainTextBuilder, which also inherits the rest of Grantlee::PlainTextMarkupBuilder. The Audio*Builder implementations really only implement support for creating output for representing audio. There is very strong separation of concerns. In turn, the AudioTextDocumentDirector is concerned only with extracting the audio information from the QTextDocument.
The entire process is also documented in the Handling Custom QTextObjects example.
The result is a text editing application which can play and export sounds. Maybe KJots 4.6 will have a feature like this.
On the left is a QTextEdit whose content is editable and which supports a stylish play button, and on the right, a QWebKit element which supports the html5 element showing the exported version. Unfortunately, I get hangs with even trivial WebKit using apps, so I’ve put the text of the audio tag in the output instead of the actual tag because I can’t test it properly.
Of course it uses Phonon internally. KJots in trunk already uses Akonadi and Plasma, with more integration with Nepomuk, GHNS and now Phonon potentially in the future. Now all I need is an excuse to add a Marble dependency. KDE Pillars: Gotta catch ’em all.