4.5. Реализация других меню.

Теперь перейдем к рассмотрению реализации слотов меню Tools и Options.

Рисунок 4.9. Меню Tools и Options.


void Spreadsheet::recalculate() { int row; for (row = 0; row < NumRows; ++row) { for (int col = 0; col < NumCols; ++col) { if (cell(row, col)) cell(row, col)->setDirty(); } } for (row = 0; row < NumRows; ++row) { for (int col = 0; col < NumCols; ++col) { if (cell(row, col)) updateCell(row, col); } } } Слот recalculate() соответствует пункту меню Tools|Recalculate. Он, кроме того, в случае необходимости, вызывается программой автоматически.

В первой группе циклов все ячейки помечаются, вызовом setDirty(), как требующие пересчета. В результате, когда в следующий раз QTable вызовет метод text() ячейки Cell, то ее содержимое будет пересчитано.

Вторая группа циклов выполняет updateCell() каждой ячейки, чтобы перерисовать ее. В результате, QTable вызовет метод text() ячейки Cell, чтобы получить ее значение, а поскольку все ячейки были помечены вызовом setDirty(), то будет выполнен пересчет.

void Spreadsheet::setAutoRecalculate(bool on) { autoRecalc = on; if (autoRecalc) recalculate(); } Слот setAutoRecalculate() соответствует пункту меню Options|Auto-recalculate. Если этот флаг включен, то как только в таблице появляются какие либо изменения, выполняется автоматический пересчет всех ячеек таблицы. В этом случае, recalculate() вызывается из somethingChanged(). void Spreadsheet::sort(const SpreadsheetCompare &compare) { vector<QStringList> rows; QTableSelection sel = selection(); int i; for (i = 0; i < sel.numRows(); ++i) { QStringList row; for (int j = 0; j < sel.numCols(); ++j) row.push_back(formula(sel.topRow() + i, sel.leftCol() + j)); rows.push_back(row); } stable_sort(rows.begin(), rows.end(), compare); for (i = 0; i < sel.numRows(); ++i) { for (int j = 0; j < sel.numCols(); ++j) setFormula(sel.topRow() + i, sel.leftCol() + j, rows[i][j]); } clearSelection(); somethingChanged(); } Сортировка выполняется на выделенной области и переупорядочивает строки в соответствии с заданными ключами и порядком сортировки, хранящимися в объекте compare. Функция сортировки представляет каждую строку таблицы в виде QStringList, а выделенную область -- как массив строк. Класс vector<?> -- это стандартный класс C++, мы подробно опишем его в Главе 11. Для простоты будем выполнять сортировку по формулам, а не по значениям.

Рисунок 4.10. Сохранение выделенной области в виде массива строк.


Собственно сортировка выполняется стандартной, для C++, функцией stable_sort(). Она принимает начальный итератор, конечный итератор и функцию сравнения. Функция сравнения -- это такая функция, которая принимает два аргумента (два QStringList) и возвращает true, если первый аргумент "меньше чем" второй и false -- в противном случае. Объект compare, который мы передаем в stable_sort(), на самом деле не является функцией сравнения, но он может быть использован как таковая, а как -- мы вскоре увидим.

Рисунок 4.11. Перемещение отсортированных данных обратно в таблицу.


После того, как stable_sort() отсортирует массив строк, мы перемещаем данные обратно в таблицу, сбрасываем выделение и вызываем somethingChanged().

В spreadsheet.h, класс SpreadsheetCompare определен как:

class SpreadsheetCompare { public: bool operator()(const QStringList &row1, const QStringList &row2) const; enum { NumKeys = 3 }; int keys[NumKeys]; bool ascending[NumKeys]; }; Это особый класс, поскольку он реализует оператор (), что позволяет использовать его так, как будто это обычная функция. Такие классы называют функторами (functor). Чтобы до конца понять принцип работы функторов, рассмотрим простой пример: class Square { public: int operator()(int x) const { return x * x; } }; Класс Square реализует единственную функцию -- operator()(int), которая возвращает квадрат входного аргумента. Такое именование функции, а скажем не compute(int), дает нам возможность использовать экземпляр класса Square как обычную функцию: Square square; int y = square(5); Теперь вернемся к классу SpreadsheetCompare: QStringList row1, row2; SpreadsheetCompare compare; ... if (compare(row1, row2)) { // row1 меньше чем row2 } Отсюда видно, что объект compare может использоваться как обычная функция compare(). Дополнительно, он имеет доступ к параметрам сортировки, которые хранятся в виде переменных-членов.

Альтернативный подход вынудил бы нас хранить параметры сортировки в глобальных переменных и использовать обычную функцию сравнения. Это очень неэлегантное решение, которое может породить трудноуловимые ошибки. Функторы -- это более мощная идиома взаимодействия с шаблонными функциями, такими как stable_sort(). Ниже приводится реализация функции, которая сравнивает две строки таблицы:

bool SpreadsheetCompare::operator()(const QStringList &row1, const QStringList &row2) const { for (int i = 0; i < NumKeys; ++i) { int column = keys[i]; if (column != -1) { if (row1[column] != row2[column]) { if (ascending[i]) return row1[column] < row2[column]; else return row1[column] > row2[column]; } } } return false; } Она возвращает true, если первая строка "меньше" чем вторая, и false -- в противном случае. Стандартная функция stable_sort() использует результат сравнения для выполнения сортировки.

Массивы keys и ascending, заполняются внутри функции MainWindow::sort() (описаной в Главе 2). Каждый ключ сортировки -- это индекс столбца или -1 (в случае "None").

Сравнению подвергаются части строк, соответствующие ячейкам, заданным ключами сортировки, с учетом порядка сортировки. В зависимости от того были найдены отличия или нет -- возвращается значение true или false. Если строки равны, то возвращается false.

На этом мы завершаем рассмотрение класса Spreadsheet. В следующем разделе мы обсудим класс Cell. Он используется для хранения формулы и реализует свой метод text(), который используется для получения значения ячейки.