Grantlee: Handling custom QTextObjects

Grantlee 5.3.0

Handling custom QTextObjects

It is possible using the QTextObjectInterface API to construct QTextDocument instances containing custom text objects. If processing a QTextDocument containing custom objects through the MarkupDirector and AbstractMarkupBuilder classes, it is necessary to implement support for the custom types in Grantlee.

In this example, we create a builder with a new method `addAudioTag`. As it may be desired to implement multiple builders with support for the `addAudioTag` method, it makes sense to put that call into an interface.

class AbstractAudioBuilder : virtual public Grantlee::AbstractMarkupBuilder
{
public:
virtual void addAudioTag( const QString &source ) = 0;
};
Grantlee::AbstractMarkupBuilder
Interface for creating marked-up text output.
Definition: abstractmarkupbuilder.h:52

We create a small interface for supporting the addition of audio markup. The interface should inherit from the AbstractMarkupBuilder interface, so that functionality from that interface is also available.

Customized builders inheriting from an existing builder and the new interface can then be created.

class AudioTextHtmlBuilder : public Grantlee::TextHTMLBuilder, public AbstractAudioBuilder
{
public:
AudioTextHtmlBuilder();
void addAudioTag( const QString &source ) override;
};
class AudioPlainTextBuilder : public Grantlee::PlainTextMarkupBuilder, public AbstractAudioBuilder
{
public:
AudioPlainTextBuilder();
void addAudioTag( const QString &source ) override;
};
Grantlee::PlainTextMarkupBuilder
Creates a simple marked up plain text document.
Definition: plaintextmarkupbuilder.h:106
Grantlee::TextHTMLBuilder
The TextHTMLBuilder creates a clean html markup output.
Definition: texthtmlbuilder.h:113

The implementation of each concrete builder is appropriate to the type of markup being created.

void AudioTextHtmlBuilder::addAudioTag( const QString &source )
{
appendRawText( QString( "<audio src=\"%1\" />").arg( source ) );
}
void AudioPlainTextBuilder::addAudioTag(const QString& source)
{
auto ref = addReference( source );
appendRawText( QString( "[%1]" ).arg( ref ) );
}

The final part to be implemented is support for the custom type in a MarkupDirector. The MarkupDirector::processCustomFragment method is called for any QTextFragments with custom QTextFormat types. This method can be implemented to instruct the builder to create appropriate output for the type.

class AudioTextDocumentDirector : public Grantlee::MarkupDirector
{
public:
AudioTextDocumentDirector(AbstractAudioBuilder* builder);
void processCustomFragment( const QTextFragment& fragment, const QTextDocument* doc) override;
private:
AbstractAudioBuilder *m_builder;
};
Grantlee::MarkupDirector
Instructs a builder object to create markup output.
Definition: markupdirector.h:93

We create a subclass of Grantlee::MarkupDirector to take our new `AbstractAudioBuilder` interface and implement the `processCustomFragment` method to handle QTextFragments with our custom format type. The name of the audio file referenced in the document is then extracted and used as a parameter in the `addAudioTag` method.

AudioTextDocumentDirector::AudioTextDocumentDirector(AbstractAudioBuilder* builder)
: MarkupDirector(builder), m_builder(builder)
{
}
void AudioTextDocumentDirector::processCustomFragment(const QTextFragment& fragment, const QTextDocument* doc)
{
if ( fragment.charFormat().objectType() != AudioType )
return Grantlee::MarkupDirector::processCustomFragment( fragment, doc );
QString name = fragment.charFormat().property( AudioProperty ).toString();
m_builder->addAudioTag( name );
}
Grantlee::MarkupDirector::processCustomFragment
virtual void processCustomFragment(const QTextFragment &fragment, QTextDocument const *doc)

The custom AudioTextDocumentDirector and builders can then be used to create output including markup for custom types.

auto builder = new AudioTextHtmlBuilder();
auto director = new AudioTextDocumentDirector(builder);
director->processDocument(document);
QString result = builder->getResult();

In a simple case, this could create output such as:

      <p>Here is a paragraph with an inline audio element: <audio src="http://www.example.com/mysound.ogg" />.

      <p>And another paragraph.

or if the AudioPlainTextBuilder was used instead:

      Here is a paragraph with an inline audio element: [1].

      And another paragraph.

      ----
      [1] http://www.example.com/mysound.ogg

The textedit example in the Grantlee source distribution implements this feature.