4.4. Реализация меню Edit.

Приступим к созданию слотов, соответствующих пунктам меню Edit.

void Spreadsheet::cut() { copy(); del(); } Слот cut() соответствует пункту меню Edit|Cut. Реализация слота чрезвычайно проста, поскольку логика работы соответствует последовательности команд Copy и Delete.

Рисунок 4.6. Меню Edit приложения Spreadsheet.


void Spreadsheet::copy() { QTableSelection sel = selection(); QString str; for (int i = 0; i < sel.numRows(); ++i) { if (i > 0) str += "\n"; for (int j = 0; j < sel.numCols(); ++j) { if (j > 0) str += "\t"; str += formula(sel.topRow() + i, sel.leftCol() + j); } } QApplication::clipboard()->setText(str); } Слот copy() соответствует пункту меню Edit|Copy. Здесь осуществляется обход ячеек в выделенной области. Формула каждой из выбранных ячеек добавляется к QString, где ячейки, находящиеся в одной строке, разделяются символом табуляции, а строки отделяются символом перевода строки.

Рисунок 4.7. Копирование выделенной области в буфер обмена.


Доступ к системному буферу обмена в Qt осуществляется через статическую функцию QApplication::clipboard(). Вызовом QClipboard::setText() мы помещаем содержимое QString в буфер обмена. Выбранный нами формат строки, где в качестве разделителя ячеек используется символ табуляции, а в качестве разделителя строк -- символ перевода строки, могут воспринимать и другие приложения, в том числе Microsoft Excel. QTableSelection Spreadsheet::selection() { if (QTable::selection(0).isEmpty()) return QTableSelection(currentRow(), currentColumn(), currentRow(), currentColumn()); return QTable::selection(0); } Функция selection() возвращает границы выделенной области. Она обращается к QTable::selection(), которая возвращает выделенную область по ее номеру. Поскольку мы ранее установили режим выделения Single, то в нашем приложении может существовать только одна область выделения -- это область с номером 0. Но возможен вариант, когда в таблице нет выделенной области. QTable не рассматривает текущую ячейку как выделенную область. Это вполне разумно, но в данном случае -- немного неудобно. Поэтому, в случае, когда нет выделенной области, функция selection() вернет текущую ячейку. void Spreadsheet::paste() { QTableSelection sel = selection(); QString str = QApplication::clipboard()->text(); QStringList rows = QStringList::split("\n", str, true); int numRows = rows.size(); int numCols = rows.first().contains("\t") + 1; if (sel.numRows() * sel.numCols() != 1 && (sel.numRows() != numRows || sel.numCols() != numCols)) { QMessageBox::information(this, tr("Spreadsheet"), tr("The information cannot be pasted because the " "copy and paste areas aren't the same size.")); return; } for (int i = 0; i < numRows; ++i) { QStringList cols = QStringList::split("\t", rows[i], true); for (int j = 0; j < numCols; ++j) { int row = sel.topRow() + i; int col = sel.leftCol() + j; if (row < NumRows && col < NumCols) setFormula(row, col, cols[j]); } } somethingChanged(); } Слот paste() соответствует пункту меню Edit|Paste. Сначала принимается текст из буфера обмена. Затем он переносится в QStringList, с разбивкой по строкам, вызовом статической функции QStringList::split().

Далее определяется размерность области копирования. Количество строк в таблице соответствует количеству строк в QStringList, а количество столбцов -- на один больше, чем количество символов табуляции в первой строке.

Если выбрана только одна ячейка, то она используется как верхний левый угол области вставки, если имеется выделенная область, то вставка осуществляется в нее.

В процессе вставки, каждая строка разбивается на ячейки, вызовом QStringList::split(), но на этот раз в качестве разделителя используется символ табуляции. Рисунок 4.8 демонстрирует процесс вставки данных в таблицу из буфера обмена.

Рисунок 4.8. Вставка текста из буфера обмена в таблицу.


void Spreadsheet::del() { QTableSelection sel = selection(); for (int i = 0; i < sel.numRows(); ++i) { for (int j = 0; j < sel.numCols(); ++j) delete cell(sel.topRow() + i, sel.leftCol() + j); } clearSelection(); } Слот del() соответствует пункту меню Edit|Delete. Для того, чтобы очистить ячейку, достаточно просто удалить объект Cell. Когда QTable обнаруживает удаление какого либо из QTableItem, то она автоматически перерисовывает себя на экране. Если после удаления ячейки вызвать cell(), то она вернет пустой указатель. void Spreadsheet::selectRow() { clearSelection(); QTable::selectRow(currentRow()); } void Spreadsheet::selectColumn() { clearSelection(); QTable::selectColumn(currentColumn()); } void Spreadsheet::selectAll() { clearSelection(); selectCells(0, 0, NumRows - 1, NumCols - 1); } Слоты selectRow(), selectColumn(), selectAll() соответствуют пунктам меню Edit|Select|Row, Edit|Select|Column, Edit|Select|All. функциональность этих слотов основана на функциях QTable: selectRow(), selectColumn(), selectCells(). void Spreadsheet::findNext(const QString &str, bool caseSensitive) { int row = currentRow(); int col = currentColumn() + 1; while (row < NumRows) { while (col < NumCols) { if (text(row, col).contains(str, caseSensitive)) { clearSelection(); setCurrentCell(row, col); setActiveWindow(); return; } ++col; } col = 0; ++row; } qApp->beep(); } Слот findNext() начинает поиск с ячейки, стоящей справа от текущей и двигается вправо до конца строки. Затем переходит на следующую строку, продолжая поиск с первой ячейки следующей строки и так далее до тех пор, пока не будет найден искомый текст или пока не будет достигнут конец таблицы. Например, если текущая ячейка C27, то поиск начинается с ячейки D27 и далее проверяются ячейки E27, F27, ..., Z27, затем A28, B28, C28, ..., Z28 и так далее, до ячейки Z999. Как только искомый текст будет обнаружен -- сбрасывается выделение, курсор перемещается в ячейку, содержимое которой совпало с искомым текстом, и активизируется окно с таблицей. Если поиск не увенчался успехом, то выдается звуковой сигнал, извещающий о том, что искомый текст не найден. void Spreadsheet::findPrev(const QString &str, bool caseSensitive) { int row = currentRow(); int col = currentColumn() - 1; while (row >= 0) { while (col >= 0) { if (text(row, col).contains(str, caseSensitive)) { clearSelection(); setCurrentCell(row, col); setActiveWindow(); return; } --col; } col = NumCols - 1; --row; } qApp->beep(); } Слот findPrev() очень похож на findNext(), только поиск ведется в обратном направлении и заканчивается по достижении ячейки A1.