На этом шаге рассмотрим реализацию пунктов меню Инструменты и Настройки в приложении Электронная таблица.
Вид меню Tools и Options (Инструменты и Настройки), приведен на рисунке 1.

Рис.1. Меню Tools и Options приложения Электронная таблица
Слот recalculate() соответствует пункту меню Tools | Recalculate (Инструменты | Пересчитать). Он также вызывается в Электронной таблице автоматически по мере необходимости.
void Spreadsheet::recalculate()
{
/*выполняем цикл по всем ячейкам и вызываем функцию setDirty(),
которая помечает каждую из них для перерасчета значения. Следующий раз, когда
QTableWidget для получения отображаемого в электронной таблице значения вызовет
text() для некоторой ячейки Cell, значение этой ячейки будет пересчитано*/
for (int row = 0; row < RowCount; ++row) {
for (int column = 0; column < ColumnCount; ++column) {
if (cell(row, column))
cell(row, column)->setDirty();
}
}
/*вызываем для области отображения функцию update() перерисовки
всей электронной таблицы. При этом используемый в QTableWidget программный
код по перерисовке вызывает функцию text() для каждой видимой ячейки для
получения отображаемого значения. Поскольку функция setDirty() вызывалась
нами для каждой ячейки, в вызовах text() будет использовано новое
рассчитанное значение. В этом случае может потребоваться расчет невидимых
ячеек, который будет проводиться до тех пор, пока не будут рассчитаны
все ячейки, влияющие на правильное отображение текста в перерассчитанной
области отображения. Этот расчет выполняется в классе Cell*/
viewport()->update();
}Слот setAutoRecalculate() соответствует пункту меню Options | Auto-Recalculate (Настройки | Автоподсчет). Если эта опция включена, мы сразу же пересчитаем всю электронную таблицу и будем уверены, что она показывает обновленные значения; впоследствии функция recalculate() будет автоматически вызываться из somethingChanged().
void Spreadsheet::setAutoRecalculate(bool recalc)
{
autoRecalc = recalc;
if (autoRecalc)
recalculate();
}Нам не нужно реализовывать специальную функцию для пункта меню Options | Show Grid (Настройки | Показать сетку), поскольку в QTableWidget уже содержится слот setShowGrid(), который наследуется от QTableView. Остается только реализовать функцию Spreadsheet::sort(), которая вызывается из MainWindow::sort():
void Spreadsheet::sort(const SpreadsheetCompare &compare)
{
/*сортировка работает на текущей выделенной области и переупорядочивает
строки в соответствии со значениями ключей порядка сортировки и хранящимися
в объекте compare. Представляем каждую строку данных в QStringList,
а выделенную область храним в виде списка строк*/
QList<QStringList> rows;
QTableWidgetSelectionRange range = selectedRange();
int i;
for (i = 0; i < range.rowCount(); ++i) {
QStringList row;
for (int j = 0; j < range.columnCount(); ++j)
row.append(formula(range.topRow() + i,
range.leftColumn() + j));
rows.append(row);
}
/*используем алгоритм Qt qStableSort() и для простоты сортируем по выражениям
формул, а не по их значениям. В качестве аргументов функции qStableSort()
используется итератор начала, итератор конца и функция сравнения. Функция
сравнения имеет два аргумента (оба имеют тип QStringLists), и она возвращает
true, когда первый аргумент «больше, чем» второй аргумент, и false в
противном случае. Передаваемый как функция сравнения объект compare
фактически не является функцией, но может использоваться и в таком качестве*/
qStableSort(rows.begin(), rows.end(), compare);
//помещаем данные обратно в таблицу
for (i = 0; i < range.rowCount(); ++i) {
for (int j = 0; j < range.columnCount(); ++j)
setFormula(range.topRow() + i, range.leftColumn() + j,
rows[i][j]);
}
//сбрасываем выделение области
clearSelection();
//и вызываем функцию somethingChanged()
somethingChanged();
}Класс SpreadsheetCompare в spreadsheet.h определен следующим образом:
class SpreadsheetCompare
{
public:
bool operator()(const QStringList &row1,
const QStringList &row2) const;
enum { KeyCount = 3 };
int keys[KeyCount];
bool ascending[KeyCount];
};Класс SpreadsheetCompare является специальным классом, реализующим оператор (). Это позволяет нам применять этот класс в качестве функции. Такие классы называются объектами функций или функторами (functors).
Для лучшего понимания работы функторов мы сначала разберем простой пример.
class Square
{
public:
int operator()(int x) const { return x * x; }
};Класс Square содержит одну функцию, operator()(int), которая возвращает квадрат переданного ей значения параметра. Обозначая функцию в виде operator()(int), а не в виде, например, compute(int), мы получаем возможность применения объекта типа Square как функции:
Square square;
int у = square(5); //у равно 25Теперь рассмотрим пример с применением объекта SpreadsheetCompare.
QStringList rowl, row2;
QSpreadsheetCompare compare;
if (compare(row1, row2)) {
// строка rowl меньше, чем row2
}Объект compare можно использовать так же, как если бы он был обычной функцией comparе(). Кроме того, он может быть реализован таким образом, что будет осуществлять доступ ко всем ключам сортировки и всем признакам порядка сортировки, которые хранятся в переменных-членах класса.
Можно использовать другой подход, когда ключи сортировки и признаки порядка сортировки хранятся в глобальных переменных и используется функция обычного типа comparе(). Однако связь через глобальные переменные выглядит неизящно и может быть причиной тонких ошибок. Функторы представляют собой более мощное средство связи для таких функций-шаблонов, как qStableSort().
Ниже приводится реализация функции, которая применяется для сравнения двух строк электронной таблицы.
bool SpreadsheetCompare::operator()(const QStringList &row1,
const QStringList &row2) const
{
/*массивы keys и ascending объекта SpreadsheetCompare заполняются
при работе функции MainWindow::sort(). Каждый ключ содержит индекс столбца
или имеет значение -1 ("None" - нет значения)*/
for (int i = 0; i < KeyCount; ++i) {
int column = keys[i];
/*сравниваем значения соответствующих ячеек двух строк, учитывая
порядок ключей сортировки. Как только оказывается, что они различны, мы
возвращаем соответствующее значение: true или false. Если все значения
оказываются равными, мы возвращаем false. При совпадении значений функция
qStableSort() coxpaняет порядок до сортировки; если строка rowl
располагалась первоначально перед строкой row2 и ни одна из них не
оказалась "меньше другой", то в результате строка rowl
по-прежнему будет предшествовать строке row2. Именно этим функция
qStableSort() отличается от своего нестабильного "родственника" qSort()*/
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. Функция qStableSort() для выполнения сортировки использует результат этой функции.
На следующем шаге рассмотрим создание подкласса QTableWidgetItem.