/*************************************************************************** * Copyright (c) Eivind Kvedalen (eivind@kvedalen.name) 2015 * * * * This file is part of the FreeCAD CAx development system. * * * * This library is free software; you can redistribute it and/or * * modify it under the terms of the GNU Library General Public * * License as published by the Free Software Foundation; either * * version 2 of the License, or (at your option) any later version. * * * * This library is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU Library General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this library; see the file COPYING.LIB. If not, * * write to the Free Software Foundation, Inc., 59 Temple Place, * * Suite 330, Boston, MA 02111-1307, USA * * * ***************************************************************************/ #include "PreCompiled.h" #ifndef _PreComp_ # include # include # include # include # include # include #endif #include #include #include #include #include #include #include "../App/Utils.h" #include "../App/Cell.h" #include #include "SheetTableView.h" #include "LineEdit.h" #include "PropertiesDialog.h" using namespace SpreadsheetGui; using namespace Spreadsheet; using namespace App; void SheetViewHeader::mouseReleaseEvent(QMouseEvent *event) { QHeaderView::mouseReleaseEvent(event); resizeFinished(); } bool SheetViewHeader::viewportEvent(QEvent *e) { if(e->type() == QEvent::ContextMenu) { auto *ce = static_cast(e); int section = logicalIndexAt(ce->pos()); if(section>=0) { if(orientation() == Qt::Horizontal) { if(!owner->selectionModel()->isColumnSelected(section,owner->rootIndex())) { owner->clearSelection(); owner->selectColumn(section); } }else if(!owner->selectionModel()->isRowSelected(section,owner->rootIndex())) { owner->clearSelection(); owner->selectRow(section); } } } return QHeaderView::viewportEvent(e); } SheetTableView::SheetTableView(QWidget *parent) : QTableView(parent) , sheet(0) { QAction * insertRows = new QAction(tr("Insert rows"), this); QAction * removeRows = new QAction(tr("Remove rows"), this); QAction * insertColumns = new QAction(tr("Insert columns"), this); QAction * removeColumns = new QAction(tr("Remove columns"), this); setHorizontalHeader(new SheetViewHeader(this,Qt::Horizontal)); setVerticalHeader(new SheetViewHeader(this,Qt::Vertical)); setVerticalScrollMode(QAbstractItemView::ScrollPerPixel); setHorizontalScrollMode(QAbstractItemView::ScrollPerPixel); horizontalHeader()->addAction(insertColumns); horizontalHeader()->addAction(removeColumns); horizontalHeader()->setContextMenuPolicy(Qt::ActionsContextMenu); verticalHeader()->addAction(insertRows); verticalHeader()->addAction(removeRows); verticalHeader()->setContextMenuPolicy(Qt::ActionsContextMenu); connect(insertRows, SIGNAL(triggered()), this, SLOT(insertRows())); connect(insertColumns, SIGNAL(triggered()), this, SLOT(insertColumns())); connect(removeRows, SIGNAL(triggered()), this, SLOT(removeRows())); connect(removeColumns, SIGNAL(triggered()), this, SLOT(removeColumns())); QAction * cellProperties = new QAction(tr("Properties..."), this); addAction(cellProperties); setContextMenuPolicy(Qt::ActionsContextMenu); setTabKeyNavigation(false); connect(cellProperties, SIGNAL(triggered()), this, SLOT(cellProperties())); } void SheetTableView::cellProperties() { std::unique_ptr dialog(new PropertiesDialog(sheet, selectedRanges(), this)); if (dialog->exec() == QDialog::Accepted) { dialog->apply(); } } std::vector SheetTableView::selectedRanges() const { QModelIndexList list = selectionModel()->selectedIndexes(); std::vector result; // Insert selected cells into set. This variable should ideally be a hash_set // but that is not part of standard stl. std::set > cells; for (QModelIndexList::const_iterator it = list.begin(); it != list.end(); ++it) cells.insert(std::make_pair((*it).row(), (*it).column())); // Create rectangular cells from the unordered collection of selected cells std::map, std::pair > rectangles; createRectangles(cells, rectangles); std::map, std::pair >::const_iterator i = rectangles.begin(); for (; i != rectangles.end(); ++i) { std::pair ul = (*i).first; std::pair size = (*i).second; result.emplace_back(ul.first, ul.second, ul.first + size.first - 1, ul.second + size.second - 1); } return result; } void SheetTableView::insertRows() { assert(sheet != 0); QModelIndexList rows = selectionModel()->selectedRows(); std::vector sortedRows; /* Make sure rows are sorted in ascending order */ for (QModelIndexList::const_iterator it = rows.begin(); it != rows.end(); ++it) sortedRows.push_back(it->row()); std::sort(sortedRows.begin(), sortedRows.end()); /* Insert rows */ Gui::Command::openCommand("Insert rows"); std::vector::const_reverse_iterator it = sortedRows.rbegin(); while (it != sortedRows.rend()) { int prev = *it; int count = 1; /* Collect neighbouring rows into one chunk */ ++it; while (it != sortedRows.rend()) { if (*it == prev - 1) { prev = *it; ++count; ++it; } else break; } Gui::cmdAppObjectArgs(sheet, "insertRows('%s', %d)", rowName(prev).c_str(), count); } Gui::Command::commitCommand(); Gui::Command::doCommand(Gui::Command::Doc, "App.ActiveDocument.recompute()"); } void SheetTableView::removeRows() { assert(sheet != 0); QModelIndexList rows = selectionModel()->selectedRows(); std::vector sortedRows; /* Make sure rows are sorted in descending order */ for (QModelIndexList::const_iterator it = rows.begin(); it != rows.end(); ++it) sortedRows.push_back(it->row()); std::sort(sortedRows.begin(), sortedRows.end(), std::greater()); /* Remove rows */ Gui::Command::openCommand("Remove rows"); for (std::vector::const_iterator it = sortedRows.begin(); it != sortedRows.end(); ++it) { Gui::cmdAppObjectArgs(sheet, "removeRows('%s', %d)", rowName(*it).c_str(), 1); } Gui::Command::commitCommand(); Gui::Command::doCommand(Gui::Command::Doc, "App.ActiveDocument.recompute()"); } void SheetTableView::insertColumns() { assert(sheet != 0); QModelIndexList cols = selectionModel()->selectedColumns(); std::vector sortedColumns; /* Make sure rows are sorted in ascending order */ for (QModelIndexList::const_iterator it = cols.begin(); it != cols.end(); ++it) sortedColumns.push_back(it->column()); std::sort(sortedColumns.begin(), sortedColumns.end()); /* Insert columns */ Gui::Command::openCommand("Insert columns"); std::vector::const_reverse_iterator it = sortedColumns.rbegin(); while (it != sortedColumns.rend()) { int prev = *it; int count = 1; /* Collect neighbouring columns into one chunk */ ++it; while (it != sortedColumns.rend()) { if (*it == prev - 1) { prev = *it; ++count; ++it; } else break; } Gui::cmdAppObjectArgs(sheet, "insertColumns('%s', %d)", columnName(prev).c_str(), count); } Gui::Command::commitCommand(); Gui::Command::doCommand(Gui::Command::Doc, "App.ActiveDocument.recompute()"); } void SheetTableView::removeColumns() { assert(sheet != 0); QModelIndexList cols = selectionModel()->selectedColumns(); std::vector sortedColumns; /* Make sure rows are sorted in descending order */ for (QModelIndexList::const_iterator it = cols.begin(); it != cols.end(); ++it) sortedColumns.push_back(it->column()); std::sort(sortedColumns.begin(), sortedColumns.end(), std::greater()); /* Remove columns */ Gui::Command::openCommand("Remove rows"); for (std::vector::const_iterator it = sortedColumns.begin(); it != sortedColumns.end(); ++it) Gui::cmdAppObjectArgs(sheet, "removeColumns('%s', %d)", columnName(*it).c_str(), 1); Gui::Command::commitCommand(); Gui::Command::doCommand(Gui::Command::Doc, "App.ActiveDocument.recompute()"); } SheetTableView::~SheetTableView() { } void SheetTableView::updateCellSpan(CellAddress address) { int rows, cols; sheet->getSpans(address, rows, cols); if (rows != rowSpan(address.row(), address.col()) || cols != columnSpan(address.row(), address.col())) setSpan(address.row(), address.col(), rows, cols); } void SheetTableView::setSheet(Sheet * _sheet) { sheet = _sheet; cellSpanChangedConnection = sheet->cellSpanChanged.connect(bind(&SheetTableView::updateCellSpan, this, _1)); // Update row and column spans std::vector usedCells = sheet->getUsedCells(); for (std::vector::const_iterator i = usedCells.begin(); i != usedCells.end(); ++i) { CellAddress address(*i); if (sheet->isMergedCell(address)) updateCellSpan(address); } // Update column widths and row height std::map columWidths = sheet->getColumnWidths(); for (std::map::const_iterator i = columWidths.begin(); i != columWidths.end(); ++i) { int newSize = i->second; if (newSize > 0 && horizontalHeader()->sectionSize(i->first) != newSize) setColumnWidth(i->first, newSize); } std::map rowHeights = sheet->getRowHeights(); for (std::map::const_iterator i = rowHeights.begin(); i != rowHeights.end(); ++i) { int newSize = i->second; if (newSize > 0 && verticalHeader()->sectionSize(i->first) != newSize) setRowHeight(i->first, newSize); } } void SheetTableView::commitData ( QWidget * editor ) { QTableView::commitData(editor); } bool SheetTableView::edit ( const QModelIndex & index, EditTrigger trigger, QEvent * event ) { if (trigger & (QAbstractItemView::DoubleClicked | QAbstractItemView::AnyKeyPressed | QAbstractItemView::EditKeyPressed) ) currentEditIndex = index; return QTableView::edit(index, trigger, event); } bool SheetTableView::event(QEvent *event) { /* Catch key presses for navigating the table; Enter/Return (+Shift), and Tab (+Shift) */ if (event && event->type() == QEvent::KeyPress) { QKeyEvent * kevent = static_cast(event); if (kevent->key() == Qt::Key_Tab) { QModelIndex c = currentIndex(); if (kevent->modifiers() == 0) { setCurrentIndex(model()->index(c.row(), qMin(c.column() + 1, model()->columnCount() -1))); return true; } } else if (kevent->key() == Qt::Key_Backtab) { QModelIndex c = currentIndex(); if (kevent->modifiers() == Qt::ShiftModifier) { setCurrentIndex(model()->index(c.row(), qMax(c.column() - 1, 0))); return true; } } else if (kevent->key() == Qt::Key_Enter || kevent->key() == Qt::Key_Return) { QModelIndex c = currentIndex(); if (kevent->modifiers() == 0) { setCurrentIndex(model()->index(qMin(c.row() + 1, model()->rowCount() - 1), c.column())); return true; } else if (kevent->modifiers() == Qt::ShiftModifier) { setCurrentIndex(model()->index(qMax(c.row() - 1, 0), c.column())); return true; } } else if (kevent->key() == Qt::Key_Delete) { deleteSelection(); return true; } else if (kevent->matches(QKeySequence::Cut)) { cutSelection(); return true; } else if (kevent->matches(QKeySequence::Copy)) { copySelection(); return true; } else if (kevent->matches(QKeySequence::Paste)) { pasteClipboard(); return true; } } else if (event && event->type() == QEvent::ShortcutOverride) { QKeyEvent * kevent = static_cast(event); if (kevent->modifiers() == Qt::NoModifier || kevent->modifiers() == Qt::ShiftModifier || kevent->modifiers() == Qt::KeypadModifier) { switch (kevent->key()) { case Qt::Key_Return: case Qt::Key_Enter: case Qt::Key_Delete: case Qt::Key_Home: case Qt::Key_End: case Qt::Key_Backspace: case Qt::Key_Left: case Qt::Key_Right: case Qt::Key_Up: case Qt::Key_Down: case Qt::Key_Tab: kevent->accept(); default: break; } if (kevent->key() < Qt::Key_Escape) { kevent->accept(); } } if (kevent->matches(QKeySequence::Cut)) { kevent->accept(); } else if (kevent->matches(QKeySequence::Copy)) { kevent->accept(); } else if (kevent->matches(QKeySequence::Paste)) { kevent->accept(); } } return QTableView::event(event); } void SheetTableView::deleteSelection() { QModelIndexList selection = selectionModel()->selectedIndexes(); if (selection.size() > 0) { Gui::Command::openCommand("Clear cell(s)"); std::vector ranges = selectedRanges(); std::vector::const_iterator i = ranges.begin(); for (; i != ranges.end(); ++i) { Gui::Command::doCommand(Gui::Command::Doc,"App.ActiveDocument.%s.clear('%s')", sheet->getNameInDocument(), i->rangeString().c_str()); } Gui::Command::commitCommand(); Gui::Command::doCommand(Gui::Command::Doc, "App.ActiveDocument.recompute()"); } } static const QLatin1String _SheetMime("application/x-fc-spreadsheet"); void SheetTableView::copySelection() { QModelIndexList selection = selectionModel()->selectedIndexes(); int minRow = INT_MAX; int maxRow = 0; int minCol = INT_MAX; int maxCol = 0; for (auto it : selection) { int row = it.row(); int col = it.column(); minRow = std::min(minRow, row); maxRow = std::max(maxRow, row); minCol = std::min(minCol, col); maxCol = std::max(maxCol, col); } QString selectedText; for (int i=minRow; i<=maxRow; i++) { for (int j=minCol; j<=maxCol; j++) { QModelIndex index = model()->index(i,j); QString cell = index.data(Qt::EditRole).toString(); if (j < maxCol) cell.append(QChar::fromLatin1('\t')); selectedText += cell; } if (i < maxRow) selectedText.append(QChar::fromLatin1('\n')); } Base::StringWriter writer; sheet->getCells()->copyCells(writer,selectedRanges()); QMimeData *mime = new QMimeData(); mime->setText(selectedText); mime->setData(_SheetMime,QByteArray(writer.getString().c_str())); QApplication::clipboard()->setMimeData(mime); } void SheetTableView::cutSelection() { copySelection(); deleteSelection(); } void SheetTableView::pasteClipboard() { const QMimeData* mimeData = QApplication::clipboard()->mimeData(); if(!mimeData || !mimeData->hasText()) return; if(selectionModel()->selectedIndexes().size()>1) { QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Spreadsheet"), QObject::tr("Spreadsheet does not support range selection when pasting.\n" "Please select one cell only.")); return; } QModelIndex current = currentIndex(); App::AutoTransaction committer("Paste cell"); try { if (!mimeData->hasFormat(_SheetMime)) { QStringList cells; QString text = mimeData->text(); int i=0; for (auto it : text.split(QLatin1Char('\n'))) { QStringList cols = it.split(QLatin1Char('\t')); int j=0; for (auto jt : cols) { QModelIndex index = model()->index(current.row()+i, current.column()+j); model()->setData(index, jt); j++; } i++; } }else{ QByteArray res = mimeData->data(_SheetMime); Base::ByteArrayIStreambuf buf(res); std::istream in(0); in.rdbuf(&buf); Base::XMLReader reader("", in); sheet->getCells()->pasteCells(reader,CellAddress(current.row(),current.column())); } GetApplication().getActiveDocument()->recompute(); }catch(Base::Exception &e) { e.ReportException(); QMessageBox::critical(Gui::getMainWindow(), QObject::tr("Copy & Paste failed"), QString::fromLatin1(e.what())); } } void SheetTableView::closeEditor(QWidget * editor, QAbstractItemDelegate::EndEditHint hint) { SpreadsheetGui::LineEdit * le = qobject_cast(editor); currentEditIndex = QModelIndex(); QTableView::closeEditor(editor, hint); setCurrentIndex(le->next()); } void SheetTableView::edit ( const QModelIndex & index ) { currentEditIndex = index; QTableView::edit(index); } #include "moc_SheetTableView.cpp"