Жасмин Бланшет - QT 4: программирование GUI на С++
01 int CityModel::offsetOf(int row, int column) const
02 {
03 if (row < column)
04 qSwap(row, column);
05 return (row * (row - 1) / 2) + column;
06 }
Закрытая функция offsetOf() вычисляет индекс заданной пары городов для вектора расстояний distances. Например, предположим, что мы имеем города А, В, С и D, и пользователь обновляет элемент со строкой 3 и столбцом 1, т. е. (B, D). Тогда индекс вектора расстояний будет равен 3 × (3 — 1) / 2 + 1 = 4. Если бы пользователь вместо этого изменил элемент со строкой 1 и столбцом 3, т.е. (D, В), благодаря применению функции qSwap(), выполнялись бы точно такие же вычисления и возвращалось бы то же самое значение.
Рис. 10.13. Структуры данных cities и distances и табличная модель.
Последний пример в данном разделе представляет собой модель, которая показывает дерево грамматического разбора заданного регулярного выражения. Регулярное выражение состоит из одного или нескольких термов, разделяемых символами '|'. Так, регулярное выражение «alpha|bravo|charlie» содержит три терма. Каждый терм представляет собой последовательность из одного или нескольких факторов: например, терм «bravo» состоит из пяти факторов (каждая буква является фактором). Факторы могут состоять из атома и необязательного квантификатора (quantifier), например '*', '+' и '?'. Поскольку регулярные выражения могут иметь подвыражения, заключенные в скобки, они могут быть представлены рекурсивными деревьями грамматического разбора.
Регулярное выражение, показанное на рис. 10.14, «ab|(cd)?e» означает, что за 'a' следует 'b' или допускается два варианта: за 'c' идет 'd' и затем 'e' или просто имеется 'e'. Поэтому подойдут строки «ab» и «cde», но не подойдут строки «bc» или «cd».
Рис. 10.14. Приложение Парсер регулярных выражений.
Приложение Парсер регулярных выражений (Regexp Parser) состоит из четырех классов:
• RegExpWindow — окно, которое позволяет пользователю вводить регулярное выражение и показывает соответствующее дерево грамматического разбора;
• RegExpParser формирует дерево грамматического разбора для заданного регулярного выражения;
• RegExpModel — модель дерева, используемая деревом грамматического разбора;
• Node (вершина) представляет один элемент в дереве грамматического разбора.
Давайте начнем с класса Node:
01 class Node {
02 public:
03 enum Type { RegExp, Expression, Term, Factor, Atom, Terminal };
04 Node(Type type, const QString &str = "");
05 ~Node();
06 Type type;
07 QString str;
08 Node *parent;
09 QList<Node *> children;
10 };
Каждая вершина имеет тип, строку (которая может быть пустой), ссылку на родительский элемент (которая может быть нулевой) и список дочерних вершин (который может быть пустым).
01 Node::Node(Type type, const QString &str)
02 {
03 this->type = type;
04 this->str = str;
05 parent = 0;
06 }
Конструктор просто инициализирует тип и строку вершины. Поскольку все данные открыты, в программном коде, использующим Node, можно непосредственно манипулировать типом, строкой, родительским элементом и дочерними элементами.
01 Node::~Node()
02 {
03 qDeleteAll(children);
04 }
Функция qDeleteAll() проходит no всем указателям контейнера и вызывает оператор delete для каждого из них. Она не устанавливает указатели в 0, поэтому, если она используется вне деструктора, обычно за ней следует вызов функции clear() для контейнера, содержащего указатели.
Теперь, когда мы определили элементы наших данных (представленные вершиной Node), мы готовы создать модель:
01 class RegExpModel : public QAbstractItemModel
02 {
03 public:
04 RegExpModel(QObject *parent = 0);
05 ~RegExpModel();
06 void setRootNode(Node *node);
07 QModelIndex index(int row, int column,
08 const QModelIndex &parent) const;
09 QModelIndex parent(const QModelIndex &child) const;
10 int rowCount(const QModelIndex &parent) const;
11 int columnCount(const QModelIndex &parent) const;
12 QVariant data(const QModelIndex &index, int role) const;
13 QVariant headerData(int section,
14 Qt::Orientation Orientation, int role) const;
15 private:
16 Node *nodeFromIndex(const QModelIndex &index) const;
17 Node *rootNode;
18 };
На этот раз мы построили подкласс на основе класса QAbstractItemModel, а не на основе его удобного подкласса QAbstractTableModel, потому что мы хотим создать иерархическую модель. Нам необходимо переопределить те же самые функции и, кроме того, требуется реализовать функции index() и parent(). Для установки данных модели предусмотрена функция setRootNode(), при вызове которой должна задаваться корневая вершина дерева грамматического разбора.
01 RegExpModel::RegExpModel(QObject *parent)
02 : QAbstractItemModel(parent)
03 {
04 rootNode = 0;
05 }
В конструкторе модели нам надо просто задать корневой вершине безопасное нулевое значение и передать указатель parent базовому классу.
01 RegExpModel::~RegExpModel()
02 {
03 delete rootNode;
04 }
В деструкторе мы удаляем корневую вершину. Если корневая вершина имеет дочерние вершины, то каждая из них удаляется и эта процедура повторяется рекурсивно деструктором Node.
01 void RegExpModel::setRootNode(Node *node)
02 {
03 delete rootNode;
04 rootNode = node;
05 reset();
06 }
При установке новой корневой вершины мы начинаем с удаления предыдущей корневой вершины (и всех ее дочерних вершин). Затем мы устанавливаем новое значение для корневой вершины и вызываем функцию reset() для уведомления всех представлений о необходимости обновления отображаемых данных всеми видимыми элементами.
01 QModelIndex RegExpModel::index(int row, int column,
02 const QModelIndex &parent) const
03 {
04 if (!rootNode)
05 return QModelIndex();
06 Node *parentNode = nodeFromIndex(parent);
07 return createIndex(row, column, parentNode->children[row]);
08 }
Функция index() класса QAbstractItemModel переопределяется. Она всегда вызывается, когда в модели или в представлении требуется создать индекс QModelIndex для конкретного дочернего элемента (или для элемента самого верхнего уровня, если parent имеет недействительное значение QModelIndex). В табличных и списковых моделях нам не требуется переопределять эту функцию, потому что обычно оказываются достаточным реализации по умолчанию моделей QAbstractListModel и QАЬstractTableModel.
В нашей реализации index(), если не задано дерево грамматического разбора, мы возвращаем недействительный индекс QModelIndex. В противном случае мы создаем QModelIndex с заданными строкой, столбцом и Node * для запрошенного дочернего элемента. В иерархических моделях знание строки и столбца элемента относительно своего родителя оказывается недостаточным для уникальной идентификации элемента; мы должны также знать, кто является его родителем. Для этого можно хранить в QModelIndex указатель на внутреннюю вершину. В объекте QModelIndex кроме номеров строк и столбцов допускается хранение указателя void * или значения типа int.
Указатель Node * на дочерний элемент можно получить из списка дочерних элементов children родительской вершины. Указатель на родительскую вершину извлекается из индекса модели parent, используя закрытую функцию nodeFromIndex():
01 Node *RegExpModel::nodeFromIndex(
02 const QModelIndex &index) const
03 {
04 if (index.isValid()) {
05 return static_cast<Node *>(index.internalPointer());
06 } else {
07 return rootNode;
07 }
Функция nodeFromIndex() приводит тип void * заданного индекса в тип Node * или возвращает указатель на корневую вершину, если индекс недостоверен, поскольку недостоверный индекс модели используется для представления корня модели.
01 int RegExpModel::rowCount(const QModelIndex
02 &parent) const
03 {
04 Node *parentNode = nodeFromlndex(parent);
05 if (!parentNode)
06 return 0;
07 return parentNode->children.count();
08 }
Число строк для заданного элемента определяется просто количеством дочерних элементов.
01 int RegExpModel::columnCount(const QModelIndex &
02 /* родительский элемент */) const
03 {