What follows is a simple implementation. The number of columns is limited to 1, items are read-only, there is no DnD support, and the model only supports the Display role for item data. Needless to say, these features are easy enough to implement, and would only distract from the example of subclassing Qt::AbstractItemModel.
The Model
=begin rdoc
A basic tree model. The contents of all tree nodes are determined by the ModelItems, not the Model, so they may be loaded lazily.
=end
class Model < Qt::AbstractItemModel
signals 'dataChanged(const QModelIndex &, const QModelIndex &)'
=begin rdoc
The invisible root item in the tree.
=end
attr_reader :root
=begin rdoc
The invisible root item in the tree.
=end
attr_reader :root
def initialize(parent=nil)
super
@root = nil
end
=begin rdoc
Load data into Model. This just creates a few fake items as an example. A full implementation would create and fill the top-level items after creating.
Note: @root is created here in order to make clearing easy. A clear() method just needs to set root to ModelItem.new('').
Note: @root is created here in order to make clearing easy. A clear() method just needs to set root to ModelItem.new('').
=end
def load()
@root = ModelItem.new('',nil)
ModelItem.new('First', @root)
ModelItem.new('Second', @root)
end
=begin rdoc
This treats an invalid index (returned by Qt::ModelIndex.new) as the index of @root.
All other indexes have the item itself stored in the 'internalPointer' field.
See AbstractItemModel#createIndex.
=end
def itemFromIndex(index)
return @root if not index.valid?
index.internalPointer
end
=begin rdoc
Return the index of the parent item for 'index'.
The key here is to treat the invalid index (returned by Qt::ModelIndex.new) as the index of @root. All other (valid) indexes are generated by AbstractItemModel#createIndex. Note that the item itself is passed as the third parameter (internalPointer) to createIndex.
See ModelItem#parent and ModelItem#childRow.
=end def index(row, column=0, parent=Qt::ModelIndex.new)
item = itemFromIndex(parent)
if item
child = item.child(row)
return createIndex(row, column, child) if child
end
Qt::ModelIndex.new
end
=begin rdoc
Return the index of the parent item for 'index'.
This is made a bit complicated by the fact that the ModelIndex must be created by AbstractItemModel.
The parent of the parent is used to obtain the 'row' of the parent. If the parent is root, the invalid Modelndex is used as usual.
=end
def parent(index)
return Qt::ModelIndex.new if not index.valid?
item = itemFromIndex(index)
parent = item.parent
return Qt::ModelIndex.new if parent == @root
pparent = parent.parent
return Qt::ModelIndex.new if not pparent
createIndex(pparent.childRow(parent), 0, parent)
end
=begin rdoc
Return data for ModelItem. This only handles the case where Display Data (the text in the Tree) is requested.
=end
def data(index, role)
return Qt::Variant.new if (not index.valid?) or role != Qt::DisplayRole
item = itemFromIndex(index)
item ? item.data : Qt::Variant.new
end
=begin rdoc
Set data in a ModelItem. This is just an example to show how the signal is emitted.
=end
def data=(index, value, role)
return false if (not index.valid?) or role != Qt::DisplayRole
item = itemFromIndex(index)
return false if not item
item.data = value.to_s
emit dataChanged(index, index)
true
end
alias :setData :data=
=begin rdoc
Delegate rowCount to item.
See ModelItem#rowCount.
=end
def rowCount(index)
item = itemFromIndex(index)
item ? item.rowCount : 0
end
=begin rdoc
Only support 1 column
=end
def columnCount(index)
1
end=begin rdoc
All items can be enabled only.
=end
def flags(index)
Qt::ItemIsEnabled
end
=begin rdoc
Don't supply any header data.
=end
def headerData(section, orientation, role)
Qt::Variant.new
end
end
=begin rdoc
An example of a ModelItem for use in the above Model. Note that it does not need to descend from QObject.
The ModelItem consists of a data member (the text displayed in the tree), a parent ModelItem, and an array of child ModelItems. This array corresponds directly to the Model 'rows' owned by this item.
An example of a ModelItem for use in the above Model. Note that it does not need to descend from QObject.
The ModelItem consists of a data member (the text displayed in the tree), a parent ModelItem, and an array of child ModelItems. This array corresponds directly to the Model 'rows' owned by this item.
=end
class ModelItem
attr_accessor :data
attr_accessor :parent
attr_reader :children
def initialize(data, parent=nil)
@data = data
@parent = parent
@children = []
parent.addChild(self) if parent
end
=begin rdoc
Return the ModelItem at index 'row' in @children. This can be made lazy by using a data source (e.g. database, filesystem) instead of an array for @children.
=end
def child(row)
@children[row]
end
=begin rdoc
Return row of child that matches 'item'. This can be made lazy by using a data source (e.g. database, filesystem) instead of an array for @children.
=end
def childRow(item)
@children.index(item)
end
=begin rdoc
Return number of children. This can be made lazy by using a data source (e.g. database, filesystem) instead of an array for @children.
=end
def rowCount
@children.size
end
=begin rdoc
Used to determine if the item is expandible.
=end
def hasChildren
childCount > 0
end
=begin rdoc
Add a child to this ModelItem. This puts the item into @children.
=end
def addChild(item)
item.parent=self
@children << item
end
end
This should serve as a basic implementation of an AbstractItemModel.
Realistically, ModelItem would be subclassed to represent different types of items in the data source, each of which would also (likely) be subclassed from ModelItem. This allows a browsable tree to be created for navigating data hierarchies.
No comments:
Post a Comment