So there I was, having a look at implementing QListView and realising that, as a control/widget, it didn't have any direct item interaction methods whatsoever!
Oh, bugger...
Fortunately, as noted in my last post, I've been creating a BlitzMax version of a type of a item model which subclasses QAbstractItemModel. And for QComboBox it is working really well. Changes to items on the combo eek down into the BlitzMax item objects. Whenever you change data in these item objects, they emit events that the rest of the model/view framework can handle appropriately. It's all rather cool and groovy!
Anyway, back to QListView. It doesn't do very much, on the face of it, apart from display a list. Any (and all) interaction with the items is done via the model and the "selection model". Changes to the model filter up to the view automagically, and stuff appears (or disappears) in the visual list before your very eyes.
As a framework, it appears to work very well indeed. Of course, if you are used to doing stuff directly with controls/widgets it is a bit of a leap to start playing with the model instead, and simply ignoring the view altogether!
As an example, if we want to change the text for an item in QComboBox, we simply call :
setItemText(index:Int, text:String)
and all the real work of processing that information and is handled by QComboBox.
On the otherhand, if we want to change the text for an item in QListView, we need to :
model.setData(index:QModelIndex, text:String)
where index is a reference to the location of the item in the model (row, column, parent item).
Notice that we need the model, and a way to map our row/index to the model's index reference.
Once we set/update the data, the view will update the text accordingly.
So, when we are working with Views, look, but don't touch!
:o)
Monday, 31 December 2012
Saturday, 29 December 2012
Not so QStandardItem...
So there I was trying to work out how I might integrate a model/view design into QtMax for handling all of the list/tree widgets, and I got to reading the QStandardItem/Model documentation.
"Hey, that sounds like just the thing I need!" I thought to myself, and hacked away at creating the wrapper code for QStandardItem and QStandardItemModel, using a custom QStandardItem object which handled user Object data on-the-fly.
Everything was looking shiny and new, so I decided I should test it out, and knocked together a quick QComboBox test app, which used my model and added a couple of items via the comboBox API.
Ta da! … oh, wait… nothing is actually happening!?
Further investigations revealed that QComboBox wasn't designed to expect a subclassed QStandardItem and went happily off to create base QStandardItems itself without using the "factory" that the documentation said I should implement (::clone()).
Which, as one would imagine, was somewhat of a let down after writing a whole bunch of code that I couldn't use ;-)
… time passes …
I pondered and pondered for a while, and went through a couple of mis-starts, but then came upon the idea that the best thing to do would be to code most of what I needed in BlitzMax, and interface that directly with the backend C++ classes, which would mostly act as a pass-through.
Okay, so the thought of implementing my own item/model for plugging into a QAbstractItemModel was a tad on the daunting side, considering that I wasn't only writing a couple of C++ classes, but a bunch of helper types, object transformations and the main BlitzMax types to hold everything together.
I ended up keeping the names, QStandardItem and QStandardItemModel, and rewrote what I already had. I decided that all the item info needed to be kept in the Type, so I came up with a way to store everything in a TMap based on the "roles" that the model/view framework uses. I also built a custom children item holder which is based on an array, rather than a TList. I thought that it would be quicker doing lookups on an array index than trawling through a TList, and I am happy to take a hit on the resize of the array when it is required.
Because the underlying system passes data via QVariant, we need to do a lot of conversion between that and Objects. but it's simple casting so it shouldn't be too slow.
It took a whole day to get to a point where I was ready to try the QComboBox test app again, and after a couple of minor tweaks, I was very pleased with the results.
A simple call to :
iterates through the entire model framework, creating a new QStandardItem and populating it appropriately with all the role data, and even the user "data" object, which you can then retrieve by calling itemData(index) !
Of course, there's still much to do, like implementing cleanup and so forth, but with the basic engine running I feel much better about using this as a core for all of the Item-based widgets - lists and trees.
:o)
"Hey, that sounds like just the thing I need!" I thought to myself, and hacked away at creating the wrapper code for QStandardItem and QStandardItemModel, using a custom QStandardItem object which handled user Object data on-the-fly.
Everything was looking shiny and new, so I decided I should test it out, and knocked together a quick QComboBox test app, which used my model and added a couple of items via the comboBox API.
Ta da! … oh, wait… nothing is actually happening!?
Further investigations revealed that QComboBox wasn't designed to expect a subclassed QStandardItem and went happily off to create base QStandardItems itself without using the "factory" that the documentation said I should implement (::clone()).
Which, as one would imagine, was somewhat of a let down after writing a whole bunch of code that I couldn't use ;-)
… time passes …
I pondered and pondered for a while, and went through a couple of mis-starts, but then came upon the idea that the best thing to do would be to code most of what I needed in BlitzMax, and interface that directly with the backend C++ classes, which would mostly act as a pass-through.
Okay, so the thought of implementing my own item/model for plugging into a QAbstractItemModel was a tad on the daunting side, considering that I wasn't only writing a couple of C++ classes, but a bunch of helper types, object transformations and the main BlitzMax types to hold everything together.
I ended up keeping the names, QStandardItem and QStandardItemModel, and rewrote what I already had. I decided that all the item info needed to be kept in the Type, so I came up with a way to store everything in a TMap based on the "roles" that the model/view framework uses. I also built a custom children item holder which is based on an array, rather than a TList. I thought that it would be quicker doing lookups on an array index than trawling through a TList, and I am happy to take a hit on the resize of the array when it is required.
Because the underlying system passes data via QVariant, we need to do a lot of conversion between that and Objects. but it's simple casting so it shouldn't be too slow.
It took a whole day to get to a point where I was ready to try the QComboBox test app again, and after a couple of minor tweaks, I was very pleased with the results.
A simple call to :
combo.addItem("item 1", data)
iterates through the entire model framework, creating a new QStandardItem and populating it appropriately with all the role data, and even the user "data" object, which you can then retrieve by calling itemData(index) !
Of course, there's still much to do, like implementing cleanup and so forth, but with the basic engine running I feel much better about using this as a core for all of the Item-based widgets - lists and trees.
:o)
Tuesday, 25 December 2012
Event Handling (or Round and Round in Circles)
Today we are going to discuss how to wrap a Qt event handler function in BlitzMax.
Meet our friend, an event function :
void QLineEdit::changeEvent ( QEvent * ev )
This is called in Qt whenever the contents of a QLineEdit changes. Details of the event can be found in the QEvent object passed into the function.
In BlitzMax, our QLineEdit type has a matching method :
Method changeEvent(event:QEvent)
You can subclass QLineEdit with your own BlitzMax type, and whenever the text changes, your overriding method will be called :
Type MyQLineEdit Extends QLineEdit
Method changeEvent(event:QEvent)
Print "It Changed!"
End Method
End Type
Well, that's just great, says you, but how does the magic really happen!?
Ah, I'm glad you asked, because it's really rather interesting.
If we were writing in C++, it would simply be a case of creating our subclass and implementing the overridden function in our new class. For any events we don't care about, we don't override them. Job Done.
However, with a wrapper, because BlitzMax code sits so high above the C++ glue you need to override ALL of the event functions so that you can push them back into BlitzMax and either perform the default function, or call into the overridden method in the new Type.
So, the first step is to override, the event function :
void MaxQLineEdit::changeEvent(QEvent * ev) {
_qt_qwidget_QWidget__OnChangeEvent(maxHandle, ev);
}
In the QWidget type, our callback function simply constructs BlitzMax objects and calls the real method :
Function _OnChangeEvent(obj:QWidget, event:Byte Ptr)
obj.changeEvent(QEvent._create(event))
End Function
Because BlitzMax is clever, if the "obj" object happens to be a subclass of QWidget, the changeEvent() method of that subclass will actually be called instead, and therefore, your overridden method will be called.
But what of those methods you haven't overridden? Shouldn't they perform their original functionality? Well, yes, of course, and therefore we must also supply this default functionality by calling back into Qt and handling the original event.
Our QWidget type has this default method call :
Method changeEvent(event:QEvent)
bmx_qt_qwidget_default_changeevent(qObjectPtr, event.qObjectPtr)
End Method
Subsequently calling this function in our C++ glue :
void MaxQWidget::defaultChangeEvent(QEvent * event) {
QWidget::changeEvent(event);
}
Which, as you can see is allowing Qt to handle the event as it would normally if we hadn't overridden it ourselves!
Ideally, in our original example, any events that your override yourself, you should really add a call to your superclass method so that Qt might have a chance of processing the event too. Otherwise you might your application acting strangely.
So, doing something like this is a good idea, unless you have a reason not to :
Method changeEvent(event:QEvent)
Print "It Changed!"
Super.changeEvent(event)
End Method
If you like going around in circles, writing wrappers may be for you!
Happy coding!
:o)
Meet our friend, an event function :
void QLineEdit::changeEvent ( QEvent * ev )
This is called in Qt whenever the contents of a QLineEdit changes. Details of the event can be found in the QEvent object passed into the function.
In BlitzMax, our QLineEdit type has a matching method :
Method changeEvent(event:QEvent)
You can subclass QLineEdit with your own BlitzMax type, and whenever the text changes, your overriding method will be called :
Type MyQLineEdit Extends QLineEdit
Method changeEvent(event:QEvent)
Print "It Changed!"
End Method
End Type
Well, that's just great, says you, but how does the magic really happen!?
Ah, I'm glad you asked, because it's really rather interesting.
If we were writing in C++, it would simply be a case of creating our subclass and implementing the overridden function in our new class. For any events we don't care about, we don't override them. Job Done.
However, with a wrapper, because BlitzMax code sits so high above the C++ glue you need to override ALL of the event functions so that you can push them back into BlitzMax and either perform the default function, or call into the overridden method in the new Type.
So, the first step is to override, the event function :
void MaxQLineEdit::changeEvent(QEvent * ev) {
_qt_qwidget_QWidget__OnChangeEvent(maxHandle, ev);
}
In our QWidget BlitzMax type we already have a callback function set up for this event (since changeEvent() is a QWidget event that QLineEdit overrides itself), so we can call this. maxHandle is a handle to our BlitzMax object (the one with the method).
In the QWidget type, our callback function simply constructs BlitzMax objects and calls the real method :
Function _OnChangeEvent(obj:QWidget, event:Byte Ptr)
obj.changeEvent(QEvent._create(event))
End Function
Because BlitzMax is clever, if the "obj" object happens to be a subclass of QWidget, the changeEvent() method of that subclass will actually be called instead, and therefore, your overridden method will be called.
But what of those methods you haven't overridden? Shouldn't they perform their original functionality? Well, yes, of course, and therefore we must also supply this default functionality by calling back into Qt and handling the original event.
Our QWidget type has this default method call :
Method changeEvent(event:QEvent)
bmx_qt_qwidget_default_changeevent(qObjectPtr, event.qObjectPtr)
End Method
Subsequently calling this function in our C++ glue :
void MaxQWidget::defaultChangeEvent(QEvent * event) {
QWidget::changeEvent(event);
}
Which, as you can see is allowing Qt to handle the event as it would normally if we hadn't overridden it ourselves!
Ideally, in our original example, any events that your override yourself, you should really add a call to your superclass method so that Qt might have a chance of processing the event too. Otherwise you might your application acting strangely.
So, doing something like this is a good idea, unless you have a reason not to :
Method changeEvent(event:QEvent)
Print "It Changed!"
Super.changeEvent(event)
End Method
If you like going around in circles, writing wrappers may be for you!
Happy coding!
:o)
Sunday, 23 December 2012
Wouldn't it be interesting..
.. if MaxIDE was actually stable on Linux, and didn't want to crash/hang/annoy at every given opportunity?
That would probably require creating a MaxGUI wrapper for QtMax (itself being a BlitzMax wrapper for Qt).
There are many advantages to doing this. The main one I see is that I get to test QtMax and check that I'm implementing all the things for all the widgets that I need to be. So, for example, any widget subclasses really need to implement all of the inherited event handlers and so forth. For example, a QLabel should implement inherited events from QObject, QWidget and QFrame, as well as its own.
It's not a difficult job, there's just a lot of it!
Having a rummage through the GtkMaxGUI source, it is incredibly messy down there. I think the main reason is that raw Gtk is harder to use generically.
As an example to show how easy it is to create a CheckBox for use in MaxGUI, here's some basic code :
That would probably require creating a MaxGUI wrapper for QtMax (itself being a BlitzMax wrapper for Qt).
There are many advantages to doing this. The main one I see is that I get to test QtMax and check that I'm implementing all the things for all the widgets that I need to be. So, for example, any widget subclasses really need to implement all of the inherited event handlers and so forth. For example, a QLabel should implement inherited events from QObject, QWidget and QFrame, as well as its own.
It's not a difficult job, there's just a lot of it!
Having a rummage through the GtkMaxGUI source, it is incredibly messy down there. I think the main reason is that raw Gtk is harder to use generically.
As an example to show how easy it is to create a CheckBox for use in MaxGUI, here's some basic code :
Type MaxGuiQCheckBoxButton Extends QCheckBox
Field gadget:TQtGadget
Method MCreate:MaxGuiQCheckBoxButton(text:String, parent:QWidget, owner:TQtGadget)
gadget = owner
Super.Create(text, parent)
Return Self
End Method
Method OnInit()
connect(Self, "toggled", Self, "onToggled")
End Method
Method onToggled()
PostGuiEvent EVENT_GADGETACTION, gadget, ButtonState(gadget)
End Method
End Type
We extend off the QCheckBox, and simply connect to the "toggled" signal, posting the subsequent event back into the MaxGUI event queue.
Notice too that we are only using pure BlitzMax code here. All the nitty-gritty interaction with low-level Qt is handled by QtMax. No externs, or glue code to be found!
Notice too that we are only using pure BlitzMax code here. All the nitty-gritty interaction with low-level Qt is handled by QtMax. No externs, or glue code to be found!
Friday, 21 December 2012
Resurrection
Some might call it the End of the World ™, but I like to think of it more as a resurrection, having recently sat down and got QtMax up and running with (the latest) Qt 4.8.4.
Funnily enough, it ran straight out of the box on Linux, and the apps happily ran headless across the network here at Brucey Central. For the other two platforms, I'm having to rebuild the shared objects/dlls and sort out the headers.
Once everything is running on the three platforms, it's time to start filling in the blanks...
Sunday, 20 December 2009
Back to the drawing board....
With the release of Qt 4.6, it's time to go back to the drawing board and get everything rebuilt and re-examined.
Apparently 4.6 has lots of exciting new features, and much improved WebKit support - which was already working rather well in 4.5.
Whether I can eek out all this through the wrapper, we'll have to wait and see.
First things first... I need to build it :-)
Monday, 19 October 2009
Piggyback
Usually when I'm wrapping a C++ class which requires me to callback into BlitzMax, I will subclass it, and override the specific methods to make those callbacks.
But sometimes, that's just not possible, because the object we want to have access to those callbacks has been created by something else - i.e. not by us!
It's always been a bit of a conundrum for me when I've come up against a class like this, as to what to do. Fortunately, I've recently conjured up a reasonably elegant solution which also happens to fit rather well into Qt's Signal/Slot model.
Essentially what I'm doing is piggybacking onto the object that I want, with a subclassed QObject of my own. We intercept the returned object, do a quick check to see if we are already piggybacking it, and if not, we attach ourselves to it, and pass it on.
During the attach phase, we plug into the object's destruction method, which we will use to detach and kill our own object. So in essence, once we are piggybacking, the object we've attached to takes ownership of our object. Fire-and-forget, if you will :-)
As is always the case, it is probably better to explain all this with an example.
For this example, we'll take a look at the QAction wrapper, which also happens to be the reference implementation.
Our wrapper class has a static method ('link') for checking if the object is already attached :
void MaxQAction::link(QAction * a) {
BBObject * handle = qfind(a);
if (handle == &bbNullObject) {
MaxQAction * action = new MaxQAction(a);
}
}
If we don't find the object in our internal map, we make the attachment. The MaxQAction() constructor creates a BlitzMax QAction object instance, and binds it to the original object. It then connects all the object's signals to our wrapper class slots.
When we receive a potentially new QAction, we simply call 'link', and the attaching is handled silently in the background :
QAction * action = group->addAction(a);
MaxQAction::link(action);
return action;
Most importantly, all this is done behind the scenes, so that all you need to do is write your BlitzMax code, and it will all "just work".
Subscribe to:
Posts (Atom)