0069: TextView and TextBuffer

These two widgets, working together, give us the basis for text/code editors, word processors, and other DTP software. The TextView not only shows us what’s contained in the TextBuffer, it gives us access so we can edit, append, etc.

A Simple Text Editor

Results of this example:
Current example output
Current example output
Current example terminal output
Current example terminal output (click for enlarged view)

To get the TextView into a Window in any kind of useful way also means using a ScrolledWindow as an intermediary container. After all, there’s no point in having hundreds of lines of text if you only have visual access to the first dozen. (There’s nothing like typing blind to keep you both focused and stressed.)

So, we stuff a ScrolledWindow into our usual AppBox, and stuff the TextView into the ScrolledWindow… like this:

class AppBox : Box
{
	bool expand = true, fill = true;
	uint globalPadding = 10, localPadding = 5;
	ScrolledTextWindow scrolledTextWindow;
	
	this()
	{
		super(Orientation.VERTICAL, globalPadding);
		
		scrolledTextWindow = new ScrolledTextWindow();
		
		packStart(scrolledTextWindow, expand, fill, localPadding); // TOP justify
		
	} // this()

} // class AppBox


class ScrolledTextWindow : ScrolledWindow
{
	MyTextView myTextView;
	
	this()
	{
		super();
		
		myTextView = new MyTextView();
		add(myTextView);
		
	} // this()
	
} // class ScrolledTextWindow

TextBuffer

We don’t have to instantiate the TextBuffer because the TextView already has one associated with it when it’s instantiated. But, there is some flexibility here. We could end up with this association between a TextView and a TextBuffer in a few different ways:

  • instantiate a TextView and grab a pointer to its TextBuffer (as we’re doing in this example),
  • instantiate the TextBuffer first and pass it to the TextView’s overloaded constructor—which doesn’t seem all that useful to me unless you…
  • instantiate one TextView and pass its TextBuffer along to the constructors for one or more other TextViews so they can share.

But in this example, we’ll do it the simplest way:

class MyTextView : TextView
{
	TextBuffer textBuffer;
	string content = "Now is the English of our discontent.";
	
	this()
	{
		super();
		textBuffer = getBuffer();
		textBuffer.setText(content);

	} // this()

} // class MyTextView

Within the MyTextView constructor, a quick call to getBuffer() gives us access and from there, we give it some content with setText().

But for thoroughness sake, let’s also look at a shared TextBuffer

TextViews with a Shared TextBuffer

Results of this example:
Current example output
Current example output
Current example terminal output
Current example terminal output (click for enlarged view)

Now, this gets a bit more complex. At some point in the hierarchy, we’ve got to establish a pointer to the first TextView’s TextBuffer and pass it along to the others. I decided to do this at the AppBox level which makes the most sense to me:

class AppBox : Box
{
	bool expand = true, fill = true;
	uint globalPadding = 10, localPadding = 5;
	ScrolledTextWindow scrolledTextWindow;
	TextView masterTextView;
	DependentTextView dependentTextView;
	TextBuffer sharedTextBuffer;
	
	this()
	{
		super(Orientation.VERTICAL, globalPadding);
		
		scrolledTextWindow = new ScrolledTextWindow();
		packStart(scrolledTextWindow, expand, fill, localPadding); // TOP justify
		
		// grab the TextBuffer pointer
		masterTextView = cast(TextView)scrolledTextWindow.getChild();
		sharedTextBuffer = masterTextView.getBuffer();
		dependentTextView = new DependentTextView(sharedTextBuffer);
		packStart(dependentTextView, expand, fill, localPadding);
		
	} // this()

} // class AppBox

Grabbing a pointer to the first TextView’s TextBuffer proves to be a two-step operation because the result of getChild() has to be cast() as a TextView. If not, the result is a generic Widget which doesn’t give us access to the TextView’s getBuffer() function.

And to complete this multi-association, the DependentTextView class looks like this:

class DependentTextView : TextView
{
	this(TextBuffer sharedTextBuffer)
	{
		super(sharedTextBuffer);
		
	} // this()
	
} // class DependentTextView

At this level, things are dead simple. The constructor takes the TextBuffer pointer as an argument and passes it along to the super-constructor.

If you type, copy, cut, or paste in one TextView, your actions are mirrored in the other… I’m sure there are uses for this type of thing, otherwise, why have this functionality, right?

Conclusion

And that’s the basics of the TextView widget and its associated TextBuffer. Turning them into something more useful, such as a full-blown text editor, we’ll leave for another time.

Happy coding.

Comments? Questions? Observations?

Did we miss a tidbit of information that would make this post even more informative? Let's talk about it in the comments.

You can also subscribe via RSS so you won't miss anything. Thank you very much for dropping by.

© Copyright 2023 Ron Tarrant