
Рис. 4.4. Меню Edit приложения Электронная таблица.
01 void Spreadsheet::cut()
02 {
03 copy();
04 del();
05 }
Слот cut() соответствует пункту меню Edit | Cut (Правка | Вырезать). Он реализуется просто, поскольку операция Cut выполняется с помощью операции Сору, за которой следует операция Delete.
01 void Spreadsheet::copy()
02 {
03 QTableWidgetSelectionRange range = selectedRange();
04 QString str;
05 for (int i = 0; i < range.rowCount(); ++i) {
06 if (i > 0)
07 str += "\n";
08 for (int j = 0; j < range.columnCount(); ++j) {
09 if (j > 0)
10 str += "\t";
11 str += formula(range.topRow() + i, range.leftColumn() + j);
12 }
13 }
14 QApplication::clipboard()->setText(str);
15 }
Слот copy() соответствует пункту меню Edit | Copy (Правка | Копировать). Он в цикле обрабатывает всю выделенную область ячеек (если нет явно выделенной области, то ею будет просто текущая ячейка). Формула каждой выделенной ячейки добавляется в QString, причем строки отделяются символом новой строки, а столбцы разделяются символом табуляции.
Доступ к буферу обмена в Qt осуществляется при помощи статической функции QApplication::clipboard(). Вызывая функцию QClipboard::setText(), мы делаем текст доступным через буфер обмена; причем этот текст могут использовать и данное, и другие приложения, поддерживающие работу с простыми текстами. Применяемый нами формат со знаками табуляции и новой строки в качестве разделителей понятен многим приложениям, включая Excel от компании Microsoft.

Рис. 4.5. Копирование выделенных ячеек в буфер обмена.
Функция QTableWidget::selectedRange() возвращает список выделенных диапазонов. Мы знаем, что может быть не более одного диапазона, потому что мы задали в конструкторе режим выделения QAbstractItemView::ContiguousSelection. Для удобства мы определяем функцию selectedRange(), которая возвращает выделенный диапазон:
01 QTableWidgetSelectionRange Spreadsheet::selectedRange() const
02 {
03 QList<QTableWidgetSelectionRange> ranges = selectedRanges();
04 if (ranges.isEmpty())
05 return QTableWidgetSelectionRange();
06 return ranges.first();
07 }
Если выделение вообще имеет место, мы возвращаем первую (и единственную) выделенную область. Мы никогда не встретимся с ситуацией, когда не выбрано никакой области, поскольку в режиме ContiguousSelection текущая ячейка рассматривается как выделенная. Однако такую ситуацию мы все же обрабатываем, чтобы защититься от ошибки в нашей программе, приводящей к отсутствию текущей ячейки.
01 void Spreadsheet::paste()
02 {
03 QTableWidgetSelectionRange range = selectedRange();
04 QString str = QApplication::clipboard()->text();
05 QStringList rows = str.split('\n');
06 int numRows = rows.count();
07 int numColumns = rows.first().count('\t') + 1;
08 if (range.rowCount() * range.columnCount() != 1
09 && (range.rowCount() != numRows
10 || range.columnCount() !=numColumns)) {
11 QMessageBox::information(this, tr("Spreadsheet"),
12 tr("The information cannot be pasted because the copy "
13 "and paste areas aren't the same size."));
14 return;
15 }
16 for (int i = 0; i < numRows; ++i) {
17 QStringList columns = rows[i].split('\t');
18 for (int j = 0; j < numColumns; ++j) {
19 int row = range.topRow() + i;
20 int column = range.leftColumn() + j;
21 if (row < RowCount && column < ColumnCount)
22 setFormula(row, column, columns[j]);
23 }
24 }
25 somethingChanged();
26 }
Слот paste() соответствует пункту меню Edit | Paste (Правка | Вставить). Мы считываем текст из буфера обмена и вызываем статическую функцию QString::split() для разбиения строки и представления ее в виде списка QStringList. Каждая строка таблицы представлена в этом списке одной строкой.
Затем мы определяем размеры области копирования. Номер строки в таблице является номером строки в QStringList; номер столбца является номером символа табуляции в первой строке плюс 1. Если выделена только одна ячейка, мы используем ее в качестве верхнего левого угла области вставки; в противном случае мы используем текущую выделенную область для вставки.
При выполнении операции вставки мы в цикле проходим по строкам и разбиваем каждую строку на значения ячеек, снова используя функцию QString::split(), но теперь в качестве разделителя применяется знак табуляции. Рис. 4.6 иллюстрирует эти действия.

Рис. 4.6. Вставка текста из буфера обмена в электронную таблицу.
01 void Spreadsheet::del()
02 {
03 foreach (QTableWidgetltem *item, selectedItems())
04 delete item;
05 }
Слот del() соответствует пункту меню Edit | Delete (Правка | Удалить). Для очистки ячеек достаточно использовать оператор delete для каждого объекта Cell. Объект QTableWidget замечает, когда удаляются его элементы QTableWidgetltem, и автоматически перерисовывает себя, если какой-нибудь из элементов оказывается видимым. Если мы вызываем функцию cell(), указывая координаты удаленной ячейки, то она возвратит нулевой указатель.
01 void Spreadsheet::selectCurrentRow()
02 {
03 selectRow(currentRow());
04 }
05 void Spreadsheet::selectCurrentColumn()
06 {
07 selectColumn(currentColumn());
08 }
Функции selectCurrentRow() и selectCurrentColumn() соответствуют пунктам меню Edit | Select | Row и Edit | Select | Column (Правка | Выделить | Строка и Правка | Выделить | Столбец). Здесь используется реализация функций selectRow() и selectColumn() класса QTableWidget. Нам не требуется реализовывать функциональность пункта меню Edit | Select | All (Правка | Выделить | Все), поскольку она обеспечивается в QTableWidget унаследованной функцией QAbstractItemView::selectAll().
01 void Spreadsheet::findNext(const QString &str, Qt::CaseSensitivity cs)
02 {
03 int row = currentRow();
04 int column = currentColumn() + 1;
05 while (row < RowCount) {
06 while (column < ColumnCount) {
07 if (text(row, column).contains(str, cs)) {
08 clearSelection();
09 setCurrentCell(row, column);
10 activateWindow();
11 return;
12 }
13 ++column;
14 }
15 column = 0;
16 ++row;
17 }
18 QApplication::beep();
19 }