From 086c2778f9dec9ab96aeb39a0b572e30f064665e Mon Sep 17 00:00:00 2001 From: Chris Hennes Date: Tue, 12 Jan 2021 21:24:23 -0600 Subject: [PATCH 01/14] Add option to auto-show selection view The SelectionView dock window was already an observer of the current selection when it was showing. This changes it to continuously monitor the selection even when invisible. If the user sets the parameter BaseApp/Preferences/Selection/AutoShowSelectionView to true, when there is an item selected, the view will show itself if it was hidden, and will re-hide itself when the selection is cleared. The option has no effect if the user had chosen to manually show the selection view prior to beginning a selection. --- src/Gui/SelectionView.cpp | 23 ++++++++++++++++++----- src/Gui/SelectionView.h | 1 + 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/src/Gui/SelectionView.cpp b/src/Gui/SelectionView.cpp index 4459b02d60..f89d8d1526 100644 --- a/src/Gui/SelectionView.cpp +++ b/src/Gui/SelectionView.cpp @@ -55,8 +55,9 @@ using namespace Gui::DockWnd; SelectionView::SelectionView(Gui::Document* pcDocument, QWidget *parent) : DockWindow(pcDocument,parent) - , SelectionObserver(false,0) + , SelectionObserver(true,0) , x(0.0f), y(0.0f), z(0.0f) + , openedAutomatically(false) { setWindowTitle(tr("Selection View")); @@ -128,6 +129,22 @@ void SelectionView::leaveEvent(QEvent *) /// @cond DOXERR void SelectionView::onSelectionChanged(const SelectionChanges &Reason) { + ParameterGrp::handle hGrp = App::GetApplication().GetUserParameter().GetGroup("BaseApp") + ->GetGroup("Preferences")->GetGroup("Selection"); + bool autoShow = hGrp->GetBool("AutoShowSelectionView", false); + hGrp->SetBool("AutoShowSelectionView", autoShow); // Remove this line once the preferences window item is implemented + + if (autoShow) { + if (!parentWidget()->isVisible() && Selection().hasSelection()) { + parentWidget()->show(); + openedAutomatically = true; + } + else if (openedAutomatically && !Selection().hasSelection()) { + parentWidget()->hide(); + openedAutomatically = false; + } + } + QString selObject; QTextStream str(&selObject); if (Reason.Type == SelectionChanges::AddSelection) { @@ -608,14 +625,10 @@ bool SelectionView::onMsg(const char* /*pMsg*/,const char** /*ppReturn*/) } void SelectionView::hideEvent(QHideEvent *ev) { - FC_TRACE(this << " detaching selection observer"); - this->detachSelection(); DockWindow::hideEvent(ev); } void SelectionView::showEvent(QShowEvent *ev) { - FC_TRACE(this << " attaching selection observer"); - this->attachSelection(); enablePickList->setChecked(Selection().needPickedList()); Gui::DockWindow::showEvent(ev); } diff --git a/src/Gui/SelectionView.h b/src/Gui/SelectionView.h index ecb9a1dad5..da72d37298 100644 --- a/src/Gui/SelectionView.h +++ b/src/Gui/SelectionView.h @@ -110,6 +110,7 @@ private: private: float x,y,z; std::vector searchList; + bool openedAutomatically; }; } // namespace DockWnd From 8aa22d1a68684348157a1134abdaa1263ce469f1 Mon Sep 17 00:00:00 2001 From: Chris Hennes Date: Wed, 6 Oct 2021 23:37:20 -0500 Subject: [PATCH 02/14] [Spreadsheet] Refactor keyboard handling LineEdit no longer actually handles motion, it simply indicates which action was taken to cause it to lose focus (e.g. which key was pressed). It's up to the client code to determine what this means. This allows significant consolidation of keyboard-handling logic, and the implementation of more extensive keyboard navigation features. New keyboard shortcuts include a tab counter to implement auto-return, plus Ctrl->Arrow, End, Home, Ctrl-End, and Ctrl-Home, matching the behavior of OpenOffice, LibreOffice, etc. Block selection via keyboard has also been added by holding down the shift key during navigation with the arrow keys (this also works in combination with the Ctrl modifier for region navigation). --- src/Mod/Spreadsheet/App/PropertySheet.cpp | 8 + src/Mod/Spreadsheet/App/PropertySheet.h | 2 + src/Mod/Spreadsheet/App/Sheet.cpp | 5 + src/Mod/Spreadsheet/App/Sheet.h | 2 + src/Mod/Spreadsheet/Gui/LineEdit.cpp | 86 ++--- src/Mod/Spreadsheet/Gui/LineEdit.h | 14 +- src/Mod/Spreadsheet/Gui/SheetTableView.cpp | 326 +++++++++++++++--- src/Mod/Spreadsheet/Gui/SheetTableView.h | 5 + .../Spreadsheet/Gui/SpreadsheetDelegate.cpp | 24 +- src/Mod/Spreadsheet/Gui/SpreadsheetDelegate.h | 4 +- src/Mod/Spreadsheet/Gui/SpreadsheetView.cpp | 95 +++-- src/Mod/Spreadsheet/Gui/SpreadsheetView.h | 5 +- 12 files changed, 394 insertions(+), 182 deletions(-) diff --git a/src/Mod/Spreadsheet/App/PropertySheet.cpp b/src/Mod/Spreadsheet/App/PropertySheet.cpp index 8a6fa6542d..286b72890f 100644 --- a/src/Mod/Spreadsheet/App/PropertySheet.cpp +++ b/src/Mod/Spreadsheet/App/PropertySheet.cpp @@ -982,6 +982,14 @@ void PropertySheet::getSpans(CellAddress address, int & rows, int & cols) const } } +App::CellAddress Spreadsheet::PropertySheet::getAnchor(App::CellAddress address) const +{ + if (auto anchor = mergedCells.find(address); anchor != mergedCells.end()) + return anchor->second; + else + return address; +} + bool PropertySheet::isMergedCell(CellAddress address) const { return mergedCells.find(address) != mergedCells.end(); diff --git a/src/Mod/Spreadsheet/App/PropertySheet.h b/src/Mod/Spreadsheet/App/PropertySheet.h index 7e20cf6a7d..60694b85b5 100644 --- a/src/Mod/Spreadsheet/App/PropertySheet.h +++ b/src/Mod/Spreadsheet/App/PropertySheet.h @@ -148,6 +148,8 @@ public: void getSpans(App::CellAddress address, int &rows, int &cols) const; + App::CellAddress getAnchor(App::CellAddress address) const; + bool isMergedCell(App::CellAddress address) const; bool isHidden(App::CellAddress address) const; diff --git a/src/Mod/Spreadsheet/App/Sheet.cpp b/src/Mod/Spreadsheet/App/Sheet.cpp index 3442e5a9b7..561e88c303 100644 --- a/src/Mod/Spreadsheet/App/Sheet.cpp +++ b/src/Mod/Spreadsheet/App/Sheet.cpp @@ -1063,6 +1063,11 @@ bool Sheet::isMergedCell(CellAddress address) const return cells.isMergedCell(address); } +App::CellAddress Spreadsheet::Sheet::getAnchor(App::CellAddress address) const +{ + return cells.getAnchor(address); +} + /** * @brief Set column with of column \a col to \a width- * @param col Index of column. diff --git a/src/Mod/Spreadsheet/App/Sheet.h b/src/Mod/Spreadsheet/App/Sheet.h index 7b60256885..18e898b849 100644 --- a/src/Mod/Spreadsheet/App/Sheet.h +++ b/src/Mod/Spreadsheet/App/Sheet.h @@ -110,6 +110,8 @@ public: bool isMergedCell(App::CellAddress address) const; + App::CellAddress getAnchor(App::CellAddress address) const; + void setColumnWidth(int col, int width); int getColumnWidth(int col) const; diff --git a/src/Mod/Spreadsheet/Gui/LineEdit.cpp b/src/Mod/Spreadsheet/Gui/LineEdit.cpp index 2e6a00a2c6..3116dcc6f7 100644 --- a/src/Mod/Spreadsheet/Gui/LineEdit.cpp +++ b/src/Mod/Spreadsheet/Gui/LineEdit.cpp @@ -26,68 +26,58 @@ # include #endif +#include + #include "LineEdit.h" using namespace SpreadsheetGui; LineEdit::LineEdit(QWidget *parent) : Gui::ExpressionLineEdit(parent, false, true) - , current() - , deltaCol(0) - , deltaRow(0) + , lastKeyPressed(0) + , lastModifiers(0) { + setFocusPolicy(Qt::FocusPolicy::ClickFocus); +} + +bool LineEdit::eventFilter(QObject* object, QEvent* event) +{ + if (event && event->type() == QEvent::KeyPress) { + QKeyEvent* keyEvent = static_cast(event); + if (keyEvent->key() == Qt::Key_Tab) { + // Special tab handling -- must be done via a QApplication event filter, otherwise the widget + // system will always grab the tab events + if (completerActive()) { + hideCompleter(); + event->accept(); + return true; // To make sure this tab press doesn't do anything else + } + else { + lastKeyPressed = keyEvent->key(); + lastModifiers = keyEvent->modifiers(); + } + } + } + return false; // We don't usually actually "handle" the tab event, we just keep track of it } bool LineEdit::event(QEvent *event) { - if (event && event->type() == QEvent::KeyPress) { + if (event && event->type() == QEvent::FocusIn) { + qApp->installEventFilter(this); + } + else if (event && event->type() == QEvent::FocusOut) { + qApp->removeEventFilter(this); + if (lastKeyPressed) + Q_EMIT finishedWithKey(lastKeyPressed, lastModifiers); + lastKeyPressed = 0; + } + else if (event && event->type() == QEvent::KeyPress && !completerActive()) { QKeyEvent * kevent = static_cast(event); - - if (kevent->key() == Qt::Key_Tab) { - if (kevent->modifiers() == 0) { - deltaCol = 1; - deltaRow = 0; - Q_EMIT returnPressed(); - return true; - } - } - else if (kevent->key() == Qt::Key_Backtab) { - if (kevent->modifiers() == Qt::ShiftModifier) { - deltaCol = -1; - deltaRow = 0; - Q_EMIT returnPressed(); - return true; - } - } - else if (kevent->key() == Qt::Key_Enter || kevent->key() == Qt::Key_Return) { - if (kevent->modifiers() == 0) { - deltaCol = 0; - deltaRow = 1; - Q_EMIT returnPressed(); - return true; - } - else if (kevent->modifiers() == Qt::ShiftModifier) { - deltaCol = 0; - deltaRow = -1; - Q_EMIT returnPressed(); - return true; - } - } + lastKeyPressed = kevent->key(); + lastModifiers = kevent->modifiers(); } return Gui::ExpressionLineEdit::event(event); } -void LineEdit::setIndex(QModelIndex _current) -{ - current = _current; -} - -QModelIndex LineEdit::next() const -{ - const QAbstractItemModel * m = current.model(); - - return m->index(qMin(qMax(0, current.row() + deltaRow), m->rowCount() - 1 ), - qMin(qMax(0, current.column() + deltaCol), m->columnCount() - 1 ) ); -} - #include "moc_LineEdit.cpp" diff --git a/src/Mod/Spreadsheet/Gui/LineEdit.h b/src/Mod/Spreadsheet/Gui/LineEdit.h index fa4e85185f..467e81cb7b 100644 --- a/src/Mod/Spreadsheet/Gui/LineEdit.h +++ b/src/Mod/Spreadsheet/Gui/LineEdit.h @@ -36,13 +36,17 @@ public: explicit LineEdit(QWidget *parent = 0); bool event(QEvent *event); - void setIndex(QModelIndex _current); - QModelIndex next() const; + +Q_SIGNALS: + void finishedWithKey(int key, Qt::KeyboardModifiers modifiers); private: - QModelIndex current; - int deltaCol; - int deltaRow; + bool eventFilter(QObject* object, QEvent* event); + + +private: + int lastKeyPressed; + Qt::KeyboardModifiers lastModifiers; }; } diff --git a/src/Mod/Spreadsheet/Gui/SheetTableView.cpp b/src/Mod/Spreadsheet/Gui/SheetTableView.cpp index 11da87b0b5..86b532213e 100644 --- a/src/Mod/Spreadsheet/Gui/SheetTableView.cpp +++ b/src/Mod/Spreadsheet/Gui/SheetTableView.cpp @@ -101,6 +101,7 @@ static std::pair selectedMinMaxColumns(QModelIndexList list) SheetTableView::SheetTableView(QWidget *parent) : QTableView(parent) , sheet(0) + , tabCounter(0) { setHorizontalHeader(new SheetViewHeader(this,Qt::Horizontal)); setVerticalHeader(new SheetViewHeader(this,Qt::Vertical)); @@ -319,7 +320,7 @@ void SheetTableView::insertColumnsAfter() { assert(sheet != 0); const auto columns = selectionModel()->selectedColumns(); - const auto & [min, max] = selectedMinMaxColumns(columns); + const auto& [min, max] = selectedMinMaxColumns(columns); assert(max - min == columns.size() - 1); Q_UNUSED(min) @@ -345,7 +346,7 @@ void SheetTableView::removeColumns() Gui::Command::openCommand(QT_TRANSLATE_NOOP("Command", "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); + columnName(*it).c_str(), 1); Gui::Command::commitCommand(); Gui::Command::doCommand(Gui::Command::Doc, "App.ActiveDocument.recompute()"); } @@ -365,7 +366,7 @@ void SheetTableView::updateCellSpan(CellAddress address) setSpan(address.row(), address.col(), rows, cols); } -void SheetTableView::setSheet(Sheet * _sheet) +void SheetTableView::setSheet(Sheet* _sheet) { sheet = _sheet; cellSpanChangedConnection = sheet->cellSpanChanged.connect(bind(&SheetTableView::updateCellSpan, this, bp::_1)); @@ -399,57 +400,45 @@ void SheetTableView::setSheet(Sheet * _sheet) } -void SheetTableView::commitData ( QWidget * editor ) +void SheetTableView::commitData(QWidget* editor) { QTableView::commitData(editor); } -bool SheetTableView::edit ( const QModelIndex & index, EditTrigger trigger, QEvent * event ) +bool SheetTableView::edit(const QModelIndex& index, EditTrigger trigger, QEvent* event) { - if (trigger & (QAbstractItemView::DoubleClicked | QAbstractItemView::AnyKeyPressed | QAbstractItemView::EditKeyPressed) ) + if (trigger & (QAbstractItemView::DoubleClicked | QAbstractItemView::AnyKeyPressed | QAbstractItemView::EditKeyPressed)) currentEditIndex = index; return QTableView::edit(index, trigger, event); } -bool SheetTableView::event(QEvent *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) { + if (event && event->type() == QEvent::KeyPress && this->hasFocus()) { + // If this widget has focus, look for keyboard events that represent movement shortcuts + // and handle them. + QKeyEvent* kevent = static_cast(event); + switch (kevent->key()) { + case Qt::Key_Return: [[fallthrough]]; + case Qt::Key_Enter: [[fallthrough]]; + case Qt::Key_Home: [[fallthrough]]; + case Qt::Key_End: [[fallthrough]]; + case Qt::Key_Left: [[fallthrough]]; + case Qt::Key_Right: [[fallthrough]]; + case Qt::Key_Up: [[fallthrough]]; + case Qt::Key_Down: [[fallthrough]]; + case Qt::Key_Tab: [[fallthrough]]; + case Qt::Key_Backtab: + finishEditWithMove(kevent->key(), kevent->modifiers(), true); + return true; + // Also handle the delete key here: + case Qt::Key_Delete: deleteSelection(); return true; + default: + break; } - else if (kevent->matches(QKeySequence::Cut)) { + if (kevent->matches(QKeySequence::Cut)) { cutSelection(); return true; } @@ -468,18 +457,19 @@ bool SheetTableView::event(QEvent *event) 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_Return: [[fallthrough]]; + case Qt::Key_Enter: [[fallthrough]]; + case Qt::Key_Delete: [[fallthrough]]; + case Qt::Key_Home: [[fallthrough]]; + case Qt::Key_End: [[fallthrough]]; + case Qt::Key_Backspace: [[fallthrough]]; + case Qt::Key_Left: [[fallthrough]]; + case Qt::Key_Right: [[fallthrough]]; + case Qt::Key_Up: [[fallthrough]]; + case Qt::Key_Down: [[fallthrough]]; case Qt::Key_Tab: kevent->accept(); + break; default: break; } @@ -614,13 +604,239 @@ void SheetTableView::pasteClipboard() } } -void SheetTableView::closeEditor(QWidget * editor, QAbstractItemDelegate::EndEditHint hint) +void SheetTableView::finishEditWithMove(int keyPressed, Qt::KeyboardModifiers modifiers, bool handleTabMotion) { - SpreadsheetGui::LineEdit * le = qobject_cast(editor); + // A utility lambda for finding the beginning and ending of data regions + auto scanForRegionBoundary = [this](int& r, int& c, int dr, int dc) { + auto startAddress = CellAddress(r, c); + auto startCell = sheet->getCell(startAddress); + bool startedAtEmptyCell = startCell ? !startCell->isUsed() : true; + const int maxRow = this->model()->rowCount() - 1; + const int maxCol = this->model()->columnCount() - 1; + while (c + dc >= 0 && r + dr >= 0 && c + dc <= maxCol && r + dr <= maxRow) { + r += dr; + c += dc; + auto cell = sheet->getCell(CellAddress(r, c)); + auto cellIsEmpty = cell ? !cell->isUsed() : true; + if (cellIsEmpty && !startedAtEmptyCell) { + // Don't stop at the empty cell, stop at the last non-empty cell + r -= dr; + c -= dc; + break; + } + else if (!cellIsEmpty && startedAtEmptyCell) { + break; + } + } + if (r == startAddress.row() && c == startAddress.col()) { + // Always move at least one cell: + r += dr; + c += dc; + } + r = std::max(0, std::min(r, maxRow)); + c = std::max(0, std::min(c, maxCol)); + }; - currentEditIndex = QModelIndex(); + int targetRow = currentIndex().row(); + int targetColumn = currentIndex().column(); + int colSpan; + int rowSpan; + sheet->getSpans(CellAddress(targetRow, targetColumn), rowSpan, colSpan); + switch (keyPressed) { + case Qt::Key_Return: + case Qt::Key_Enter: + if (modifiers == Qt::NoModifier) { + targetRow += rowSpan; + targetColumn -= tabCounter; + } + else if (modifiers == Qt::ShiftModifier) { + targetRow -= 1; + targetColumn -= tabCounter; + } + else { + // For an unrecognized modifier, just go down + targetRow += rowSpan; + } + tabCounter = 0; + break; + + case Qt::Key_Home: + // Home: row 1, same column + // Ctrl-Home: row 1, column 1 + targetRow = 0; + if (modifiers == Qt::ControlModifier) + targetColumn = 0; + tabCounter = 0; + break; + + case Qt::Key_End: + { + // End should take you to the last occupied cell in the current column + // Ctrl-End takes you to the last cell in the sheet + auto usedCells = sheet->getCells()->getUsedCells(); + for (const auto& cell : usedCells) { + if (modifiers == Qt::NoModifier) { + if (cell.col() == targetColumn) + targetRow = std::max(targetRow, cell.row()); + } + else if (modifiers == Qt::ControlModifier) { + targetRow = std::max(targetRow, cell.row()); + targetColumn = std::max(targetColumn, cell.col()); + } + } + tabCounter = 0; + break; + } + + case Qt::Key_Left: + if (targetColumn == 0) + break; // Nothing to do, we're already in the first column + if (modifiers == Qt::NoModifier || modifiers == Qt::ShiftModifier) + targetColumn--; + else if (modifiers == Qt::ControlModifier || + modifiers == (Qt::ControlModifier | Qt::ShiftModifier)) + scanForRegionBoundary(targetRow, targetColumn, 0, -1); + else + targetColumn--; //Unrecognized modifier combination: default to just moving one cell + tabCounter = 0; + break; + case Qt::Key_Right: + if (targetColumn >= this->model()->columnCount() - 1) + break; // Nothing to do, we're already in the last column + if (modifiers == Qt::NoModifier || modifiers == Qt::ShiftModifier) + targetColumn += colSpan; + else if (modifiers == Qt::ControlModifier || + modifiers == (Qt::ControlModifier | Qt::ShiftModifier)) + scanForRegionBoundary(targetRow, targetColumn, 0, 1); + else + targetColumn += colSpan; //Unrecognized modifier combination: default to just moving one cell + tabCounter = 0; + break; + case Qt::Key_Up: + if (targetRow == 0) + break; // Nothing to do, we're already in the first column + if (modifiers == Qt::NoModifier || modifiers == Qt::ShiftModifier) + targetRow--; + else if (modifiers == Qt::ControlModifier || + modifiers == (Qt::ControlModifier | Qt::ShiftModifier)) + scanForRegionBoundary(targetRow, targetColumn, -1, 0); + else + targetRow--; //Unrecognized modifier combination: default to just moving one cell + tabCounter = 0; + break; + case Qt::Key_Down: + if (targetRow >= this->model()->rowCount() - 1) + break; // Nothing to do, we're already in the last row + if (modifiers == Qt::NoModifier || modifiers == Qt::ShiftModifier) + targetRow += rowSpan; + else if (modifiers == Qt::ControlModifier || + modifiers == (Qt::ControlModifier | Qt::ShiftModifier)) + scanForRegionBoundary(targetRow, targetColumn, 1, 0); + else + targetRow += rowSpan; //Unrecognized modifier combination: default to just moving one cell + tabCounter = 0; + break; + case Qt::Key_Tab: + if (modifiers == Qt::NoModifier) { + tabCounter++; + if (handleTabMotion) + targetColumn += colSpan; + } + else if (modifiers == Qt::ShiftModifier) { + tabCounter = 0; + if (handleTabMotion) + targetColumn--; + } + break; + case Qt::Key_Backtab: + if (modifiers == Qt::NoModifier) { + targetColumn--; + } + tabCounter = 0; + break; + default: + break; + } + + if (this->sheet->isMergedCell(CellAddress(targetRow, targetColumn))) { + auto anchor = this->sheet->getAnchor(CellAddress(targetRow, targetColumn)); + targetRow = anchor.row(); + targetColumn = anchor.col(); + } + + // Overflow/underflow protection: + const int maxRow = this->model()->rowCount() - 1; + const int maxCol = this->model()->columnCount() - 1; + targetRow = std::max(0, std::min(targetRow, maxRow)); + targetColumn = std::max(0, std::min(targetColumn, maxCol)); + + if (!(modifiers & Qt::ShiftModifier) || keyPressed == Qt::Key_Tab || keyPressed == Qt::Key_Enter || keyPressed == Qt::Key_Return) { + // We have to use this method so that Ctrl-modifier combinations don't result in multiple selection + this->selectionModel()->setCurrentIndex(model()->index(targetRow, targetColumn), + QItemSelectionModel::ClearAndSelect); + } + else if (modifiers & Qt::ShiftModifier) { + // With shift down, this motion becomes a block selection command, rather than just simple motion: + ModifyBlockSelection(targetRow, targetColumn); + } + +} + +void SheetTableView::ModifyBlockSelection(int targetRow, int targetCol) +{ + int startingRow = currentIndex().row(); + int startingCol = currentIndex().column(); + + // Get the current block selection size: + auto selection = this->selectionModel()->selection(); + for (const auto& range : selection) { + if (range.contains(currentIndex())) { + // This range contains the current cell, so it's the one we're going to modify (assuming we're at one of the corners) + int rangeMinRow = range.top(); + int rangeMaxRow = range.bottom(); + int rangeMinCol = range.left(); + int rangeMaxCol = range.right(); + if ((startingRow == rangeMinRow || startingRow == rangeMaxRow) && + (startingCol == rangeMinCol || startingCol == rangeMaxCol)) { + if (range.contains(model()->index(targetRow, targetCol))) { + // If the range already contains the target cell, then we're making the range smaller + if (startingRow == rangeMinRow) + rangeMinRow = targetRow; + if (startingRow == rangeMaxRow) + rangeMaxRow = targetRow; + if (startingCol == rangeMinCol) + rangeMinCol = targetCol; + if (startingCol == rangeMaxCol) + rangeMaxCol = targetCol; + } + else { + // We're making the range bigger + rangeMinRow = std::min(rangeMinRow, targetRow); + rangeMaxRow = std::max(rangeMaxRow, targetRow); + rangeMinCol = std::min(rangeMinCol, targetCol); + rangeMaxCol = std::max(rangeMaxCol, targetCol); + } + QItemSelection oldRange(range.topLeft(), range.bottomRight()); + this->selectionModel()->select(oldRange, QItemSelectionModel::Deselect); + QItemSelection newRange(model()->index(rangeMinRow, rangeMinCol), model()->index(rangeMaxRow, rangeMaxCol)); + this->selectionModel()->select(newRange, QItemSelectionModel::Select); + } + break; + } + } + + this->selectionModel()->setCurrentIndex(model()->index(targetRow, targetCol), QItemSelectionModel::Current); +} + +void SheetTableView::closeEditor(QWidget * editor, QAbstractItemDelegate::EndEditHint hint) +{ QTableView::closeEditor(editor, hint); - setCurrentIndex(le->next()); +} + +void SheetTableView::mousePressEvent(QMouseEvent* event) +{ + tabCounter = 0; + QTableView::mousePressEvent(event); } void SheetTableView::edit ( const QModelIndex & index ) diff --git a/src/Mod/Spreadsheet/Gui/SheetTableView.h b/src/Mod/Spreadsheet/Gui/SheetTableView.h index 5dbb7b3b05..fb0f260a23 100644 --- a/src/Mod/Spreadsheet/Gui/SheetTableView.h +++ b/src/Mod/Spreadsheet/Gui/SheetTableView.h @@ -62,6 +62,9 @@ public: void copySelection(); void cutSelection(); void pasteClipboard(); + void finishEditWithMove(int keyPressed, Qt::KeyboardModifiers modifiers, bool handleTabMotion = false); + + void ModifyBlockSelection(int targetRow, int targetColumn); protected Q_SLOTS: void commitData(QWidget *editor); @@ -77,8 +80,10 @@ protected: bool edit(const QModelIndex &index, EditTrigger trigger, QEvent *event); bool event(QEvent *event); void closeEditor(QWidget *editor, QAbstractItemDelegate::EndEditHint hint); + void mousePressEvent(QMouseEvent* event); QModelIndex currentEditIndex; + int tabCounter; Spreadsheet::Sheet * sheet; boost::signals2::scoped_connection cellSpanChangedConnection; diff --git a/src/Mod/Spreadsheet/Gui/SpreadsheetDelegate.cpp b/src/Mod/Spreadsheet/Gui/SpreadsheetDelegate.cpp index 835a0ec6af..682e22b0c4 100644 --- a/src/Mod/Spreadsheet/Gui/SpreadsheetDelegate.cpp +++ b/src/Mod/Spreadsheet/Gui/SpreadsheetDelegate.cpp @@ -47,28 +47,11 @@ QWidget *SpreadsheetDelegate::createEditor(QWidget *parent, const QModelIndex &index) const { SpreadsheetGui::LineEdit *editor = new SpreadsheetGui::LineEdit(parent); - editor->setIndex(index); - editor->setDocumentObject(sheet); - connect(editor, SIGNAL(returnPressed()), this, SLOT(commitAndCloseEditor())); + connect(editor, &SpreadsheetGui::LineEdit::finishedWithKey, this, &SpreadsheetDelegate::on_editorFinishedWithKey); return editor; } -void SpreadsheetDelegate::commitAndCloseEditor() -{ - Gui::ExpressionLineEdit *editor = qobject_cast(sender()); - if (editor->completerActive()) { - editor->hideCompleter(); - return; - } - - // See https://forum.freecadweb.org/viewtopic.php?f=3&t=41694 - // It looks like the slot commitAndCloseEditor() is not needed any more and even - // causes a crash when doing so because the LineEdit is still accessed after its destruction. - //Q_EMIT commitData(editor); - //Q_EMIT closeEditor(editor); -} - void SpreadsheetDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const { @@ -89,6 +72,11 @@ void SpreadsheetDelegate::setModelData(QWidget *editor, } } +void SpreadsheetDelegate::on_editorFinishedWithKey(int key, Qt::KeyboardModifiers modifiers) +{ + Q_EMIT finishedWithKey(key, modifiers); +} + QSize SpreadsheetDelegate::sizeHint(const QStyleOptionViewItem & option, const QModelIndex & index) const { Q_UNUSED(option); diff --git a/src/Mod/Spreadsheet/Gui/SpreadsheetDelegate.h b/src/Mod/Spreadsheet/Gui/SpreadsheetDelegate.h index 03080af514..fe4efc7855 100644 --- a/src/Mod/Spreadsheet/Gui/SpreadsheetDelegate.h +++ b/src/Mod/Spreadsheet/Gui/SpreadsheetDelegate.h @@ -45,8 +45,10 @@ public: const QModelIndex &index) const; QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const; +Q_SIGNALS: + void finishedWithKey(int key, Qt::KeyboardModifiers modifiers); private Q_SLOTS: - void commitAndCloseEditor(); + void on_editorFinishedWithKey(int key, Qt::KeyboardModifiers modifiers); private: Spreadsheet::Sheet * sheet; }; diff --git a/src/Mod/Spreadsheet/Gui/SpreadsheetView.cpp b/src/Mod/Spreadsheet/Gui/SpreadsheetView.cpp index 1bd97d7af6..5addb23b96 100644 --- a/src/Mod/Spreadsheet/Gui/SpreadsheetView.cpp +++ b/src/Mod/Spreadsheet/Gui/SpreadsheetView.cpp @@ -98,9 +98,12 @@ SheetView::SheetView(Gui::Document *pcDocument, App::DocumentObject *docObj, QWi connect(ui->cells->verticalHeader(), SIGNAL(sectionResized ( int, int, int ) ), this, SLOT(rowResized(int, int, int))); - connect(ui->cellContent, SIGNAL(returnPressed()), this, SLOT( editingFinished() )); - connect(ui->cellAlias, SIGNAL(returnPressed()), this, SLOT( editingFinished() )); - connect(ui->cellAlias, SIGNAL(textEdited(QString)), this, SLOT(aliasChanged(QString))); + connect(delegate, &SpreadsheetDelegate::finishedWithKey, this, &SheetView::editingFinishedWithKey); + connect(ui->cellContent, &LineEdit::finishedWithKey, this, [this](int, Qt::KeyboardModifiers) {confirmContentChanged(ui->cellContent->text()); }); + connect(ui->cellContent, &LineEdit::returnPressed, this, [this]() {confirmContentChanged(ui->cellContent->text()); }); + connect(ui->cellAlias, &LineEdit::finishedWithKey, this, [this](int, Qt::KeyboardModifiers) {confirmAliasChanged(ui->cellAlias->text()); }); + connect(ui->cellAlias, &LineEdit::returnPressed, this, [this]() {confirmAliasChanged(ui->cellAlias->text()); }); + connect(ui->cellAlias, &LineEdit::textEdited, this, &SheetView::aliasChanged); columnWidthChangedConnection = sheet->columnWidthChanged.connect(bind(&SheetView::resizeColumn, this, bp::_1, bp::_2)); rowHeightChangedConnection = sheet->rowHeightChanged.connect(bind(&SheetView::resizeRow, this, bp::_1, bp::_2)); @@ -215,20 +218,6 @@ void SheetView::setCurrentCell(QString str) updateAliasLine(); } -void SheetView::keyPressEvent(QKeyEvent *event) -{ - if (event->key() == Qt::Key_Delete) { - if (event->modifiers() == 0) { - //model()->setData(currentIndex(), QVariant(), Qt::EditRole); - } - else if (event->modifiers() == Qt::ControlModifier) { - //model()->setData(currentIndex(), QVariant(), Qt::EditRole); - } - } - else - Gui::MDIView::keyPressEvent(event); -} - void SheetView::updateContentLine() { QModelIndex i = ui->cells->currentIndex(); @@ -238,7 +227,6 @@ void SheetView::updateContentLine() if (const auto * cell = sheet->getCell(CellAddress(i.row(), i.column()))) (void)cell->getStringContent(str); ui->cellContent->setText(QString::fromUtf8(str.c_str())); - ui->cellContent->setIndex(i); ui->cellContent->setEnabled(true); // Update completer model; for the time being, we do this by setting the document object of the input line. @@ -255,7 +243,6 @@ void SheetView::updateAliasLine() if (const auto * cell = sheet->getCell(CellAddress(i.row(), i.column()))) (void)cell->getAlias(str); ui->cellAlias->setText(QString::fromUtf8(str.c_str())); - ui->cellAlias->setIndex(i); ui->cellAlias->setEnabled(true); // Update completer model; for the time being, we do this by setting the document object of the input line. @@ -322,51 +309,53 @@ void SheetView::resizeRow(int col, int newSize) ui->cells->setRowHeight(col, newSize); } -void SheetView::editingFinished() +void SheetView::editingFinishedWithKey(int key, Qt::KeyboardModifiers modifiers) { - if (ui->cellContent->completerActive()) { - ui->cellContent->hideCompleter(); - return; - } - - if (ui->cellAlias->completerActive()) { - ui->cellAlias->hideCompleter(); - return; - } - QModelIndex i = ui->cells->currentIndex(); if (i.isValid()) { - QString str = ui->cellAlias->text(); - bool aliasOkay = true; - - if (str.length()!= 0 && !sheet->isValidAlias(Base::Tools::toStdString(str))){ - aliasOkay = false; - } - - ui->cellAlias->setDocumentObject(sheet); ui->cells->model()->setData(i, QVariant(ui->cellContent->text()), Qt::EditRole); + ui->cells->finishEditWithMove(key, modifiers); + } +} - if (const auto * cell = sheet->getCell(CellAddress(i.row(), i.column()))){ - if (!aliasOkay){ - //do not show error message if failure to set new alias is because it is already the same string - std::string current_alias; - (void)cell->getAlias(current_alias); - if (str != QString::fromUtf8(current_alias.c_str())){ - Base::Console().Error("Unable to set alias: %s\n", Base::Tools::toStdString(str).c_str()); - } - } else { - std::string address = CellAddress(i.row(), i.column()).toString(); - Gui::cmdAppObjectArgs(sheet, "setAlias('%s', '%s')", - address, str.toStdString()); - Gui::cmdAppDocument(sheet->getDocument(), "recompute()"); +void SheetView::confirmAliasChanged(const QString& text) +{ + bool aliasOkay = true; + + ui->cellAlias->setDocumentObject(sheet); + if (text.length() != 0 && !sheet->isValidAlias(Base::Tools::toStdString(text))) { + aliasOkay = false; + } + + QModelIndex i = ui->cells->currentIndex(); + if (const auto* cell = sheet->getCell(CellAddress(i.row(), i.column()))) { + if (!aliasOkay) { + //do not show error message if failure to set new alias is because it is already the same string + std::string current_alias; + (void)cell->getAlias(current_alias); + if (text != QString::fromUtf8(current_alias.c_str())) { + Base::Console().Error("Unable to set alias: %s\n", Base::Tools::toStdString(text).c_str()); } } - ui->cells->setCurrentIndex(ui->cellContent->next()); - ui->cells->setFocus(); + else { + std::string address = CellAddress(i.row(), i.column()).toString(); + Gui::cmdAppObjectArgs(sheet, "setAlias('%s', '%s')", + address, text.toStdString()); + Gui::cmdAppDocument(sheet->getDocument(), "recompute()"); + ui->cells->setFocus(); + } } } + +void SheetView::confirmContentChanged(const QString& text) +{ + QModelIndex i = ui->cells->currentIndex(); + ui->cells->model()->setData(i, QVariant(ui->cellContent->text()), Qt::EditRole); + ui->cells->setFocus(); +} + void SheetView::aliasChanged(const QString& text) { // check live the input and highlight if the user input invalid characters diff --git a/src/Mod/Spreadsheet/Gui/SpreadsheetView.h b/src/Mod/Spreadsheet/Gui/SpreadsheetView.h index 8e6aced3ef..141cdfe59b 100644 --- a/src/Mod/Spreadsheet/Gui/SpreadsheetView.h +++ b/src/Mod/Spreadsheet/Gui/SpreadsheetView.h @@ -82,8 +82,10 @@ public: virtual void deleteSelf(); protected Q_SLOTS: - void editingFinished(); + void editingFinishedWithKey(int key, Qt::KeyboardModifiers modifiers); + void confirmAliasChanged(const QString& text); void aliasChanged(const QString& text); + void confirmContentChanged(const QString& text); void currentChanged( const QModelIndex & current, const QModelIndex & previous ); void columnResized(int col, int oldSize, int newSize); void rowResized(int row, int oldSize, int newSize); @@ -94,7 +96,6 @@ protected: void updateContentLine(); void updateAliasLine(); void setCurrentCell(QString str); - void keyPressEvent(QKeyEvent *event); void resizeColumn(int col, int newSize); void resizeRow(int col, int newSize); From 608682fc1f0087998ff4beb72e3142d8984fe809 Mon Sep 17 00:00:00 2001 From: Roy-043 <70520633+Roy-043@users.noreply.github.com> Date: Sat, 9 Oct 2021 10:32:50 +0200 Subject: [PATCH 03/14] Update draft_test_objects.py --- .../Draft/drafttests/draft_test_objects.py | 797 +++++++++--------- 1 file changed, 380 insertions(+), 417 deletions(-) diff --git a/src/Mod/Draft/drafttests/draft_test_objects.py b/src/Mod/Draft/drafttests/draft_test_objects.py index a4f10caadf..602bc2d2dc 100644 --- a/src/Mod/Draft/drafttests/draft_test_objects.py +++ b/src/Mod/Draft/drafttests/draft_test_objects.py @@ -1,5 +1,6 @@ # *************************************************************************** -# * (c) 2020 Eliud Cabrera Castillo * +# * Copyright (c) 2020 Eliud Cabrera Castillo * +# * Copyright (c) 2021 FreeCAD Developers * # * * # * This file is part of the FreeCAD CAx development system. * # * * @@ -66,17 +67,7 @@ def _set_text(text_list, position): def _create_frame(doc=None): - """Draw a frame with information on the version of the software. - - It includes the date created, the version, the release type, - and the branch. - - Parameters - ---------- - doc: App::Document, optional - It defaults to `None`, which then defaults to the current - active document, or creates a new document. - """ + """Draw a frame with information on the version of the software.""" if not doc: doc = App.activeDocument() if not doc: @@ -101,10 +92,10 @@ def _create_frame(doc=None): record.ViewObject.FontSize = 400 record.ViewObject.TextColor = (0.0, 0.0, 0.0) - p1 = Vector(-1000, -3500, 0) - p2 = Vector(20000, -3500, 0) - p3 = Vector(20000, 8500, 0) - p4 = Vector(-1000, 8500, 0) + p1 = Vector(-1000, -4000, 0) + p2 = Vector(17000, -4000, 0) + p3 = Vector(17000, 8000, 0) + p4 = Vector(-1000, 8000, 0) poly = Part.makePolygon([p1, p2, p3, p4, p1]) frame = doc.addObject("Part::Feature", "Frame") @@ -112,199 +103,135 @@ def _create_frame(doc=None): def _create_objects(doc=None, - font_file="/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf"): - """Create the objects of the test file. - - Parameters - ---------- - doc: App::Document, optional - It defaults to `None`, which then defaults to the current - active document, or creates a new document. - """ + font_file=None, + hatch_file=None, + hatch_name=None): + """Create the objects of the test file.""" if not doc: doc = App.activeDocument() if not doc: doc = App.newDocument() - # Line, wire, and fillet + # Drafting ############################################################## + + # Line _msg(16 * "-") _msg("Line") Draft.make_line(Vector(0, 0, 0), Vector(500, 500, 0)) - t_xpos = -50 - t_ypos = -200 - _set_text(["Line"], Vector(t_xpos, t_ypos, 0)) + _set_text(["Line"], Vector(0, -200, 0)) + # Wire _msg(16 * "-") _msg("Wire") - Draft.make_wire([Vector(500, 0, 0), - Vector(1000, 500, 0), - Vector(1000, 1000, 0)]) - t_xpos += 500 - _set_text(["Wire"], Vector(t_xpos, t_ypos, 0)) + Draft.make_wire([Vector(1000, 0, 0), + Vector(1500, 250, 0), + Vector(1500, 500, 0)]) + _set_text(["Wire"], Vector(1000, -200, 0)) + # Fillet _msg(16 * "-") _msg("Fillet") - line_h_1 = Draft.make_line(Vector(1500, 0, 0), Vector(1500, 500, 0)) - line_h_2 = Draft.make_line(Vector(1500, 500, 0), Vector(2000, 500, 0)) + line_1 = Draft.make_line(Vector(2000, 0, 0), Vector(2000, 500, 0)) + line_2 = Draft.make_line(Vector(2000, 500, 0), Vector(2500, 500, 0)) if App.GuiUp: - line_h_1.ViewObject.DrawStyle = "Dotted" - line_h_2.ViewObject.DrawStyle = "Dotted" + line_1.ViewObject.DrawStyle = "Dotted" + line_2.ViewObject.DrawStyle = "Dotted" doc.recompute() + Draft.make_fillet([line_1, line_2], 400) + _set_text(["Fillet"], Vector(2000, -200, 0)) - Draft.make_fillet([line_h_1, line_h_2], 400) - t_xpos += 900 - _set_text(["Fillet"], Vector(t_xpos, t_ypos, 0)) - - # Circle, arc, arc by 3 points - _msg(16 * "-") - _msg("Circle") - circle = Draft.make_circle(350) - circle.Placement.Base = Vector(2500, 500, 0) - t_xpos += 1050 - _set_text(["Circle"], Vector(t_xpos, t_ypos, 0)) - + # Circular arc _msg(16 * "-") _msg("Circular arc") - arc = Draft.make_circle(350, startangle=0, endangle=100) - arc.Placement.Base = Vector(3200, 500, 0) - t_xpos += 800 - _set_text(["Circular arc"], Vector(t_xpos, t_ypos, 0)) + arc = Draft.make_circle(250, startangle=90, endangle=270) + arc.Placement.Base = Vector(3250, 250, 0) + _set_text(["Circular arc"], Vector(3000, -200, 0)) + # Circular arc 3 points _msg(16 * "-") _msg("Circular arc 3 points") - Draft.make_arc_3points([Vector(4600, 0, 0), - Vector(4600, 800, 0), - Vector(4000, 1000, 0)]) - t_xpos += 600 - _set_text(["Circular arc 3 points"], Vector(t_xpos, t_ypos, 0)) + Draft.make_arc_3points([Vector(4250, 0, 0), + Vector(4000, 250, 0), + Vector(4250, 500, 0)]) + _set_text(["Circular arc 3 points"], Vector(4000, -200, 0)) - # Ellipse, polygon, rectangle + # Circle + _msg(16 * "-") + _msg("Circle") + circle = Draft.make_circle(250) + circle.Placement.Base = Vector(5250, 250, 0) + _set_text(["Circle"], Vector(5000, -200, 0)) + + # Ellipse _msg(16 * "-") _msg("Ellipse") - ellipse = Draft.make_ellipse(500, 300) - ellipse.Placement.Base = Vector(5500, 250, 0) - t_xpos += 1600 - _set_text(["Ellipse"], Vector(t_xpos, t_ypos, 0)) + ellipse = Draft.make_ellipse(250, 150) + ellipse.Placement.Base = Vector(6250, 150, 0) + _set_text(["Ellipse"], Vector(6000, -200, 0)) + # Rectangle + _msg(16 * "-") + _msg("Rectangle") + rectangle = Draft.make_rectangle(500, 300, 0) + rectangle.Placement.Base = Vector(7000, 0, 0) + _set_text(["Rectangle"], Vector(7000, -200, 0)) + + # Polygon _msg(16 * "-") _msg("Polygon") polygon = Draft.make_polygon(5, 250) - polygon.Placement.Base = Vector(6500, 500, 0) - t_xpos += 950 - _set_text(["Polygon"], Vector(t_xpos, t_ypos, 0)) - - _msg(16 * "-") - _msg("Rectangle") - rectangle = Draft.make_rectangle(500, 1000, 0) - rectangle.Placement.Base = Vector(7000, 0, 0) - t_xpos += 650 - _set_text(["Rectangle"], Vector(t_xpos, t_ypos, 0)) - - # Text - _msg(16 * "-") - _msg("Text") - text = Draft.make_text(["Testing", "text"], Vector(7700, 500, 0)) - if App.GuiUp: - text.ViewObject.FontSize = 100 - t_xpos += 700 - _set_text(["Text"], Vector(t_xpos, t_ypos, 0)) - - # Linear dimension - _msg(16 * "-") - _msg("Linear dimension") - line = Draft.make_wire([Vector(8700, 200, 0), - Vector(8700, 1200, 0)]) - - dimension = Draft.make_linear_dimension(Vector(8600, 200, 0), - Vector(8600, 1000, 0), - Vector(8400, 750, 0)) - if App.GuiUp: - dimension.ViewObject.ArrowSize = 15 - dimension.ViewObject.ExtLines = 1000 - dimension.ViewObject.ExtOvershoot = 100 - dimension.ViewObject.DimOvershoot = 50 - dimension.ViewObject.FontSize = 100 - dimension.ViewObject.ShowUnit = False - doc.recompute() - - dim_obj = Draft.make_linear_dimension_obj(line, 1, 2, - Vector(9000, 750, 0)) - if App.GuiUp: - dim_obj.ViewObject.ArrowSize = 15 - dim_obj.ViewObject.ArrowType = "Arrow" - dim_obj.ViewObject.ExtLines = 100 - dim_obj.ViewObject.ExtOvershoot = 100 - dim_obj.ViewObject.DimOvershoot = 50 - dim_obj.ViewObject.FontSize = 100 - dim_obj.ViewObject.ShowUnit = False - - t_xpos += 680 - _set_text(["Dimension"], Vector(t_xpos, t_ypos, 0)) - - # Radius and diameter dimension - _msg(16 * "-") - _msg("Radius and diameter dimension") - arc_h = Draft.make_circle(500, startangle=0, endangle=90) - arc_h.Placement.Base = Vector(9500, 0, 0) - doc.recompute() - - dimension_r = Draft.make_radial_dimension_obj(arc_h, 1, - "radius", - Vector(9750, 200, 0)) - if App.GuiUp: - dimension_r.ViewObject.ArrowSize = 15 - dimension_r.ViewObject.FontSize = 100 - dimension_r.ViewObject.ShowUnit = False - - arc_h2 = Draft.make_circle(450, startangle=-120, endangle=80) - arc_h2.Placement.Base = Vector(10000, 1000, 0) - doc.recompute() - - dimension_d = Draft.make_radial_dimension_obj(arc_h2, 1, - "diameter", - Vector(10750, 900, 0)) - if App.GuiUp: - dimension_d.ViewObject.ArrowSize = 15 - dimension_d.ViewObject.FontSize = 100 - dimension_d.ViewObject.ShowUnit = False - t_xpos += 950 - _set_text(["Radius dimension", - "Diameter dimension"], Vector(t_xpos, t_ypos, 0)) - - # Angular dimension - _msg(16 * "-") - _msg("Angular dimension") - Draft.make_line(Vector(10500, 300, 0), Vector(11500, 1000, 0)) - Draft.make_line(Vector(10500, 300, 0), Vector(11500, 0, 0)) - angle1 = -20 - angle2 = 40 - dimension_a = Draft.make_angular_dimension(Vector(10500, 300, 0), - [angle1, angle2], - Vector(11500, 300, 0)) - if App.GuiUp: - dimension_a.ViewObject.ArrowSize = 15 - dimension_a.ViewObject.FontSize = 100 - t_xpos += 1700 - _set_text(["Angle dimension"], Vector(t_xpos, t_ypos, 0)) + polygon.Placement.Base = Vector(8250, 250, 0) + _set_text(["Polygon"], Vector(8000, -200, 0)) # BSpline _msg(16 * "-") _msg("BSpline") - Draft.make_bspline([Vector(12500, 0, 0), - Vector(12500, 500, 0), - Vector(13000, 500, 0), - Vector(13000, 1000, 0)]) - t_xpos += 1500 - _set_text(["BSpline"], Vector(t_xpos, t_ypos, 0)) + Draft.make_bspline([Vector(9000, 0, 0), + Vector(9100, 200, 0), + Vector(9400, 300, 0), + Vector(9500, 500, 0)]) + _set_text(["BSpline"], Vector(9000, -200, 0)) + + # Cubic bezier + _msg(16 * "-") + _msg("Cubic bezier") + Draft.make_bezcurve([Vector(10000, 0, 0), + Vector(10000, 500, 0), + Vector(10500, 0, 0), + Vector(10500, 500, 0)], degree=3) + _set_text(["Cubic bezier"], Vector(10000, -200, 0)) + + # N-degree bezier + _msg(16 * "-") + _msg("N-degree bezier") + Draft.make_bezcurve([Vector (11000, 0, 0), + Vector (11100, 400, 0), + Vector (11250, 250, 0), + Vector (11400, 100, 0), + Vector (11500, 500, 0)]) + _set_text(["N-degree bezier"], Vector(11000, -200, 0)) # Point _msg(16 * "-") _msg("Point") - point = Draft.make_point(13500, 500, 0) + point = Draft.make_point(12000, 0, 0) if App.GuiUp: point.ViewObject.PointSize = 10 - t_xpos += 900 - _set_text(["Point"], Vector(t_xpos, t_ypos, 0)) + _set_text(["Point"], Vector(12000, -200, 0)) + + # Facebinder + _msg(16 * "-") + _msg("Facebinder") + box = doc.addObject("Part::Box", "Box") + box.Length = 200 + box.Width = 500 + box.Height = 100 + box.Placement.Base = Vector(13000, 0, 0) + if App.GuiUp: + box.ViewObject.Visibility = False + facebinder = Draft.make_facebinder([(box, ("Face1", "Face3", "Face6"))]) + facebinder.Extrusion = 10 + _set_text(["Facebinder"], Vector(13000, -200, 0)) # Shapestring _msg(16 * "-") @@ -313,268 +240,347 @@ def _create_objects(doc=None, shape_string = Draft.make_shapestring("Testing", font_file, 100) - shape_string.Placement.Base = Vector(14000, 500) + shape_string.Placement.Base = Vector(14000, 0) except Exception: _wrn("Shapestring could not be created") _wrn("Possible cause: the font file may not exist") _wrn(font_file) - rect = Draft.make_rectangle(500, 100) - rect.Placement.Base = Vector(14000, 500) - t_xpos += 600 - _set_text(["Shapestring"], Vector(t_xpos, t_ypos, 0)) + _set_text(["Shapestring"], Vector(14000, -200, 0)) - # Facebinder + # Hatch _msg(16 * "-") - _msg("Facebinder") - box = doc.addObject("Part::Box", "Cube") - box.Length = 200 - box.Width = 500 - box.Height = 100 - box.Placement.Base = Vector(15000, 0, 0) + _msg("Hatch") + rectangle = Draft.make_rectangle(500, 300, 0) + rectangle.Placement.Base = Vector(15000, 0, 0) + rectangle.MakeFace = True if App.GuiUp: - box.ViewObject.Visibility = False + rectangle.ViewObject.Visibility = False + try: + Draft.make_hatch(rectangle, + hatch_file, + hatch_name, + scale=10, + rotation=45) + except Exception: + _wrn("Hatch could not be created") + _wrn("Possible cause: the hatch file may not exist") + _wrn(hatch_file) + _set_text(["Hatch"], Vector(15000, -200, 0)) - facebinder = Draft.make_facebinder([(box, ("Face1", "Face3", "Face6"))]) - facebinder.Extrusion = 10 - t_xpos += 780 - _set_text(["Facebinder"], Vector(t_xpos, t_ypos, 0)) + # Annotation ############################################################ - # Cubic bezier curve, n-degree bezier curve + # Text _msg(16 * "-") - _msg("Cubic bezier") - Draft.make_bezcurve([Vector(15500, 0, 0), - Vector(15578, 485, 0), - Vector(15879, 154, 0), - Vector(15975, 400, 0), - Vector(16070, 668, 0), - Vector(16423, 925, 0), - Vector(16500, 500, 0)], degree=3) - t_xpos += 680 - _set_text(["Cubic bezier"], Vector(t_xpos, t_ypos, 0)) + _msg("Text") + text = Draft.make_text(["Testing", "text"], Vector(0, 2100, 0)) + if App.GuiUp: + text.ViewObject.FontSize = 100 + _set_text(["Text"], Vector(0, 1800, 0)) + # Linear dimension _msg(16 * "-") - _msg("N-degree bezier") - Draft.make_bezcurve([Vector(16500, 0, 0), - Vector(17000, 500, 0), - Vector(17500, 500, 0), - Vector(17500, 1000, 0), - Vector(17000, 1000, 0), - Vector(17063, 1256, 0), - Vector(17732, 1227, 0), - Vector(17790, 720, 0), - Vector(17702, 242, 0)]) - t_xpos += 1200 - _set_text(["n-Bezier"], Vector(t_xpos, t_ypos, 0)) + _msg("Linear dimension") + dimension = Draft.make_linear_dimension(Vector(1500, 2000, 0), + Vector(1500, 2400, 0), + Vector(1000, 2200, 0)) + if App.GuiUp: + dimension.ViewObject.ArrowSize = 15 + dimension.ViewObject.ExtLines = 0 + dimension.ViewObject.ExtOvershoot = 50 + dimension.ViewObject.DimOvershoot = 25 + dimension.ViewObject.FontSize = 50 + dimension.ViewObject.Decimals = 1 + dimension.ViewObject.ShowUnit = False + + line = Draft.make_wire([Vector(1500, 2600, 0), + Vector(1500, 3000, 0)]) + doc.recompute() + dimension = Draft.make_linear_dimension_obj(line, 1, 2, + Vector(1000, 2800, 0)) + if App.GuiUp: + dimension.ViewObject.ArrowSize = 15 + dimension.ViewObject.ArrowType = "Arrow" + dimension.ViewObject.ExtLines = -50 + dimension.ViewObject.ExtOvershoot = 50 + dimension.ViewObject.DimOvershoot = 25 + dimension.ViewObject.FontSize = 50 + dimension.ViewObject.Decimals = 1 + dimension.ViewObject.ShowUnit = False + + _set_text(["Dimension"], Vector(1000, 1800, 0)) + + # Radius and diameter dimension + _msg(16 * "-") + _msg("Radius and diameter dimension") + circle = Draft.make_circle(200) + circle.Placement.Base = Vector(2200, 2200, 0) + circle.MakeFace = False + doc.recompute() + dimension = Draft.make_radial_dimension_obj(circle, + 1, + "radius", + Vector(2300, 2300, 0)) + if App.GuiUp: + dimension.ViewObject.ArrowSize = 15 + dimension.ViewObject.FontSize = 50 + dimension.ViewObject.Decimals = 1 + dimension.ViewObject.ShowUnit = False + + circle = Draft.make_circle(200) + circle.Placement.Base = Vector(2200, 2800, 0) + circle.MakeFace = False + doc.recompute() + dimension = Draft.make_radial_dimension_obj(circle, + 1, + "diameter", + Vector(2300, 2900, 0)) + if App.GuiUp: + dimension.ViewObject.ArrowSize = 15 + dimension.ViewObject.FontSize = 50 + dimension.ViewObject.Decimals = 1 + dimension.ViewObject.ShowUnit = False + _set_text(["Radius dimension", + "Diameter dimension"], Vector(2000, 1800, 0)) + + # Angular dimension + _msg(16 * "-") + _msg("Angular dimension") + Draft.make_line(Vector(3000, 2000, 0), Vector(3500, 2000, 0)) + Draft.make_line(Vector(3000, 2000, 0), Vector(3500, 2500, 0)) + dimension = Draft.make_angular_dimension(Vector(3000, 2000, 0), + [0, 45], + Vector(3250, 2250, 0)) + if App.GuiUp: + dimension.ViewObject.ArrowSize = 15 + dimension.ViewObject.FontSize = 50 + dimension.ViewObject.Decimals = 1 + _set_text(["Angle dimension"], Vector(3000, 1800, 0)) # Label _msg(16 * "-") _msg("Label") - place = App.Placement(Vector(18500, 500, 0), App.Rotation()) - label = Draft.make_label(target_point=Vector(18000, 0, 0), + place = App.Placement(Vector(4250, 2250, 0), App.Rotation()) + label = Draft.make_label(target_point=Vector(4000, 2000, 0), placement=place, custom_text="Example label", - distance=-250) + distance=-100) label.Text = "Testing" if App.GuiUp: label.ViewObject.ArrowSize = 15 - label.ViewObject.TextSize = 100 + label.ViewObject.TextSize = 50 doc.recompute() - t_xpos += 1200 - _set_text(["Label"], Vector(t_xpos, t_ypos, 0)) + _set_text(["Label"], Vector(4000, 1800, 0)) - # Orthogonal array and orthogonal link array + # Array ################################################################# + + # Orthogonal array _msg(16 * "-") _msg("Orthogonal array") - rect_h = Draft.make_rectangle(500, 500) - rect_h.Placement.Base = Vector(1500, 2500, 0) + rectangle = Draft.make_rectangle(100, 50) + rectangle.Placement.Base = Vector(0, 4000, 0) if App.GuiUp: - rect_h.ViewObject.Visibility = False - - Draft.make_ortho_array(rect_h, - Vector(600, 0, 0), - Vector(0, 600, 0), + rectangle.ViewObject.Visibility = False + Draft.make_ortho_array(rectangle, + Vector(200, 0, 0), + Vector(0, 150, 0), Vector(0, 0, 0), - 3, 2, 1, + 3, + 2, + 1, use_link=False) - t_xpos = 1700 - t_ypos = 2200 - _set_text(["Array"], Vector(t_xpos, t_ypos, 0)) - - rect_h_2 = Draft.make_rectangle(500, 100) - rect_h_2.Placement.Base = Vector(1500, 5000, 0) - if App.GuiUp: - rect_h_2.ViewObject.Visibility = False + _set_text(["Orthogonal array"], Vector(0, 3800, 0)) + # Orthogonal link array _msg(16 * "-") _msg("Orthogonal link array") - Draft.make_ortho_array(rect_h_2, - Vector(800, 0, 0), - Vector(0, 500, 0), + rectangle = Draft.make_rectangle(50, 50) + rectangle.Placement.Base = Vector(1000, 4000, 0) + if App.GuiUp: + rectangle.ViewObject.Visibility = False + Draft.make_ortho_array(rectangle, + Vector(200, 0, 0), + Vector(0, 150, 0), Vector(0, 0, 0), - 2, 4, 1, + 3, + 2, + 1, use_link=True) - t_ypos += 2600 - _set_text(["Link array"], Vector(t_xpos, t_ypos, 0)) + _set_text(["Orthogonal link array"], Vector(1000, 3800, 0)) - # Polar array and polar link array + # Polar array _msg(16 * "-") _msg("Polar array") - wire_h = Draft.make_wire([Vector(5500, 3000, 0), - Vector(6000, 3500, 0), - Vector(6000, 3200, 0), - Vector(5800, 3200, 0)]) + wire = Draft.make_wire([Vector(2000, 4050, 0), + Vector(2000, 4000, 0), + Vector(2100, 4000, 0)]) if App.GuiUp: - wire_h.ViewObject.Visibility = False - - Draft.make_polar_array(wire_h, - 8, - 200, - Vector(5000, 3000, 0), + wire.ViewObject.Visibility = False + Draft.make_polar_array(wire, + 4, + 90, + Vector(2000, 4250, 0), use_link=False) + _set_text(["Polar array"], Vector(2000, 3800, 0)) - t_xpos = 4600 - t_ypos = 2200 - _set_text(["Polar array"], Vector(t_xpos, t_ypos, 0)) - + # Polar link array _msg(16 * "-") _msg("Polar link array") - wire_h_2 = Draft.make_wire([Vector(5500, 6000, 0), - Vector(6000, 6000, 0), - Vector(5800, 5700, 0), - Vector(5800, 5750, 0)]) + wire = Draft.make_wire([Vector(3000, 4050, 0), + Vector(3000, 4000, 0), + Vector(3050, 4000, 0)]) if App.GuiUp: - wire_h_2.ViewObject.Visibility = False - - Draft.make_polar_array(wire_h_2, - 8, - 200, - Vector(5000, 6000, 0), + wire.ViewObject.Visibility = False + Draft.make_polar_array(wire, + 4, + 90, + Vector(3000, 4250, 0), use_link=True) - t_ypos += 3200 - _set_text(["Polar link array"], Vector(t_xpos, t_ypos, 0)) + _set_text(["Polar link array"], Vector(3000, 3800, 0)) - # Circular array and circular link array + # Circular array _msg(16 * "-") _msg("Circular array") - poly_h = Draft.make_polygon(5, 200) - poly_h.Placement.Base = Vector(8000, 3000, 0) + polygon = Draft.make_polygon(5, 30) + polygon.Placement.Base = Vector(4250, 4250, 0) if App.GuiUp: - poly_h.ViewObject.Visibility = False - - Draft.make_circular_array(poly_h, - 500, 600, + polygon.ViewObject.Visibility = False + Draft.make_circular_array(polygon, + 110, + 100, 3, 1, Vector(0, 0, 1), Vector(0, 0, 0), use_link=False) - t_xpos = 7700 - t_ypos = 1700 - _set_text(["Circular array"], Vector(t_xpos, t_ypos, 0)) + _set_text(["Circular array"], Vector(4000, 3800, 0)) + # Circular link array _msg(16 * "-") _msg("Circular link array") - poly_h_2 = Draft.make_polygon(6, 150) - poly_h_2.Placement.Base = Vector(8000, 6250, 0) + polygon = Draft.make_polygon(6, 30) + polygon.Placement.Base = Vector(5250, 4250, 0) if App.GuiUp: - poly_h_2.ViewObject.Visibility = False - - Draft.make_circular_array(poly_h_2, - 550, 450, + polygon.ViewObject.Visibility = False + Draft.make_circular_array(polygon, + 110, + 100, 3, 1, Vector(0, 0, 1), Vector(0, 0, 0), use_link=True) - t_ypos += 3100 - _set_text(["Circular link array"], Vector(t_xpos, t_ypos, 0)) + _set_text(["Circular link array"], Vector(5000, 3800, 0)) - # Path array and path link array + # Path array _msg(16 * "-") _msg("Path array") - poly_h = Draft.make_polygon(3, 250) - poly_h.Placement.Base = Vector(10000, 3000, 0) + polygon = Draft.make_polygon(3, 30) + polygon.Placement.Base = Vector(6000, 4000, 0) if App.GuiUp: - poly_h.ViewObject.Visibility = False - - bspline_path = Draft.make_bspline([Vector(10500, 2500, 0), - Vector(11000, 3000, 0), - Vector(11500, 3200, 0), - Vector(12000, 4000, 0)]) - - Draft.make_path_array(poly_h, bspline_path, 5, - use_link=False) - t_xpos = 10400 - t_ypos = 2200 - _set_text(["Path array"], Vector(t_xpos, t_ypos, 0)) + polygon.ViewObject.Visibility = False + spline = Draft.make_bspline([Vector(6000, 4000, 0), + Vector(6100, 4200, 0), + Vector(6400, 4300, 0), + Vector(6500, 4500, 0)]) + Draft.make_path_array(polygon, spline, 5, use_link=False) + _set_text(["Path array"], Vector(6000, 3800, 0)) + # Path link array _msg(16 * "-") _msg("Path link array") - poly_h_2 = Draft.make_polygon(4, 200) - poly_h_2.Placement.Base = Vector(10000, 5000, 0) + polygon = Draft.make_polygon(4, 30) + polygon.Placement.Base = Vector(7000, 4000, 0) if App.GuiUp: - poly_h_2.ViewObject.Visibility = False - - bspline_path_2 = Draft.make_bspline([Vector(10500, 4500, 0), - Vector(11000, 6800, 0), - Vector(11500, 6000, 0), - Vector(12000, 5200, 0)]) - - Draft.make_path_array(poly_h_2, bspline_path_2, 6, - use_link=True) - t_ypos += 2000 - _set_text(["Path link array"], Vector(t_xpos, t_ypos, 0)) + polygon.ViewObject.Visibility = False + spline = Draft.make_bspline([Vector(7000, 4000, 0), + Vector(7100, 4200, 0), + Vector(7400, 4300, 0), + Vector(7500, 4500, 0)]) + Draft.make_path_array(polygon, spline, 5, use_link=True) + _set_text(["Path link array"], Vector(7000, 3800, 0)) # Point array _msg(16 * "-") _msg("Point array") - poly_h = Draft.make_polygon(3, 250) - poly_h.Placement.Base = Vector(12500, 2500, 0) - - point_1 = Draft.make_point(13000, 3000, 0) - point_2 = Draft.make_point(13000, 3500, 0) - point_3 = Draft.make_point(14000, 2500, 0) - point_4 = Draft.make_point(14000, 3000, 0) - + polygon = Draft.make_polygon(3, 30) + polygon.Placement.Base = Vector(8000, 4000, 0) + point_1 = Draft.make_point(8030, 4030, 0) + point_2 = Draft.make_point(8030, 4250, 0) + point_3 = Draft.make_point(8470, 4250, 0) + point_4 = Draft.make_point(8470, 4470, 0) add_list, delete_list = Draft.upgrade([point_1, point_2, point_3, point_4]) compound = add_list[0] if App.GuiUp: compound.ViewObject.PointSize = 5 + Draft.make_point_array(polygon, compound, use_link=False) + _set_text(["Point array"], Vector(8000, 3800, 0)) - Draft.make_point_array(poly_h, compound) - t_xpos = 13000 - t_ypos = 2200 - _set_text(["Point array"], Vector(t_xpos, t_ypos, 0)) - - # Clone and mirror + # Point link array _msg(16 * "-") - _msg("Clone") - wire_h = Draft.make_wire([Vector(15000, 2500, 0), - Vector(15200, 3000, 0), - Vector(15500, 2500, 0), - Vector(15200, 2300, 0)]) + _msg("Point link array") + polygon = Draft.make_polygon(4, 30) + polygon.Placement.Base = Vector(9000, 4000, 0) + point_1 = Draft.make_point(9030, 4030, 0) + point_2 = Draft.make_point(9030, 4250, 0) + point_3 = Draft.make_point(9470, 4250, 0) + point_4 = Draft.make_point(9470, 4470, 0) + add_list, delete_list = Draft.upgrade([point_1, point_2, + point_3, point_4]) + compound = add_list[0] + if App.GuiUp: + compound.ViewObject.PointSize = 5 + Draft.make_point_array(polygon, compound, use_link=True) + _set_text(["Point link array"], Vector(9000, 3800, 0)) - Draft.make_clone(wire_h, Vector(0, 1000, 0)) - t_xpos = 15000 - t_ypos = 2100 - _set_text(["Clone"], Vector(t_xpos, t_ypos, 0)) + # Miscellaneous ######################################################### + # Mirror _msg(16 * "-") _msg("Mirror") - wire_h = Draft.make_wire([Vector(17000, 2500, 0), - Vector(16500, 4000, 0), - Vector(16000, 2700, 0), - Vector(16500, 2500, 0), - Vector(16700, 2700, 0)]) + wire = Draft.make_wire([Vector(0, 6000, 0), + Vector(150, 6200, 0), + Vector(500, 6000, 0)]) + Draft.mirror(wire, + Vector(0, 6250, 0), + Vector(500, 6250, 0)) + _set_text(["Mirror"], Vector(0, 5800, 0)) - Draft.mirror(wire_h, - Vector(17100, 2000, 0), - Vector(17100, 4000, 0)) - t_xpos = 17000 - t_ypos = 2200 - _set_text(["Mirror"], Vector(t_xpos, t_ypos, 0)) + # Clone + _msg(16 * "-") + _msg("Clone") + wire = Draft.make_wire([Vector(1000, 6000, 0), + Vector(1150, 6200, 0), + Vector(1500, 6000, 0)]) + Draft.make_clone(wire, Vector(0, 300, 0)) + _set_text(["Clone"], Vector(1000, 5800, 0)) + # Shape2DView + _msg(16 * "-") + _msg("Shape2DView") + place = App.Placement(Vector(2000, 6000, 0), + App.Rotation(Vector(0, 0, 1), Vector(1, 2, 3))) + box = doc.addObject("Part::Box", "Box") + box.Length = 200 + box.Width = 500 + box.Height = 100 + box.Placement = place + if App.GuiUp: + box.ViewObject.Visibility = False + Draft.make_shape2dview(box) + _set_text(["Shape2DView"], Vector(2000, 5800, 0)) + + # WorkingPlaneProxy + _msg(16 * "-") + _msg("WorkingPlaneProxy") + place = App.Placement(Vector(3250, 6250, 0), App.Rotation()) + proxy = Draft.make_workingplaneproxy(place) + if App.GuiUp: + proxy.ViewObject.DisplaySize = 500 + proxy.ViewObject.ArrowSize = 50 + _set_text(["WorkingPlaneProxy"], Vector(3000, 5800, 0)) + + # Layer _msg(16 * "-") _msg("Layer") layer = Draft.make_layer("Custom layer", @@ -582,39 +588,24 @@ def _create_objects(doc=None, shape_color=(0.56, 0.89, 0.56), line_width=4, transparency=50) - cube = doc.addObject('Part::Box') - cube.Length = 350 - cube.Width = 300 - cube.Height = 250 - cube.Placement.Base = Vector(14000, 5500, 0) - - cone = doc.addObject('Part::Cone') - cone.Radius1 = 400 - cone.Height = 600 - cone.Angle = 270 - cone.Placement.Base = Vector(15000, 6000, 0) - - sphere = doc.addObject('Part::Sphere') - sphere.Radius = 450 - sphere.Angle1 = -45 - sphere.Angle2 = 45 - sphere.Angle3 = 300 - sphere.Placement.Base = Vector(14000, 7000, 0) - - layer.Proxy.addObject(layer, cube) - layer.Proxy.addObject(layer, cone) + box = doc.addObject("Part::Box", "Box") + box.Length = 200 + box.Width = 500 + box.Height = 100 + box.Placement.Base = Vector(4000, 6000, 0) + sphere = doc.addObject("Part::Sphere", "Sphere") + sphere.Radius = 100 + sphere.Placement.Base = Vector(4400, 6250, 0) + layer.Proxy.addObject(layer, box) layer.Proxy.addObject(layer, sphere) + _set_text(["Layer"], Vector(4000, 5800, 0)) - t_xpos = 14000 - t_ypos = 5000 - _set_text(["Layer"], Vector(t_xpos, t_ypos, 0)) doc.recompute() -def create_test_file(file_name="draft_test_objects", - file_path=os.environ["HOME"], - save=False, - font_file="/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf"): +def create_test_file(font_file=App.getHomePath()+"data/Mod/TechDraw/Resources/fonts/osifont-lgpl3fe.ttf", + hatch_file=App.getHomePath()+"data/Mod/TechDraw/PAT/FCPAT.pat", + hatch_name="Horizontal5"): """Create a complete test file of Draft objects. It draws a frame with information on the software used to create @@ -622,63 +613,35 @@ def create_test_file(file_name="draft_test_objects", Parameters ---------- - file_name: str, optional - It defaults to `'draft_test_objects'`. - It is the name of the document that is created. - - The `file_name` will be appended to `file_path` - to determine the actual path to save. The extension `.FCStd` - will be added automatically. - - file_path: str, optional - It defaults to the value of `os.environ['HOME']` - which in Linux is usually `'/home/user'`. - - If it is the empty string `''` it will use the value - returned by `App.getUserAppDataDir()`, - for example, `'/home/user/.FreeCAD/'`. - - save: bool, optional - It defaults to `False`. If it is `True` the new document - will be saved to disk after creating all objects. - font_file: str, optional - It defaults to `'/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf'`. - It is the full path of a font in the system to be used - to create a `Draft ShapeString`. - If the font is not found, this object is not created. + It defaults to `App.getHomePath()+"data/Mod/TechDraw/Resources/fonts/osifont-lgpl3fe.ttf"` + It is the full path of a font file to be used to create a `Draft ShapeString`. + If the file is not found, this object is not created. + + hatch_file: str, optional + It defaults to `App.getHomePath()+"data/Mod/TechDraw/PAT/FCPAT.pat"` + It is the full path of a PAT file to be used to create a `Draft Hatch`. + If the file is not found, this object is not created. + + hatch_name: str, optional + It defaults to `"Horizontal5"` + It is the name of a hatch pattern in the hatch_file. Returns ------- App::Document A reference to the test document that was created. - - To Do - ----- - Find a reliable way of getting a default font to be able to create - the `Draft ShapeString`. """ - doc = App.newDocument(file_name) _msg(16 * "-") - _msg("Filename: {}".format(file_name)) _msg("If the units tests fail, this script may fail as well") + doc = App.newDocument() _create_frame(doc=doc) - _create_objects(doc=doc, font_file=font_file) + _create_objects(doc=doc, font_file=font_file, hatch_file=hatch_file, hatch_name=hatch_name) if App.GuiUp: Gui.runCommand("Std_ViewFitAll") - - # Export - if not file_path: - file_path = App.getUserAppDataDir() - - out_name = os.path.join(file_path, file_name + ".FCStd") - doc.FileName = out_name - if save: - doc.save() - _msg(16 * "-") - _msg("Saved: {}".format(out_name)) + Gui.Selection.clearSelection() return doc From 5721086ea93fb04e960263982d8dafec632be135 Mon Sep 17 00:00:00 2001 From: Roy-043 <70520633+Roy-043@users.noreply.github.com> Date: Sat, 9 Oct 2021 10:37:05 +0200 Subject: [PATCH 04/14] Update draft_test_objects.py --- src/Mod/Draft/drafttests/draft_test_objects.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Mod/Draft/drafttests/draft_test_objects.py b/src/Mod/Draft/drafttests/draft_test_objects.py index 602bc2d2dc..1544b7089a 100644 --- a/src/Mod/Draft/drafttests/draft_test_objects.py +++ b/src/Mod/Draft/drafttests/draft_test_objects.py @@ -41,7 +41,6 @@ Or load it as a module and use the defined function. ## \addtogroup drafttests # @{ import datetime -import os import FreeCAD as App import Part From b2a629c494da804380275334f2ffd749091db571 Mon Sep 17 00:00:00 2001 From: Chris Hennes Date: Sun, 10 Oct 2021 20:23:57 -0500 Subject: [PATCH 05/14] [Part] Allow setting alpha for face colors --- src/Mod/Part/Gui/TaskFaceColors.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Mod/Part/Gui/TaskFaceColors.cpp b/src/Mod/Part/Gui/TaskFaceColors.cpp index d7195a65be..be1126d48f 100644 --- a/src/Mod/Part/Gui/TaskFaceColors.cpp +++ b/src/Mod/Part/Gui/TaskFaceColors.cpp @@ -272,6 +272,7 @@ FaceColors::FaceColors(ViewProviderPartExt* vp, QWidget* parent) d->ui->setupUi(this); d->ui->groupBox->setTitle(QString::fromUtf8(vp->getObject()->Label.getValue())); d->ui->colorButton->setDisabled(true); + d->ui->colorButton->setAllowTransparency(true); FaceSelection* gate = new FaceSelection(d->vp->getObject()); Gui::Selection().addSelectionGate(gate); @@ -354,10 +355,9 @@ void FaceColors::on_defaultButton_clicked() void FaceColors::on_colorButton_changed() { if (!d->index.isEmpty()) { - float alpha = static_cast(d->vp->Transparency.getValue())/100; QColor c = d->ui->colorButton->color(); for (QSet::iterator it = d->index.begin(); it != d->index.end(); ++it) { - d->perface[*it].set(c.redF(), c.greenF(), c.blueF(), alpha); + d->perface[*it].set(c.redF(), c.greenF(), c.blueF(), c.alphaF()); } d->vp->DiffuseColor.setValues(d->perface); } @@ -380,7 +380,7 @@ void FaceColors::onSelectionChanged(const Gui::SelectionChanges& msg) d->index.insert(index); const App::Color& c = d->perface[index]; QColor color; - color.setRgbF(c.r,c.g,c.b); + color.setRgbF(c.r,c.g,c.b,c.a); d->ui->colorButton->setColor(color); selection_changed = true; } From 8c2fc25cd9a0c82d4a41c0a3977ee1315535f002 Mon Sep 17 00:00:00 2001 From: Chris Hennes Date: Mon, 11 Oct 2021 21:13:22 -0500 Subject: [PATCH 06/14] [Tools] Eliminate default value modification LGTM catches a potential error with the mutation of default values: implement their suggested workaround of using a None placeholder to ensure the default itself is never modified. --- src/Tools/generateBase/generateTools.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/Tools/generateBase/generateTools.py b/src/Tools/generateBase/generateTools.py index d6d6ad92b1..2e4efa3243 100644 --- a/src/Tools/generateBase/generateTools.py +++ b/src/Tools/generateBase/generateTools.py @@ -100,12 +100,15 @@ class copier: except TypeError: self.ouf.write(self.regex.sub(repl, line)) i=i+1 - def __init__(self, regex=_never, dict={}, + def __init__(self, regex=_never, dict=None, restat=_never, restend=_never, recont=_never, preproc=identity, handle=nohandle, ouf=sys.stdout): "Initialize self's attributes" self.regex = regex - self.globals = dict + if dict is not None: + self.globals = dict + else: + self.globals = {} self.globals['sys'] = sys self.locals = { '_cb':self.copyblock } self.restat = restat From 34a7fc95d81f4308d3b9bf4915732cc8bd6e9049 Mon Sep 17 00:00:00 2001 From: Chris Hennes Date: Mon, 11 Oct 2021 21:28:55 -0500 Subject: [PATCH 07/14] [Draft] Eliminate default value modification --- src/Mod/Draft/draftmake/make_dimension.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/Mod/Draft/draftmake/make_dimension.py b/src/Mod/Draft/draftmake/make_dimension.py index 9cd4ebfe8d..1bc3d90fda 100644 --- a/src/Mod/Draft/draftmake/make_dimension.py +++ b/src/Mod/Draft/draftmake/make_dimension.py @@ -509,7 +509,7 @@ def make_radial_dimension_obj(edge_object, index=1, mode="radius", def make_angular_dimension(center=App.Vector(0, 0, 0), - angles=[0, 90], + angles=None, # If None, set to [0,90] dim_line=App.Vector(10, 10, 0), normal=None): """Create an angular dimension from the given center and angles. @@ -555,6 +555,10 @@ def make_angular_dimension(center=App.Vector(0, 0, 0), _name = "make_angular_dimension" utils.print_header(_name, "Angular dimension") + # Prevent later modification of a default parameter by using a placeholder + if angles is None: + angles = [0, 90] + found, doc = utils.find_doc(App.activeDocument()) if not found: _err(translate("draft","No active document. Aborting.")) From 13beed81ae2a0d804ce4cc4618cf8b0ec678e56b Mon Sep 17 00:00:00 2001 From: Chris Hennes Date: Mon, 11 Oct 2021 22:01:44 -0500 Subject: [PATCH 08/14] [Arch] Remove redundant else: from while loop --- src/Mod/Arch/ArchWall.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Mod/Arch/ArchWall.py b/src/Mod/Arch/ArchWall.py index 1feef163a3..37ebb77c8b 100644 --- a/src/Mod/Arch/ArchWall.py +++ b/src/Mod/Arch/ArchWall.py @@ -878,8 +878,7 @@ class _Wall(ArchComponent.Component): else: cuts2.append(sh) offset += (obj.BlockLength.Value + obj.Joint.Value) - else: - offset -= (edge.Length - obj.Joint.Value) + offset -= (edge.Length - obj.Joint.Value) if isinstance(bplates,list): bplates = bplates[0] From 3d3fd72c7bd340ae7d0b6dccd957ec9e00c56716 Mon Sep 17 00:00:00 2001 From: Chris Hennes Date: Mon, 11 Oct 2021 22:22:59 -0500 Subject: [PATCH 09/14] [Arch] Remove unnecessary pass --- src/Mod/Arch/InitGui.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Mod/Arch/InitGui.py b/src/Mod/Arch/InitGui.py index ca778b1cee..5f1927b60f 100644 --- a/src/Mod/Arch/InitGui.py +++ b/src/Mod/Arch/InitGui.py @@ -128,7 +128,6 @@ class ArchWorkbench(FreeCADGui.Workbench): import RebarTools except Exception: del self.archtools[3] # remove "Arch_Rebar_Submenu" - pass else: class RebarGroupCommand: def GetCommands(self): From d7e0210aae19efb08224717230bd3965884b1906 Mon Sep 17 00:00:00 2001 From: Chris Hennes Date: Mon, 11 Oct 2021 22:37:05 -0500 Subject: [PATCH 10/14] [Part] Remove redundant 'global' statement --- src/Mod/Part/AttachmentEditor/Commands.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Mod/Part/AttachmentEditor/Commands.py b/src/Mod/Part/AttachmentEditor/Commands.py index f19e7c813c..3a477f995c 100644 --- a/src/Mod/Part/AttachmentEditor/Commands.py +++ b/src/Mod/Part/AttachmentEditor/Commands.py @@ -99,7 +99,6 @@ class CommandEditAttachment: return False if App.GuiUp: - global command_instance import FreeCADGui as Gui command_instance = CommandEditAttachment() Gui.addCommand('Part_EditAttachment', command_instance) From 2a7df72d27bc3a8a163eb90174b10ed2ab85b059 Mon Sep 17 00:00:00 2001 From: Chris Hennes Date: Mon, 11 Oct 2021 22:40:20 -0500 Subject: [PATCH 11/14] [Arch] Separate out return of None for clarity --- src/Mod/Arch/ArchCutPlane.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Mod/Arch/ArchCutPlane.py b/src/Mod/Arch/ArchCutPlane.py index fbc804a647..7565f69cd6 100644 --- a/src/Mod/Arch/ArchCutPlane.py +++ b/src/Mod/Arch/ArchCutPlane.py @@ -64,7 +64,8 @@ def cutComponentwithPlane(archObject, cutPlane, sideFace): obj.ViewObject.ShapeColor = (1.00,0.00,0.00) obj.ViewObject.Transparency = 75 if "Additions" in archObject.Object.PropertiesList: - return ArchCommands.removeComponents(obj,archObject.Object) + ArchCommands.removeComponents(obj,archObject.Object) + return None else: cutObj = FreeCAD.ActiveDocument.addObject("Part::Cut","CutPlane") cutObj.Base = archObject.Object From 8242c685091c7b0cbcdaddf859ba19a7db987f36 Mon Sep 17 00:00:00 2001 From: Abdullah Tahiri Date: Tue, 12 Oct 2021 09:41:58 +0200 Subject: [PATCH 12/14] Sketcher: Copy and Array tool Snap at 5 degrees using CTRL --- src/Mod/Sketcher/Gui/CommandSketcherTools.cpp | 42 ++++++++++++++----- 1 file changed, 31 insertions(+), 11 deletions(-) diff --git a/src/Mod/Sketcher/Gui/CommandSketcherTools.cpp b/src/Mod/Sketcher/Gui/CommandSketcherTools.cpp index d0db2dfb97..9b41a6637d 100644 --- a/src/Mod/Sketcher/Gui/CommandSketcherTools.cpp +++ b/src/Mod/Sketcher/Gui/CommandSketcherTools.cpp @@ -1292,6 +1292,7 @@ public: Sketcher::PointPos originpos, int nelements, SketcherCopy::Op op) : Mode(STATUS_SEEK_First) + , snapMode(SnapMode::Free) , geoIdList(geoidlist) , Origin() , OriginGeoId(origingeoid) @@ -1309,6 +1310,11 @@ public: STATUS_End }; + enum class SnapMode { + Free, + Snap5Degree + }; + virtual void activated(ViewProviderSketch *sketchgui) { setCursor(QPixmap(cursor_createcopy), 7, 7); @@ -1319,15 +1325,29 @@ public: virtual void mouseMove(Base::Vector2d onSketchPos) { if (Mode == STATUS_SEEK_First) { + + if(QApplication::keyboardModifiers() == Qt::ControlModifier) + snapMode = SnapMode::Snap5Degree; + else + snapMode = SnapMode::Free; + float length = (onSketchPos - EditCurve[0]).Length(); - float angle = (onSketchPos - EditCurve[0]).GetAngle(Base::Vector2d(1.f,0.f)); + float angle = (onSketchPos - EditCurve[0]).Angle(); + + Base::Vector2d endpoint = onSketchPos; + + if (snapMode == SnapMode::Snap5Degree) { + angle = round(angle / (M_PI/36)) * M_PI/36; + endpoint = EditCurve[0] + length * Base::Vector2d(cos(angle),sin(angle)); + } + SbString text; text.sprintf(" (%.1f, %.1fdeg)", length, angle * 180 / M_PI); - setPositionText(onSketchPos, text); + setPositionText(endpoint, text); - EditCurve[1] = onSketchPos; + EditCurve[1] = endpoint; sketchgui->drawEdit(EditCurve); - if (seekAutoConstraint(sugConstr1, onSketchPos, Base::Vector2d(0.0, 0.0), AutoConstraint::VERTEX)) { + if (seekAutoConstraint(sugConstr1, endpoint, Base::Vector2d(0.0, 0.0), AutoConstraint::VERTEX)) { renderSuggestConstraintsCursor(sugConstr1); return; } @@ -1335,10 +1355,9 @@ public: applyCursor(); } - virtual bool pressButton(Base::Vector2d onSketchPos) + virtual bool pressButton(Base::Vector2d) { if (Mode == STATUS_SEEK_First) { - EditCurve[1] = onSketchPos; sketchgui->drawEdit(EditCurve); Mode = STATUS_End; } @@ -1401,6 +1420,7 @@ public: } protected: SelectMode Mode; + SnapMode snapMode; string geoIdList; Base::Vector3d Origin; int OriginGeoId; @@ -1841,7 +1861,7 @@ public: enum class SnapMode { Free, - Snap10Degree + Snap5Degree }; virtual void activated(ViewProviderSketch *sketchgui) @@ -1856,17 +1876,17 @@ public: if (Mode==STATUS_SEEK_First) { if(QApplication::keyboardModifiers() == Qt::ControlModifier) - snapMode = SnapMode::Snap10Degree; + snapMode = SnapMode::Snap5Degree; else snapMode = SnapMode::Free; float length = (onSketchPos - EditCurve[0]).Length(); - float angle = (onSketchPos - EditCurve[0]).GetAngle(Base::Vector2d(1.f, 0.f)); + float angle = (onSketchPos - EditCurve[0]).Angle(); Base::Vector2d endpoint = onSketchPos; - if (snapMode == SnapMode::Snap10Degree) { - angle = round(angle / (M_PI/18)) * M_PI/18; + if (snapMode == SnapMode::Snap5Degree) { + angle = round(angle / (M_PI/36)) * M_PI/36; endpoint = EditCurve[0] + length * Base::Vector2d(cos(angle),sin(angle)); } From ef7692d413c474cb03204c31b08e9c983d4279c5 Mon Sep 17 00:00:00 2001 From: wmayer Date: Tue, 12 Oct 2021 10:03:11 +0200 Subject: [PATCH 13/14] OCCT: port flatmesh to version 7.6 --- src/Mod/MeshPart/App/MeshFlattening.cpp | 26 ++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/src/Mod/MeshPart/App/MeshFlattening.cpp b/src/Mod/MeshPart/App/MeshFlattening.cpp index 245ed98b12..d01f8496c1 100644 --- a/src/Mod/MeshPart/App/MeshFlattening.cpp +++ b/src/Mod/MeshPart/App/MeshFlattening.cpp @@ -141,37 +141,37 @@ FaceUnwrapper::FaceUnwrapper(const TopoDS_Face& face) if (triangulation.IsNull()) throw std::runtime_error("null triangulation in face construction"); + Standard_Integer numNodes = triangulation->NbNodes(); + Standard_Integer numTriangles = triangulation->NbTriangles(); + // compute uv coordinates if (triangulation->HasUVNodes()) { - const TColgp_Array1OfPnt2d &_uv_nodes = triangulation->UVNodes(); - this->uv_nodes.resize(triangulation->NbNodes(), 2); + this->uv_nodes.resize(numNodes, 2); i = 0; - for (Standard_Integer index = _uv_nodes.Lower(); index <= _uv_nodes.Upper(); ++index) + for (Standard_Integer index = 1; index <= numNodes; ++index) { - const gp_Pnt2d& _uv_node = _uv_nodes.Value(index); + const gp_Pnt2d& _uv_node = triangulation->UVNode(index); this->uv_nodes.row(i) << _uv_node.X(), _uv_node.Y(); i++; } } // - const TColgp_Array1OfPnt &_nodes = triangulation->Nodes(); - this->xyz_nodes.resize(triangulation->NbNodes(), 3); + this->xyz_nodes.resize(numNodes, 3); i = 0; - for (Standard_Integer index = _nodes.Lower(); index <= _nodes.Upper(); ++index) + for (Standard_Integer index = 1; index <= numNodes; ++index) { - gp_Pnt _node = _nodes.Value(index); + gp_Pnt _node = triangulation->Node(index); this->xyz_nodes.row(i) << _node.X(), _node.Y(), _node.Z(); i++; } - - const Poly_Array1OfTriangle &_tris = triangulation->Triangles(); - this->tris.resize(triangulation->NbTriangles(), 3); + + this->tris.resize(numTriangles, 3); i = 0; - for (Standard_Integer index = _tris.Lower(); index <= _tris.Upper(); ++index) + for (Standard_Integer index = 1; index <= numTriangles; ++index) { int n1, n2, n3; - Poly_Triangle _tri = _tris.Value(index); + const Poly_Triangle& _tri = triangulation->Triangle(index); _tri.Get(n1, n2, n3); this->tris.row(i) << n1-1, n2-1, n3-1; i++; From 82e3bc58443ee837c040a29cdea90cb733fdcaaa Mon Sep 17 00:00:00 2001 From: Chris Hennes Date: Tue, 12 Oct 2021 11:53:48 -0500 Subject: [PATCH 14/14] [Spreadsheet] Clean up compilation warnings --- src/Mod/Spreadsheet/Gui/LineEdit.cpp | 2 +- src/Mod/Spreadsheet/Gui/SheetTableView.h | 2 +- src/Mod/Spreadsheet/Gui/SpreadsheetDelegate.cpp | 1 + src/Mod/Spreadsheet/Gui/SpreadsheetView.cpp | 2 +- 4 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/Mod/Spreadsheet/Gui/LineEdit.cpp b/src/Mod/Spreadsheet/Gui/LineEdit.cpp index 3116dcc6f7..f5b5f5b7d6 100644 --- a/src/Mod/Spreadsheet/Gui/LineEdit.cpp +++ b/src/Mod/Spreadsheet/Gui/LineEdit.cpp @@ -35,13 +35,13 @@ using namespace SpreadsheetGui; LineEdit::LineEdit(QWidget *parent) : Gui::ExpressionLineEdit(parent, false, true) , lastKeyPressed(0) - , lastModifiers(0) { setFocusPolicy(Qt::FocusPolicy::ClickFocus); } bool LineEdit::eventFilter(QObject* object, QEvent* event) { + Q_UNUSED(object); if (event && event->type() == QEvent::KeyPress) { QKeyEvent* keyEvent = static_cast(event); if (keyEvent->key() == Qt::Key_Tab) { diff --git a/src/Mod/Spreadsheet/Gui/SheetTableView.h b/src/Mod/Spreadsheet/Gui/SheetTableView.h index fb0f260a23..828856fd4f 100644 --- a/src/Mod/Spreadsheet/Gui/SheetTableView.h +++ b/src/Mod/Spreadsheet/Gui/SheetTableView.h @@ -83,8 +83,8 @@ protected: void mousePressEvent(QMouseEvent* event); QModelIndex currentEditIndex; - int tabCounter; Spreadsheet::Sheet * sheet; + int tabCounter; boost::signals2::scoped_connection cellSpanChangedConnection; }; diff --git a/src/Mod/Spreadsheet/Gui/SpreadsheetDelegate.cpp b/src/Mod/Spreadsheet/Gui/SpreadsheetDelegate.cpp index 682e22b0c4..6ced58e311 100644 --- a/src/Mod/Spreadsheet/Gui/SpreadsheetDelegate.cpp +++ b/src/Mod/Spreadsheet/Gui/SpreadsheetDelegate.cpp @@ -46,6 +46,7 @@ QWidget *SpreadsheetDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &, const QModelIndex &index) const { + Q_UNUSED(index) SpreadsheetGui::LineEdit *editor = new SpreadsheetGui::LineEdit(parent); editor->setDocumentObject(sheet); connect(editor, &SpreadsheetGui::LineEdit::finishedWithKey, this, &SpreadsheetDelegate::on_editorFinishedWithKey); diff --git a/src/Mod/Spreadsheet/Gui/SpreadsheetView.cpp b/src/Mod/Spreadsheet/Gui/SpreadsheetView.cpp index 5addb23b96..3d2056cb3f 100644 --- a/src/Mod/Spreadsheet/Gui/SpreadsheetView.cpp +++ b/src/Mod/Spreadsheet/Gui/SpreadsheetView.cpp @@ -352,7 +352,7 @@ void SheetView::confirmAliasChanged(const QString& text) void SheetView::confirmContentChanged(const QString& text) { QModelIndex i = ui->cells->currentIndex(); - ui->cells->model()->setData(i, QVariant(ui->cellContent->text()), Qt::EditRole); + ui->cells->model()->setData(i, QVariant(text), Qt::EditRole); ui->cells->setFocus(); }