Loading files
0e185510cThis file is pinned to the selected release. Download the original to open it in your design tool.
/*
* LibrePCB - Professional EDA for everyone!
* Copyright (C) 2013 LibrePCB Developers, see AUTHORS.md for contributors.
* https://librepcb.org/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/*******************************************************************************
* Includes
******************************************************************************/
#include "symboltab.h"
#include "../../cmd/cmdtextedit.h"
#include "../../graphics/graphicsscene.h"
#include "../../graphics/slintgraphicsview.h"
#include "../../guiapplication.h"
#include "../../mainwindow.h"
#include "../../rulecheck/rulecheckmessagesmodel.h"
#include "../../undostack.h"
#include "../../utils/editortoolbox.h"
#include "../../utils/imagehelpers.h"
#include "../../utils/slinthelpers.h"
#include "../../utils/uihelpers.h"
#include "../../workspace/categorytreemodel.h"
#include "../../workspace/desktopservices.h"
#include "../cmd/cmdlibraryelementedit.h"
#include "../cmd/cmdsymbolpinedit.h"
#include "../cmd/cmdsymbolreload.h"
#include "../libraryeditor.h"
#include "../libraryelementcategoriesmodel.h"
#include "fsm/symboleditorfsm.h"
#include "fsm/symboleditorstate_addnames.h"
#include "fsm/symboleditorstate_addpins.h"
#include "fsm/symboleditorstate_addvalues.h"
#include "fsm/symboleditorstate_drawarc.h"
#include "fsm/symboleditorstate_drawcircle.h"
#include "fsm/symboleditorstate_drawline.h"
#include "fsm/symboleditorstate_drawpolygon.h"
#include "fsm/symboleditorstate_drawrect.h"
#include "fsm/symboleditorstate_drawtext.h"
#include "graphics/graphicslayerlist.h"
#include "symbolgraphicsitem.h"
#include <librepcb/core/fileio/transactionaldirectory.h>
#include <librepcb/core/fileio/transactionalfilesystem.h>
#include <librepcb/core/library/library.h>
#include <librepcb/core/library/librarybaseelementcheckmessages.h>
#include <librepcb/core/library/libraryelementcheckmessages.h>
#include <librepcb/core/library/sym/symbol.h>
#include <librepcb/core/library/sym/symbolcheckmessages.h>
#include <librepcb/core/library/sym/symbolpainter.h>
#include <librepcb/core/workspace/colorrole.h>
#include <librepcb/core/workspace/workspace.h>
#include <librepcb/core/workspace/workspacelibrarydb.h>
#include <librepcb/core/workspace/workspacesettings.h>
#include <QtCore>
#include <QtWidgets>
#include <memory>
/*******************************************************************************
* Namespace
******************************************************************************/
namespace librepcb {
namespace editor {
/*******************************************************************************
* Constructors / Destructor
******************************************************************************/
SymbolTab::SymbolTab(LibraryEditor& editor, std::unique_ptr<Symbol> sym,
Mode mode, QObject* parent) noexcept
: LibraryEditorTab(editor, parent),
onDerivedUiDataChanged(*this),
mSymbol(std::move(sym)),
mLayers(GraphicsLayerList::libraryLayers(
&mEditor.getWorkspace().getSettings())),
mView(new SlintGraphicsView(SlintGraphicsView::defaultSymbolSceneRect(),
SlintGraphicsView::defaultEditorMargins(),
this)),
mIsNewElement(isPathOutsideLibDir()),
mMsgImportPins(mApp.getWorkspace(), "EMPTY_SYMBOL_IMPORT_PINS"),
mWizardMode(mode != Mode::Open),
mCurrentPageIndex(mWizardMode ? 0 : 1),
mGridStyle(mApp.getWorkspace().getSettings().schematicGridStyle.get()),
mUnit(LengthUnit::millimeters()),
mChooseCategory(false),
mElementDuplicated(false),
mCompactLayout(false),
mFrameIndex(0),
mNameParsed(mSymbol->getNames().getDefaultValue()),
mVersionParsed(mSymbol->getVersion()),
mCategories(new LibraryElementCategoriesModel(
editor.getWorkspace(),
LibraryElementCategoriesModel::Type::ComponentCategory)),
mCategoriesTree(new CategoryTreeModel(editor.getWorkspace().getLibraryDb(),
editor.getWorkspace().getSettings(),
CategoryTreeModel::Filter::CmpCat)),
mToolFeatures(),
mTool(ui::EditorTool::Select),
mToolCursorShape(Qt::ArrowCursor),
mToolLayers(std::make_shared<slint::VectorModel<slint::SharedString>>()),
mToolLayer(nullptr),
mToolLineWidth(mApp.getWorkspace().getSettings()),
mToolSize(mApp.getWorkspace().getSettings()),
mToolFilled(false),
mToolGrabArea(false),
mToolValueSuggestions(
std::make_shared<slint::VectorModel<slint::SharedString>>()),
mIsInterfaceBroken(false),
mOriginalSymbolPinUuids(mSymbol->getPins().getUuidSet()) {
// Setup graphics view.
mView->setEventHandler(this);
mView->setUseOpenGl(mApp.getWorkspace().getSettings().useOpenGl.get());
connect(
&mApp.getWorkspace().getSettings().useOpenGl,
&WorkspaceSettingsItem::edited, this, [this]() {
mView->setUseOpenGl(mApp.getWorkspace().getSettings().useOpenGl.get());
});
connect(mView.get(), &SlintGraphicsView::transformChanged, this,
&SymbolTab::requestRepaint);
connect(mView.get(), &SlintGraphicsView::stateChanged, this,
&SymbolTab::notifyDerivedUiDataChanged);
// Connect undo stack.
connect(mUndoStack.get(), &UndoStack::stateModified, this, [this]() {
mAutoReloadOnFileModifications = false; // Disable auto-reload.
scheduleChecks();
refreshUiData();
});
// Connect models.
connect(mCategories.get(), &LibraryElementCategoriesModel::modified, this,
&SymbolTab::commitUiData, Qt::QueuedConnection);
// Setup messages.
connect(&mMsgImportPins, &DismissableMessageContext::visibilityChanged, this,
[this]() { onDerivedUiDataChanged.notify(); });
// Load finite state machine (FSM).
SymbolEditorFsm::Context fsmContext{*mSymbol, *mUndoStack, mUnit, *this};
mFsm = std::make_unique<SymbolEditorFsm>(fsmContext);
// Apply workspace settings whenever they have been modified.
connect(&mApp.getWorkspace().getSettings().schematicGridStyle,
&WorkspaceSettingsItem::edited, this, [this]() {
mGridStyle =
mApp.getWorkspace().getSettings().schematicGridStyle.get();
applyWorkspaceSettings();
});
connect(&mApp.getWorkspace().getSettings().schematicColorSchemes,
&WorkspaceSettingsItem_ColorSchemes::colorsModified, this,
&SymbolTab::applyWorkspaceSettings);
// Refresh content.
refreshUiData();
scheduleChecks();
// Setup file system watcher.
updateWatchedFiles();
// Clear name for new elements so the user can just start typing.
if (mode == Mode::New) {
mName = slint::SharedString();
validateElementName(s2q(mName), mNameError);
}
// Make save button primary if it's a new element.
if (mode != Mode::Open) {
mManualModificationsMade = true;
}
}
SymbolTab::~SymbolTab() noexcept {
deactivate();
// Clean up the state machine nicely to avoid unexpected behavior. Triggering
// abort (Esc) two times is usually sufficient to leave any active tool, so
// let's call it three times to be on the safe side. Unfortunately there's
// no clean way to forcible and guaranteed leaving a tool.
mFsm->processAbortCommand();
mFsm->processAbortCommand();
mFsm->processAbortCommand();
mFsm.reset();
// Delete all command objects in the undo stack. This mmust be done before
// other important objects are deleted, as undo command objects can hold
// pointers/references to them!
mUndoStack->clear();
mUndoStack.reset();
mView->setEventHandler(nullptr);
}
/*******************************************************************************
* General Methods
******************************************************************************/
FilePath SymbolTab::getDirectoryPath() const noexcept {
return mSymbol->getDirectory().getAbsPath();
}
ui::TabData SymbolTab::getUiData() const noexcept {
const bool writable = fsmIsWritable();
ui::TabFeatures features = {};
features.save = toFs(writable);
features.undo = toFs(mUndoStack->canUndo());
features.redo = toFs(mUndoStack->canRedo());
if ((!mWizardMode) && ((!mCompactLayout) || (mCurrentPageIndex == 1))) {
features.grid = toFs(writable);
features.zoom = toFs(true);
features.import_graphics =
toFs(mToolFeatures.testFlag(Feature::ImportGraphics));
features.export_graphics = toFs(mTool == ui::EditorTool::Select);
features.select = toFs(mToolFeatures.testFlag(Feature::Select));
features.cut = toFs(mToolFeatures.testFlag(Feature::Cut));
features.copy = toFs(mToolFeatures.testFlag(Feature::Copy));
features.paste = toFs(mToolFeatures.testFlag(Feature::Paste));
features.remove = toFs(mToolFeatures.testFlag(Feature::Remove));
features.rotate = toFs(mToolFeatures.testFlag(Feature::Rotate));
features.mirror = toFs(mToolFeatures.testFlag(Feature::Mirror));
features.snap_to_grid = toFs(mToolFeatures.testFlag(Feature::SnapToGrid));
features.edit_properties =
toFs(mToolFeatures.testFlag(Feature::Properties));
}
return ui::TabData{
ui::TabType::Symbol, // Type
q2s(*mSymbol->getNames().getDefaultValue()), // Title
features, // Features
!writable, // Read-only
hasUnsavedChanges(), // Unsaved changes
q2s(mUndoStack->getUndoCmdText()), // Undo text
q2s(mUndoStack->getRedoCmdText()), // Redo text
slint::SharedString(), // Find term
nullptr, // Find suggestions
nullptr, // Layers
};
}
ui::SymbolTabData SymbolTab::getDerivedUiData() const noexcept {
const ColorScheme& scheme =
mApp.getWorkspace().getSettings().schematicColorSchemes.getActive();
const auto infoBoxColors = scheme.getColors(ColorRole::schematicInfoBox());
const QColor bgColor =
scheme.getColors(ColorRole::schematicBackground()).primary;
const QColor fgColor = (bgColor.lightnessF() >= 0.5) ? Qt::black : Qt::white;
return ui::SymbolTabData{
mEditor.getUiIndex(), // Library index
q2s(mSymbol->getDirectory().getAbsPath().toStr()), // Path
mWizardMode, // Wizard mode
mCurrentPageIndex, // Page index
mName, // Name
mNameError, // Name error
mDescription, // Description
mKeywords, // Keywords
mAuthor, // Author
mAgeDays, // Age in days
mVersion, // Version
mVersionError, // Version error
mDeprecated, // Deprecated
mCategories, // Categories
mCategoriesTree, // Categories tree
mChooseCategory, // Choose category
ui::RuleCheckData{
ui::RuleCheckType::SymbolCheck, // Check type
ui::RuleCheckState::UpToDate, // Check state
mCheckMessages, // Check messages
mCheckMessages->getUnapprovedCount(), // Check unapproved count
mCheckMessages->getErrorCount(), // Check errors count
mCheckError, // Check execution error
!fsmIsWritable(), // Check read-only
},
q2s(bgColor), // Background color
q2s(fgColor), // Foreground color
q2s(infoBoxColors.primary), // Overlay color
q2s(infoBoxColors.secondary), // Overlay text color
l2s(mGridStyle), // Grid style
l2s(*mSymbol->getGridInterval()), // Grid interval
l2s(mUnit), // Unit
!mModifiedWatchedFiles.isEmpty(), // Watched files modified
mIsInterfaceBroken, // Interface broken
mElementDuplicated && (!mDeprecated), // Element duplicated
mMsgImportPins.getUiData(), // Message "import pins"
mTool, // Tool
q2s(mView->isPanning() ? Qt::ClosedHandCursor
: mToolCursorShape), // Tool cursor
q2s(mToolOverlayText), // Tool overlay text
ui::ComboBoxData{
// Tool layer
mToolLayers, // Items
static_cast<int>(mToolLayersQt.indexOf(mToolLayer)), // Current index
},
mToolLineWidth.getUiData(), // Tool line width
mToolSize.getUiData(), // // Tool size
ui::AngleEditData{
// Tool angle
l2s(mToolAngle), // Angle
false, // Increase
false, // Decrease
},
mToolFilled, // Tool filled
mToolGrabArea, // Tool grab area
ui::LineEditData{
// Tool value
true, // Enabled
q2s(EditorToolbox::toSingleLine(mToolValue)), // Text
slint::SharedString(), // Placeholder
mToolValueSuggestions, // Suggestions
},
l2s(mToolAlign.getH()), // Tool horizontal alignment
l2s(mToolAlign.getV()), // Tool vertical alignment
mCompactLayout, // Compact layout
q2s(mSceneImagePos), // Scene image position
mFrameIndex, // Frame index
slint::SharedString(), // New category
};
}
void SymbolTab::setDerivedUiData(const ui::SymbolTabData& data) noexcept {
// General
if (data.page_index != mCurrentPageIndex) {
mCurrentPageIndex = data.page_index;
onUiDataChanged.notify();
}
if (data.compact_layout != mCompactLayout) {
mCompactLayout = data.compact_layout;
onUiDataChanged.notify();
}
mSceneImagePos = s2q(data.scene_image_pos);
// Metadata
mName = data.name;
if (auto value = validateElementName(s2q(mName), mNameError)) {
mNameParsed = *value;
}
mDescription = data.description;
mKeywords = data.keywords;
mAuthor = data.author;
mVersion = data.version;
if (auto value = validateVersion(s2q(mVersion), mVersionError)) {
mVersionParsed = *value;
}
mDeprecated = data.deprecated;
if (auto uuid = Uuid::tryFromString(s2q(data.new_category))) {
mCategories->add(*uuid);
}
mChooseCategory = data.choose_category;
// View
mGridStyle = s2l(data.grid_style);
if (auto interval = s2plength(data.grid_interval)) {
setGridInterval(*interval);
}
if (mScene) {
mScene->setGridStyle(mGridStyle);
mScene->setGridInterval(mSymbol->getGridInterval());
}
const LengthUnit unit = s2l(data.unit);
if (unit != mUnit) {
mUnit = unit;
}
// Messages
mMsgImportPins.setUiData(data.import_pins_msg);
if (data.deprecated || (!data.element_duplicated_msg)) {
mElementDuplicated = false;
}
// Tool
if (const Layer* layer = mToolLayersQt.value(data.tool_layer.current_index)) {
emit layerRequested(*layer);
}
if (data.tool_angle.increase) {
emit angleRequested(mToolAngle + Angle::deg45());
} else if (data.tool_angle.decrease) {
emit angleRequested(mToolAngle - Angle::deg45());
} else {
emit angleRequested(s2angle(data.tool_angle.value));
}
emit filledRequested(data.tool_filled);
emit grabAreaRequested(data.tool_grab_area);
mToolLineWidth.setUiData(data.tool_line_width);
mToolSize.setUiData(data.tool_size);
emit valueRequested(EditorToolbox::toMultiLine(s2q(data.tool_value.text)));
emit hAlignRequested(s2l(data.tool_halign));
emit vAlignRequested(s2l(data.tool_valign));
requestRepaint();
}
void SymbolTab::activate() noexcept {
mScene = std::make_unique<GraphicsScene>(this);
mScene->setGridInterval(mSymbol->getGridInterval());
connect(mScene.get(), &GraphicsScene::changed, this,
&SymbolTab::requestRepaint);
mGraphicsItem = std::make_unique<SymbolGraphicsItem>(
*mSymbol, *mLayers, QPointer<const Component>{},
std::shared_ptr<const ComponentSymbolVariantItem>{}, QStringList{},
false);
mScene->addItem(*mGraphicsItem);
applyWorkspaceSettings();
requestRepaint();
}
void SymbolTab::deactivate() noexcept {
mGraphicsItem.reset();
mScene.reset();
}
void SymbolTab::trigger(ui::TabAction a) noexcept {
switch (a) {
case ui::TabAction::Next: {
if (mWizardMode) {
mWizardMode = false;
mCurrentPageIndex = 1;
scheduleChecks();
}
onDerivedUiDataChanged.notify();
break;
}
case ui::TabAction::Apply: {
commitUiData();
refreshUiData();
break;
}
case ui::TabAction::Save: {
commitUiData();
save();
break;
}
case ui::TabAction::SaveAs: { // Duplicate the library element.
commitUiData(); // Exits the current tool and releases the undo stack.
if (mWindow && mWindow->openNewSymbolTab(mEditor, {}, mSymbol.get())) {
undoBreakingChanges(mIsInterfaceBroken);
mElementDuplicated = true;
onDerivedUiDataChanged.notify();
}
break;
}
case ui::TabAction::ReloadFromDisk: {
try {
reloadFromDisk();
} catch (const Exception& e) {
QMessageBox::critical(getWindow(), tr("Error"), e.getMsg());
}
break;
}
case ui::TabAction::Undo: {
try {
commitUiData();
mUndoStack->undo();
} catch (const Exception& e) {
QMessageBox::critical(getWindow(), tr("Error"), e.getMsg());
}
break;
}
case ui::TabAction::Redo: {
try {
commitUiData();
mUndoStack->redo();
} catch (const Exception& e) {
QMessageBox::critical(getWindow(), tr("Error"), e.getMsg());
}
break;
}
case ui::TabAction::Unlock: {
mAllowBreakingChanges = true;
onUiDataChanged.notify();
onDerivedUiDataChanged.notify();
break;
}
case ui::TabAction::Close: {
if (requestClose()) {
WindowTab::trigger(a);
}
break;
}
case ui::TabAction::Print: {
execGraphicsExportDialog(GraphicsExportDialog::Output::Print, "print");
break;
}
case ui::TabAction::ExportImage: {
execGraphicsExportDialog(GraphicsExportDialog::Output::Image,
"image_export");
break;
}
case ui::TabAction::ExportPdf: {
execGraphicsExportDialog(GraphicsExportDialog::Output::Pdf, "pdf_export");
break;
}
case ui::TabAction::ImportDxf: {
mFsm->processStartDxfImport();
break;
}
case ui::TabAction::SelectAll: {
mFsm->processSelectAll();
break;
}
case ui::TabAction::Abort: {
mFsm->processAbortCommand();
break;
}
case ui::TabAction::Cut: {
mFsm->processCut();
break;
}
case ui::TabAction::Copy: {
mFsm->processCopy();
break;
}
case ui::TabAction::Paste: {
QByteArray data;
QString basename;
QString format;
if (ImageHelpers::getImageFromClipboard(data, format, basename)) {
mFsm->processStartAddingImage(data, format, basename);
} else {
mFsm->processPaste();
}
break;
}
case ui::TabAction::Delete: {
mFsm->processRemove();
break;
}
case ui::TabAction::RotateCcw: {
mFsm->processRotate(Angle::deg90());
break;
}
case ui::TabAction::RotateCw: {
mFsm->processRotate(-Angle::deg90());
break;
}
case ui::TabAction::MirrorHorizontally: {
mFsm->processMirror(Qt::Horizontal);
break;
}
case ui::TabAction::MirrorVertically: {
mFsm->processMirror(Qt::Vertical);
break;
}
case ui::TabAction::MoveLeft: {
if (!mFsm->processMove(Point(-mSymbol->getGridInterval(), 0))) {
mView->scrollLeft();
}
break;
}
case ui::TabAction::MoveRight: {
if (!mFsm->processMove(Point(*mSymbol->getGridInterval(), 0))) {
mView->scrollRight();
}
break;
}
case ui::TabAction::MoveUp: {
if (!mFsm->processMove(Point(0, *mSymbol->getGridInterval()))) {
mView->scrollUp();
}
break;
}
case ui::TabAction::MoveDown: {
if (!mFsm->processMove(Point(0, -mSymbol->getGridInterval()))) {
mView->scrollDown();
}
break;
}
case ui::TabAction::SnapToGrid: {
mFsm->processSnapToGrid();
break;
}
case ui::TabAction::EditProperties: {
mFsm->processEditProperties();
break;
}
case ui::TabAction::GridIntervalIncrease: {
setGridInterval(PositiveLength(mSymbol->getGridInterval() * 2));
break;
}
case ui::TabAction::GridIntervalDecrease: {
if ((*mSymbol->getGridInterval() % 2) == 0) {
setGridInterval(PositiveLength(mSymbol->getGridInterval() / 2));
}
break;
}
case ui::TabAction::ZoomIn: {
mView->zoomIn();
break;
}
case ui::TabAction::ZoomOut: {
mView->zoomOut();
break;
}
case ui::TabAction::ZoomFit: {
if (mScene) mView->zoomToSceneRect(mScene->itemsBoundingRect(), true);
break;
}
case ui::TabAction::SymbolImportPins: {
mFsm->processStartAddingSymbolPins(true);
break;
}
case ui::TabAction::ToolSelect: {
mFsm->processStartSelecting();
break;
}
case ui::TabAction::ToolLine: {
mFsm->processStartDrawLines();
break;
}
case ui::TabAction::ToolRect: {
mFsm->processStartDrawRects();
break;
}
case ui::TabAction::ToolPolygon: {
mFsm->processStartDrawPolygons();
break;
}
case ui::TabAction::ToolCircle: {
mFsm->processStartDrawCircles();
break;
}
case ui::TabAction::ToolArc: {
mFsm->processStartDrawArcs();
break;
}
case ui::TabAction::ToolName: {
mFsm->processStartAddingNames();
break;
}
case ui::TabAction::ToolValue: {
mFsm->processStartAddingValues();
break;
}
case ui::TabAction::ToolText: {
mFsm->processStartDrawTexts();
break;
}
case ui::TabAction::ToolImage: {
mFsm->processStartAddingImage();
break;
}
case ui::TabAction::ToolPin: {
mFsm->processStartAddingSymbolPins(false);
break;
}
case ui::TabAction::ToolMeasure: {
mFsm->processStartMeasure();
break;
}
default: {
WindowTab::trigger(a);
break;
}
}
}
slint::Image SymbolTab::renderScene(float width, float height,
int scene) noexcept {
Q_UNUSED(scene);
if (mScene) {
return mView->render(*mScene, width, height);
}
return slint::Image();
}
void SymbolTab::processScenePointerEvent(const QPointF& pos,
slint::private_api::PointerEvent e,
int scene) noexcept {
Q_UNUSED(scene);
mView->pointerEvent(pos, e);
}
bool SymbolTab::processSceneScrolled(const QPointF& pos,
slint::private_api::PointerScrollEvent e,
int scene) noexcept {
Q_UNUSED(scene);
return mView->scrollEvent(pos, e);
}
bool SymbolTab::processSceneKeyPressed(
const slint::language::KeyEvent& e) noexcept {
return mView->keyPressed(e);
}
bool SymbolTab::processSceneKeyReleased(
const slint::language::KeyEvent& e) noexcept {
return mView->keyReleased(e);
}
bool SymbolTab::requestClose() noexcept {
commitUiData();
if (!hasUnsavedChanges()) {
return true; // Nothing to save.
}
QMessageBox::StandardButtons buttons = QMessageBox::No | QMessageBox::Cancel;
QMessageBox::StandardButton defaultButton = QMessageBox::Cancel;
if ((!mIsInterfaceBroken) || mAllowBreakingChanges) {
buttons |= QMessageBox::Yes;
defaultButton = QMessageBox::Yes;
}
const QMessageBox::StandardButton choice =
QMessageBox::question(getWindow(), tr("Save Changes?"),
tr("The symbol '%1' contains unsaved changes.\n"
"Do you want to save them before closing it?")
.arg(*mSymbol->getNames().getDefaultValue()),
buttons, defaultButton);
if (choice == QMessageBox::Yes) {
return save();
} else if (choice == QMessageBox::No) {
return true;
} else {
return false;
}
}
/*******************************************************************************
* IF_GraphicsViewEventHandler Methods
******************************************************************************/
bool SymbolTab::graphicsSceneKeyPressed(
const GraphicsSceneKeyEvent& e) noexcept {
return mFsm->processKeyPressed(e);
}
bool SymbolTab::graphicsSceneKeyReleased(
const GraphicsSceneKeyEvent& e) noexcept {
return mFsm->processKeyReleased(e);
}
bool SymbolTab::graphicsSceneMouseMoved(
const GraphicsSceneMouseEvent& e) noexcept {
emit cursorCoordinatesChanged(e.scenePos, mUnit);
return mFsm->processGraphicsSceneMouseMoved(e);
}
bool SymbolTab::graphicsSceneLeftMouseButtonPressed(
const GraphicsSceneMouseEvent& e) noexcept {
return mFsm->processGraphicsSceneLeftMouseButtonPressed(e);
}
bool SymbolTab::graphicsSceneLeftMouseButtonReleased(
const GraphicsSceneMouseEvent& e) noexcept {
return mFsm->processGraphicsSceneLeftMouseButtonReleased(e);
}
bool SymbolTab::graphicsSceneLeftMouseButtonDoubleClicked(
const GraphicsSceneMouseEvent& e) noexcept {
return mFsm->processGraphicsSceneLeftMouseButtonDoubleClicked(e);
}
bool SymbolTab::graphicsSceneRightMouseButtonReleased(
const GraphicsSceneMouseEvent& e) noexcept {
return mFsm->processGraphicsSceneRightMouseButtonReleased(e);
}
/*******************************************************************************
* SymbolEditorFsmAdapter
******************************************************************************/
bool SymbolTab::fsmIsWritable() const noexcept {
return mIsNewElement ||
(mSymbol->getDirectory().isWritable() &&
((!mIsInterfaceBroken) || mAllowBreakingChanges));
}
QWidget* SymbolTab::fsmGetParentWidget() noexcept {
return getWindow();
}
GraphicsScene* SymbolTab::fsmGetGraphicsScene() noexcept {
return mScene.get();
}
SymbolGraphicsItem* SymbolTab::fsmGetGraphicsItem() noexcept {
return mGraphicsItem.get();
}
PositiveLength SymbolTab::fsmGetGridInterval() const noexcept {
return mSymbol->getGridInterval();
}
void SymbolTab::fsmSetViewCursor(
const std::optional<Qt::CursorShape>& shape) noexcept {
if (shape) {
mToolCursorShape = *shape;
} else {
mToolCursorShape = Qt::ArrowCursor;
}
onDerivedUiDataChanged.notify();
}
void SymbolTab::fsmSetViewGrayOut(bool grayOut) noexcept {
if (mScene) {
mScene->setGrayOut(grayOut);
}
}
void SymbolTab::fsmSetViewInfoBoxText(const QString& text) noexcept {
QString t = text;
t.replace(" ", " ");
t.replace("<br>", "\n");
t.replace("<b>", "");
t.replace("</b>", "");
if (t != mToolOverlayText) {
mToolOverlayText = t;
onDerivedUiDataChanged.notify();
}
}
void SymbolTab::fsmSetViewRuler(
const std::optional<std::pair<Point, Point>>& pos) noexcept {
if (mScene) {
mScene->setRulerPositions(pos);
}
}
void SymbolTab::fsmSetSceneCursor(const Point& pos, bool cross,
bool circle) noexcept {
if (mScene) {
mScene->setSceneCursor(pos, cross, circle);
}
}
QPainterPath SymbolTab::fsmCalcPosWithTolerance(
const Point& pos, qreal multiplier) const noexcept {
return mView->calcPosWithTolerance(pos, multiplier);
}
Point SymbolTab::fsmMapGlobalPosToScenePos(const QPoint& pos) const noexcept {
if (QWidget* win = getWindow()) {
return mView->mapToScenePos(win->mapFromGlobal(pos) - mSceneImagePos,
win->devicePixelRatioF());
} else {
qWarning() << "Failed to map global position to scene position.";
return Point();
}
}
void SymbolTab::fsmSetStatusBarMessage(const QString& message,
int timeoutMs) noexcept {
emit statusBarMessageChanged(message, timeoutMs);
}
void SymbolTab::fsmSetFeatures(Features features) noexcept {
if (features != mToolFeatures) {
mToolFeatures = features;
onUiDataChanged.notify();
}
}
void SymbolTab::fsmToolLeave() noexcept {
while (!mFsmStateConnections.isEmpty()) {
disconnect(mFsmStateConnections.takeLast());
}
mTool = ui::EditorTool::Select;
fsmSetFeatures(Features());
onDerivedUiDataChanged.notify();
}
void SymbolTab::fsmToolEnter(SymbolEditorState_Select& state) noexcept {
Q_UNUSED(state);
mTool = ui::EditorTool::Select;
onDerivedUiDataChanged.notify();
}
void SymbolTab::fsmToolEnter(SymbolEditorState_DrawLine& state) noexcept {
mTool = ui::EditorTool::Line;
// Layers
mToolLayersQt = Layer::sorted(state.getAvailableLayers());
mToolLayers->clear();
for (const Layer* layer : std::as_const(mToolLayersQt)) {
mToolLayers->push_back(q2s(layer->getNameTr()));
}
// Layer
auto setLayer = [this](const Layer& layer) {
mToolLayer = &layer;
onDerivedUiDataChanged.notify();
};
setLayer(state.getLayer());
mFsmStateConnections.append(connect(
&state, &SymbolEditorState_DrawLine::layerChanged, this, setLayer));
mFsmStateConnections.append(connect(this, &SymbolTab::layerRequested, &state,
&SymbolEditorState_DrawLine::setLayer));
// Line width
mToolLineWidth.configure(state.getLineWidth(),
LengthEditContext::Steps::generic(),
"symbol_editor/draw_line/line_width");
mFsmStateConnections.append(
connect(&state, &SymbolEditorState_DrawLine::lineWidthChanged,
&mToolLineWidth, &LengthEditContext::setValueUnsigned));
mFsmStateConnections.append(
connect(&mToolLineWidth, &LengthEditContext::valueChangedUnsigned, &state,
&SymbolEditorState_DrawLine::setLineWidth));
// Angle
auto setAngle = [this](const Angle& angle) {
mToolAngle = angle;
onDerivedUiDataChanged.notify();
};
setAngle(state.getAngle());
mFsmStateConnections.append(connect(
&state, &SymbolEditorState_DrawLine::angleChanged, this, setAngle));
mFsmStateConnections.append(connect(this, &SymbolTab::angleRequested, &state,
&SymbolEditorState_DrawLine::setAngle));
onDerivedUiDataChanged.notify();
}
void SymbolTab::fsmToolEnter(SymbolEditorState_DrawRect& state) noexcept {
mTool = ui::EditorTool::Rect;
// Layers
mToolLayersQt = Layer::sorted(state.getAvailableLayers());
mToolLayers->clear();
for (const Layer* layer : std::as_const(mToolLayersQt)) {
mToolLayers->push_back(q2s(layer->getNameTr()));
}
// Layer
auto setLayer = [this](const Layer& layer) {
mToolLayer = &layer;
onDerivedUiDataChanged.notify();
};
setLayer(state.getLayer());
mFsmStateConnections.append(connect(
&state, &SymbolEditorState_DrawRect::layerChanged, this, setLayer));
mFsmStateConnections.append(connect(this, &SymbolTab::layerRequested, &state,
&SymbolEditorState_DrawRect::setLayer));
// Line width
mToolLineWidth.configure(state.getLineWidth(),
LengthEditContext::Steps::generic(),
"symbol_editor/draw_rect/line_width");
mFsmStateConnections.append(
connect(&state, &SymbolEditorState_DrawRect::lineWidthChanged,
&mToolLineWidth, &LengthEditContext::setValueUnsigned));
mFsmStateConnections.append(
connect(&mToolLineWidth, &LengthEditContext::valueChangedUnsigned, &state,
&SymbolEditorState_DrawRect::setLineWidth));
// Filled
auto setFilled = [this](bool filled) {
mToolFilled = filled;
onDerivedUiDataChanged.notify();
};
setFilled(state.getFilled());
mFsmStateConnections.append(connect(
&state, &SymbolEditorState_DrawRect::filledChanged, this, setFilled));
mFsmStateConnections.append(connect(this, &SymbolTab::filledRequested, &state,
&SymbolEditorState_DrawRect::setFilled));
// Grab area
auto setGrabArea = [this](bool grabArea) {
mToolGrabArea = grabArea;
onDerivedUiDataChanged.notify();
};
setGrabArea(state.getGrabArea());
mFsmStateConnections.append(connect(
&state, &SymbolEditorState_DrawRect::grabAreaChanged, this, setGrabArea));
mFsmStateConnections.append(
connect(this, &SymbolTab::grabAreaRequested, &state,
&SymbolEditorState_DrawRect::setGrabArea));
onDerivedUiDataChanged.notify();
}
void SymbolTab::fsmToolEnter(SymbolEditorState_DrawPolygon& state) noexcept {
mTool = ui::EditorTool::Polygon;
// Layers
mToolLayersQt = Layer::sorted(state.getAvailableLayers());
mToolLayers->clear();
for (const Layer* layer : std::as_const(mToolLayersQt)) {
mToolLayers->push_back(q2s(layer->getNameTr()));
}
// Layer
auto setLayer = [this](const Layer& layer) {
mToolLayer = &layer;
onDerivedUiDataChanged.notify();
};
setLayer(state.getLayer());
mFsmStateConnections.append(connect(
&state, &SymbolEditorState_DrawPolygon::layerChanged, this, setLayer));
mFsmStateConnections.append(
connect(this, &SymbolTab::layerRequested, &state,
&SymbolEditorState_DrawPolygon::setLayer));
// Line width
mToolLineWidth.configure(state.getLineWidth(),
LengthEditContext::Steps::generic(),
"symbol_editor/draw_polygon/line_width");
mFsmStateConnections.append(
connect(&state, &SymbolEditorState_DrawPolygon::lineWidthChanged,
&mToolLineWidth, &LengthEditContext::setValueUnsigned));
mFsmStateConnections.append(
connect(&mToolLineWidth, &LengthEditContext::valueChangedUnsigned, &state,
&SymbolEditorState_DrawPolygon::setLineWidth));
// Angle
auto setAngle = [this](const Angle& angle) {
mToolAngle = angle;
onDerivedUiDataChanged.notify();
};
setAngle(state.getAngle());
mFsmStateConnections.append(connect(
&state, &SymbolEditorState_DrawPolygon::angleChanged, this, setAngle));
mFsmStateConnections.append(
connect(this, &SymbolTab::angleRequested, &state,
&SymbolEditorState_DrawPolygon::setAngle));
// Filled
auto setFilled = [this](bool filled) {
mToolFilled = filled;
onDerivedUiDataChanged.notify();
};
setFilled(state.getFilled());
mFsmStateConnections.append(connect(
&state, &SymbolEditorState_DrawPolygon::filledChanged, this, setFilled));
mFsmStateConnections.append(
connect(this, &SymbolTab::filledRequested, &state,
&SymbolEditorState_DrawPolygon::setFilled));
// Grab area
auto setGrabArea = [this](bool grabArea) {
mToolGrabArea = grabArea;
onDerivedUiDataChanged.notify();
};
setGrabArea(state.getGrabArea());
mFsmStateConnections.append(
connect(&state, &SymbolEditorState_DrawPolygon::grabAreaChanged, this,
setGrabArea));
mFsmStateConnections.append(
connect(this, &SymbolTab::grabAreaRequested, &state,
&SymbolEditorState_DrawPolygon::setGrabArea));
onDerivedUiDataChanged.notify();
}
void SymbolTab::fsmToolEnter(SymbolEditorState_DrawCircle& state) noexcept {
mTool = ui::EditorTool::Circle;
// Layers
mToolLayersQt = Layer::sorted(state.getAvailableLayers());
mToolLayers->clear();
for (const Layer* layer : std::as_const(mToolLayersQt)) {
mToolLayers->push_back(q2s(layer->getNameTr()));
}
// Layer
auto setLayer = [this](const Layer& layer) {
mToolLayer = &layer;
onDerivedUiDataChanged.notify();
};
setLayer(state.getLayer());
mFsmStateConnections.append(connect(
&state, &SymbolEditorState_DrawCircle::layerChanged, this, setLayer));
mFsmStateConnections.append(connect(this, &SymbolTab::layerRequested, &state,
&SymbolEditorState_DrawCircle::setLayer));
// Line width
mToolLineWidth.configure(state.getLineWidth(),
LengthEditContext::Steps::generic(),
"symbol_editor/draw_circle/line_width");
mFsmStateConnections.append(
connect(&state, &SymbolEditorState_DrawCircle::lineWidthChanged,
&mToolLineWidth, &LengthEditContext::setValueUnsigned));
mFsmStateConnections.append(
connect(&mToolLineWidth, &LengthEditContext::valueChangedUnsigned, &state,
&SymbolEditorState_DrawCircle::setLineWidth));
// Filled
auto setFilled = [this](bool filled) {
mToolFilled = filled;
onDerivedUiDataChanged.notify();
};
setFilled(state.getFilled());
mFsmStateConnections.append(connect(
&state, &SymbolEditorState_DrawCircle::filledChanged, this, setFilled));
mFsmStateConnections.append(
connect(this, &SymbolTab::filledRequested, &state,
&SymbolEditorState_DrawCircle::setFilled));
// Grab area
auto setGrabArea = [this](bool grabArea) {
mToolGrabArea = grabArea;
onDerivedUiDataChanged.notify();
};
setGrabArea(state.getGrabArea());
mFsmStateConnections.append(
connect(&state, &SymbolEditorState_DrawCircle::grabAreaChanged, this,
setGrabArea));
mFsmStateConnections.append(
connect(this, &SymbolTab::grabAreaRequested, &state,
&SymbolEditorState_DrawCircle::setGrabArea));
onDerivedUiDataChanged.notify();
}
void SymbolTab::fsmToolEnter(SymbolEditorState_DrawArc& state) noexcept {
mTool = ui::EditorTool::Arc;
// Layers
mToolLayersQt = Layer::sorted(state.getAvailableLayers());
mToolLayers->clear();
for (const Layer* layer : std::as_const(mToolLayersQt)) {
mToolLayers->push_back(q2s(layer->getNameTr()));
}
// Layer
auto setLayer = [this](const Layer& layer) {
mToolLayer = &layer;
onDerivedUiDataChanged.notify();
};
setLayer(state.getLayer());
mFsmStateConnections.append(connect(
&state, &SymbolEditorState_DrawArc::layerChanged, this, setLayer));
mFsmStateConnections.append(connect(this, &SymbolTab::layerRequested, &state,
&SymbolEditorState_DrawArc::setLayer));
// Line width
mToolLineWidth.configure(state.getLineWidth(),
LengthEditContext::Steps::generic(),
"symbol_editor/draw_arc/line_width");
mFsmStateConnections.append(
connect(&state, &SymbolEditorState_DrawArc::lineWidthChanged,
&mToolLineWidth, &LengthEditContext::setValueUnsigned));
mFsmStateConnections.append(
connect(&mToolLineWidth, &LengthEditContext::valueChangedUnsigned, &state,
&SymbolEditorState_DrawArc::setLineWidth));
onDerivedUiDataChanged.notify();
}
void SymbolTab::fsmToolEnter(SymbolEditorState_AddNames& state) noexcept {
mTool = ui::EditorTool::Name;
// Height
mToolSize.configure(state.getHeight(), LengthEditContext::Steps::textHeight(),
"symbol_editor/draw_text/height");
mFsmStateConnections.append(
connect(&state, &SymbolEditorState_DrawText::heightChanged, &mToolSize,
&LengthEditContext::setValuePositive));
mFsmStateConnections.append(
connect(&mToolSize, &LengthEditContext::valueChangedPositive, &state,
&SymbolEditorState_DrawText::setHeight));
// Horizontal alignment
auto setHAlign = [this](const HAlign& align) {
mToolAlign.setH(align);
onDerivedUiDataChanged.notify();
};
setHAlign(state.getHAlign());
mFsmStateConnections.append(connect(
&state, &SymbolEditorState_DrawText::hAlignChanged, this, setHAlign));
mFsmStateConnections.append(connect(this, &SymbolTab::hAlignRequested, &state,
&SymbolEditorState_DrawText::setHAlign));
// Vertical alignment
auto setVAlign = [this](const VAlign& align) {
mToolAlign.setV(align);
onDerivedUiDataChanged.notify();
};
setVAlign(state.getVAlign());
mFsmStateConnections.append(connect(
&state, &SymbolEditorState_DrawText::vAlignChanged, this, setVAlign));
mFsmStateConnections.append(connect(this, &SymbolTab::vAlignRequested, &state,
&SymbolEditorState_DrawText::setVAlign));
onDerivedUiDataChanged.notify();
}
void SymbolTab::fsmToolEnter(SymbolEditorState_AddValues& state) noexcept {
mTool = ui::EditorTool::Value;
// Height
mToolSize.configure(state.getHeight(), LengthEditContext::Steps::textHeight(),
"symbol_editor/draw_text/height");
mFsmStateConnections.append(
connect(&state, &SymbolEditorState_DrawText::heightChanged, &mToolSize,
&LengthEditContext::setValuePositive));
mFsmStateConnections.append(
connect(&mToolSize, &LengthEditContext::valueChangedPositive, &state,
&SymbolEditorState_DrawText::setHeight));
// Horizontal alignment
auto setHAlign = [this](const HAlign& align) {
mToolAlign.setH(align);
onDerivedUiDataChanged.notify();
};
setHAlign(state.getHAlign());
mFsmStateConnections.append(connect(
&state, &SymbolEditorState_DrawText::hAlignChanged, this, setHAlign));
mFsmStateConnections.append(connect(this, &SymbolTab::hAlignRequested, &state,
&SymbolEditorState_DrawText::setHAlign));
// Vertical alignment
auto setVAlign = [this](const VAlign& align) {
mToolAlign.setV(align);
onDerivedUiDataChanged.notify();
};
setVAlign(state.getVAlign());
mFsmStateConnections.append(connect(
&state, &SymbolEditorState_DrawText::vAlignChanged, this, setVAlign));
mFsmStateConnections.append(connect(this, &SymbolTab::vAlignRequested, &state,
&SymbolEditorState_DrawText::setVAlign));
onDerivedUiDataChanged.notify();
}
void SymbolTab::fsmToolEnter(SymbolEditorState_DrawText& state) noexcept {
mTool = ui::EditorTool::Text;
// Layers
mToolLayersQt = Layer::sorted(state.getAvailableLayers());
mToolLayers->clear();
for (const Layer* layer : std::as_const(mToolLayersQt)) {
mToolLayers->push_back(q2s(layer->getNameTr()));
}
// Layer
auto setLayer = [this](const Layer& layer) {
mToolLayer = &layer;
onDerivedUiDataChanged.notify();
};
setLayer(state.getLayer());
mFsmStateConnections.append(connect(
&state, &SymbolEditorState_DrawText::layerChanged, this, setLayer));
mFsmStateConnections.append(connect(this, &SymbolTab::layerRequested, &state,
&SymbolEditorState_DrawText::setLayer));
// Height
mToolSize.configure(state.getHeight(), LengthEditContext::Steps::textHeight(),
"symbol_editor/draw_text/height");
mFsmStateConnections.append(
connect(&state, &SymbolEditorState_DrawText::heightChanged, &mToolSize,
&LengthEditContext::setValuePositive));
mFsmStateConnections.append(
connect(&mToolSize, &LengthEditContext::valueChangedPositive, &state,
&SymbolEditorState_DrawText::setHeight));
// Text
auto setText = [this](const QString& text) {
mToolValue = text;
onDerivedUiDataChanged.notify();
};
setText(state.getText());
mFsmStateConnections.append(
connect(&state, &SymbolEditorState_DrawText::textChanged, this, setText));
mFsmStateConnections.append(connect(this, &SymbolTab::valueRequested, &state,
&SymbolEditorState_DrawText::setText));
// Text suggestions
mToolValueSuggestions->clear();
for (const QString& v : state.getTextSuggestions()) {
mToolValueSuggestions->push_back(q2s(v));
}
// Horizontal alignment
auto setHAlign = [this](const HAlign& align) {
mToolAlign.setH(align);
onDerivedUiDataChanged.notify();
};
setHAlign(state.getHAlign());
mFsmStateConnections.append(connect(
&state, &SymbolEditorState_DrawText::hAlignChanged, this, setHAlign));
mFsmStateConnections.append(connect(this, &SymbolTab::hAlignRequested, &state,
&SymbolEditorState_DrawText::setHAlign));
// Vertical alignment
auto setVAlign = [this](const VAlign& align) {
mToolAlign.setV(align);
onDerivedUiDataChanged.notify();
};
setVAlign(state.getVAlign());
mFsmStateConnections.append(connect(
&state, &SymbolEditorState_DrawText::vAlignChanged, this, setVAlign));
mFsmStateConnections.append(connect(this, &SymbolTab::vAlignRequested, &state,
&SymbolEditorState_DrawText::setVAlign));
onDerivedUiDataChanged.notify();
}
void SymbolTab::fsmToolEnter(SymbolEditorState_AddImage& state) noexcept {
Q_UNUSED(state);
mTool = ui::EditorTool::Image;
onDerivedUiDataChanged.notify();
}
void SymbolTab::fsmToolEnter(SymbolEditorState_AddPins& state) noexcept {
mTool = ui::EditorTool::Pin;
// Name
auto setName = [this](const CircuitIdentifier& name) {
mToolValue = *name;
onDerivedUiDataChanged.notify();
};
setName(state.getName());
mFsmStateConnections.append(
connect(&state, &SymbolEditorState_AddPins::nameChanged, this, setName));
mFsmStateConnections.append(connect(
this, &SymbolTab::valueRequested, &state, [&state](const QString& text) {
if (auto name = parseCircuitIdentifier(cleanCircuitIdentifier(text))) {
state.setName(*name);
}
}));
// Length
mToolSize.configure(state.getLength(), LengthEditContext::Steps::pinLength(),
"symbol_editor/add_pins/length");
mFsmStateConnections.append(
connect(&state, &SymbolEditorState_AddPins::lengthChanged, &mToolSize,
&LengthEditContext::setValueUnsigned));
mFsmStateConnections.append(
connect(&mToolSize, &LengthEditContext::valueChangedUnsigned, &state,
&SymbolEditorState_AddPins::setLength));
onDerivedUiDataChanged.notify();
}
void SymbolTab::fsmToolEnter(SymbolEditorState_Measure& state) noexcept {
Q_UNUSED(state);
mTool = ui::EditorTool::Measure;
onDerivedUiDataChanged.notify();
}
/*******************************************************************************
* Protected Methods
******************************************************************************/
void SymbolTab::watchedFilesModifiedChanged() noexcept {
onDerivedUiDataChanged.notify();
}
void SymbolTab::reloadFromDisk() {
mFsm->processAbortCommand();
mFsm->processAbortCommand();
mFsm->processAbortCommand();
mUndoStack->execCmd(new CmdSymbolReload(*mSymbol));
mUndoStack->setClean();
memorizeInterface();
updateWatchedFiles();
mManualModificationsMade = false;
mAutoReloadOnFileModifications = true; // Enable auto-reload.
// This is actually already called by the undo stack change, but the
// memorized interface is updated afterwards, so we need to call it again.
refreshUiData();
}
std::optional<std::pair<RuleCheckMessageList, QSet<SExpression>>>
SymbolTab::runChecksImpl() {
// Do not run checks during wizard mode as it would be too early.
if (mWizardMode) {
return std::nullopt;
}
// Do not run checks if a tool is active because it could lead to annoying,
// flickering messages. For example when placing pins, they always overlap
// right after placing them, so we have to wait until the user has moved the
// cursor to place the pin at a different position.
if (mTool != ui::EditorTool::Select) {
return std::nullopt;
}
return std::make_pair(mSymbol->runChecks(), mSymbol->getMessageApprovals());
}
bool SymbolTab::autoFixImpl(const std::shared_ptr<const RuleCheckMessage>& msg,
bool checkOnly) {
if (autoFixHelper<MsgNameNotTitleCase>(msg, checkOnly)) return true;
if (autoFixHelper<MsgMissingAuthor>(msg, checkOnly)) return true;
if (autoFixHelper<MsgMissingCategories>(msg, checkOnly)) return true;
if (autoFixHelper<MsgMissingSymbolName>(msg, checkOnly)) return true;
if (autoFixHelper<MsgMissingSymbolValue>(msg, checkOnly)) return true;
if (autoFixHelper<MsgWrongSymbolTextLayer>(msg, checkOnly)) return true;
if (autoFixHelper<MsgSymbolPinNotOnGrid>(msg, checkOnly)) return true;
if (autoFixHelper<MsgNonFunctionalSymbolPinInversionSign>(msg, checkOnly))
return true;
if (autoFixHelper<MsgSymbolOriginNotInCenter>(msg, checkOnly)) return true;
return false;
}
template <typename MessageType>
bool SymbolTab::autoFixHelper(
const std::shared_ptr<const RuleCheckMessage>& msg, bool checkOnly) {
if (msg) {
if (auto m = msg->as<MessageType>()) {
if (checkOnly) {
return true;
} else {
return autoFix(*m); // can throw
}
}
}
return false;
}
void SymbolTab::messageApprovalChanged(const SExpression& approval,
bool approved) noexcept {
if (mSymbol->setMessageApproved(approval, approved)) {
if (!mManualModificationsMade) {
mManualModificationsMade = true;
onUiDataChanged.notify();
}
}
}
void SymbolTab::notifyDerivedUiDataChanged() noexcept {
onDerivedUiDataChanged.notify();
}
/*******************************************************************************
* Rule check autofixes
******************************************************************************/
template <>
bool SymbolTab::autoFix(const MsgNameNotTitleCase& msg) {
mNameParsed = msg.getFixedName();
commitUiData();
return true;
}
template <>
bool SymbolTab::autoFix(const MsgMissingAuthor& msg) {
Q_UNUSED(msg);
mAuthor = q2s(getWorkspaceSettingsUserName());
commitUiData();
return true;
}
template <>
bool SymbolTab::autoFix(const MsgMissingCategories& msg) {
Q_UNUSED(msg);
mCurrentPageIndex = 0;
mChooseCategory = true;
onDerivedUiDataChanged.notify();
return true;
}
template <>
bool SymbolTab::autoFix(const MsgMissingSymbolName& msg) {
Q_UNUSED(msg);
mFsm->processStartAddingNames();
return true;
}
template <>
bool SymbolTab::autoFix(const MsgMissingSymbolValue& msg) {
Q_UNUSED(msg);
mFsm->processStartAddingValues();
return true;
}
template <>
bool SymbolTab::autoFix(const MsgWrongSymbolTextLayer& msg) {
std::shared_ptr<Text> text = mSymbol->getTexts().get(msg.getText().get());
std::unique_ptr<CmdTextEdit> cmd(new CmdTextEdit(*text));
cmd->setLayer(msg.getExpectedLayer(), false);
mUndoStack->execCmd(cmd.release());
return true;
}
template <>
bool SymbolTab::autoFix(const MsgSymbolPinNotOnGrid& msg) {
std::shared_ptr<SymbolPin> pin = mSymbol->getPins().get(msg.getPin().get());
Point newPos = pin->getPosition().mappedToGrid(msg.getGridInterval());
std::unique_ptr<CmdSymbolPinEdit> cmd(new CmdSymbolPinEdit(pin));
cmd->setPosition(newPos, false);
mUndoStack->execCmd(cmd.release());
return true;
}
template <>
bool SymbolTab::autoFix(const MsgNonFunctionalSymbolPinInversionSign& msg) {
std::shared_ptr<SymbolPin> pin = mSymbol->getPins().get(msg.getPin().get());
std::unique_ptr<CmdSymbolPinEdit> cmd(new CmdSymbolPinEdit(pin));
cmd->setName(CircuitIdentifier("!" % pin->getName()->mid(1)), false);
mUndoStack->execCmd(cmd.release());
return true;
}
template <>
bool SymbolTab::autoFix(const MsgSymbolOriginNotInCenter& msg) {
mFsm->processAbortCommand();
mFsm->processAbortCommand();
mFsm->processSelectAll();
mFsm->processMove(-msg.getCenter().mappedToGrid(mSymbol->getGridInterval()));
mFsm->processAbortCommand(); // Clear selection.
return true;
}
/*******************************************************************************
* Private Methods
******************************************************************************/
void SymbolTab::refreshUiData() noexcept {
mName = q2s(*mSymbol->getNames().getDefaultValue());
mNameError = slint::SharedString();
mNameParsed = mSymbol->getNames().getDefaultValue();
mDescription = q2s(mSymbol->getDescriptions().getDefaultValue());
mKeywords = q2s(mSymbol->getKeywords().getDefaultValue());
mAuthor = q2s(mSymbol->getAuthor());
mVersion = q2s(mSymbol->getVersion().toStr());
mVersionError = slint::SharedString();
mVersionParsed = mSymbol->getVersion();
mDeprecated = mSymbol->isDeprecated();
mCategories->setCategories(mSymbol->getCategories());
updateAgeDays(mSymbol->getCreated());
mMsgImportPins.setActive(mSymbol->isEmpty());
// Update "interface broken" only when no command is active since it would
// be annoying to get it during intermediate states.
if (!mUndoStack->isCommandGroupActive()) {
mIsInterfaceBroken = (!mIsNewElement) && (!mWizardMode) &&
(mSymbol->getPins().getUuidSet() != mOriginalSymbolPinUuids);
}
onUiDataChanged.notify();
onDerivedUiDataChanged.notify();
}
void SymbolTab::commitUiData() noexcept {
// Abort any active command as this would block the undo stack.
mFsm->processAbortCommand();
mFsm->processAbortCommand();
mFsm->processAbortCommand();
try {
std::unique_ptr<CmdLibraryElementEdit> cmd(
new CmdLibraryElementEdit(*mSymbol, tr("Edit Symbol Properties")));
cmd->setName(QString(), mNameParsed);
const QString description = s2q(mDescription);
if (description != mSymbol->getDescriptions().getDefaultValue()) {
cmd->setDescription(QString(), description.trimmed());
}
const QString keywords = s2q(mKeywords);
if (keywords != mSymbol->getKeywords().getDefaultValue()) {
cmd->setKeywords(QString(), EditorToolbox::cleanKeywords(keywords));
}
const QString author = s2q(mAuthor);
if (author != mSymbol->getAuthor()) {
cmd->setAuthor(author.trimmed());
}
cmd->setVersion(mVersionParsed);
cmd->setDeprecated(mDeprecated);
cmd->setCategories(mCategories->getCategories());
mUndoStack->execCmd(cmd.release());
} catch (const Exception& e) {
QMessageBox::critical(getWindow(), tr("Error"), e.getMsg());
}
}
bool SymbolTab::save() noexcept {
try {
// Remove obsolete message approvals (bypassing the undo stack). Since
// the checks are run asynchronously, the approvals may be outdated, so
// we first run the checks once synchronosuly.
runChecks();
mSymbol->setMessageApprovals(mSymbol->getMessageApprovals() -
mDisappearedApprovals);
mSymbol->save();
if (isPathOutsideLibDir()) {
const QString dirName =
mEditor.getLibrary().getElementsDirectoryName<Symbol>();
const FilePath fp =
mEditor.getLibrary().getDirectory().getAbsPath(dirName).getPathTo(
mSymbol->getUuid().toStr());
TransactionalDirectory dir(TransactionalFileSystem::open(
fp, mEditor.isWritable(),
&TransactionalFileSystem::RestoreMode::abort));
mSymbol->saveTo(dir);
}
mSymbol->getDirectory().getFileSystem()->save();
mUndoStack->setClean();
mManualModificationsMade = false;
memorizeInterface();
updateWatchedFiles();
mEditor.getWorkspace().getLibraryDb().startLibraryRescan();
if (mWizardMode && (mCurrentPageIndex == 0)) {
++mCurrentPageIndex;
mWizardMode = false;
scheduleChecks();
}
refreshUiData();
return true;
} catch (const Exception& e) {
QMessageBox::critical(getWindow(), tr("Error"), e.getMsg());
refreshUiData();
return false;
}
}
void SymbolTab::memorizeInterface() noexcept {
mOriginalSymbolPinUuids = mSymbol->getPins().getUuidSet();
}
void SymbolTab::updateWatchedFiles() noexcept {
QSet<QString> files = {"symbol.lp"};
for (const Image& image : mSymbol->getImages()) {
files.insert(*image.getFileName());
}
setWatchedFiles(mSymbol->getDirectory(), files);
}
void SymbolTab::setGridInterval(const PositiveLength& interval) noexcept {
if (interval != mSymbol->getGridInterval()) {
mSymbol->setGridInterval(interval);
mFsm->processGridIntervalChanged(mSymbol->getGridInterval());
if (mScene) {
mScene->setGridInterval(mSymbol->getGridInterval());
requestRepaint();
}
mManualModificationsMade = true;
onUiDataChanged.notify();
}
}
bool SymbolTab::execGraphicsExportDialog(GraphicsExportDialog::Output output,
const QString& settingsKey) noexcept {
try {
// Determine default file path.
QString symbolName =
FilePath::cleanFileName(*mSymbol->getNames().getDefaultValue(),
FilePath::ReplaceSpaces | FilePath::KeepCase);
FilePath defaultFilePath(QDir::homePath() % "/" % symbolName % "_Symbol");
// Copy symbol items to allow processing them in worker threads.
QList<std::shared_ptr<GraphicsPagePainter>> pages = {
std::make_shared<SymbolPainter>(*mSymbol, nullptr),
};
// Show dialog, which will do all the work.
GraphicsExportDialog dialog(
GraphicsExportDialog::Mode::Schematic, output, pages, 0,
*mSymbol->getNames().getDefaultValue(), 0, defaultFilePath, mUnit,
mApp.getWorkspace().getSettings().schematicColorSchemes.getActive(),
"symbol_editor/" % settingsKey, getWindow());
connect(&dialog, &GraphicsExportDialog::requestOpenFile, this,
[this](const FilePath& fp) {
DesktopServices ds(mApp.getWorkspace().getSettings());
ds.openLocalPath(fp);
});
dialog.exec();
} catch (const Exception& e) {
QMessageBox::warning(getWindow(), tr("Error"), e.getMsg());
}
return true;
}
void SymbolTab::requestRepaint() noexcept {
++mFrameIndex;
onDerivedUiDataChanged.notify();
}
void SymbolTab::applyWorkspaceSettings() noexcept {
const ColorScheme& scheme =
mApp.getWorkspace().getSettings().schematicColorSchemes.getActive();
if (mScene) {
const auto background = scheme.getColors(ColorRole::schematicBackground());
mScene->setBackgroundColors(background.primary, background.secondary);
const auto overlay = scheme.getColors(ColorRole::schematicOverlays());
mScene->setOverlayColors(overlay.primary, overlay.secondary);
const auto selection = scheme.getColors(ColorRole::schematicSelection());
mScene->setSelectionRectColors(selection.primary, selection.secondary);
mScene->setGridStyle(mGridStyle);
}
onDerivedUiDataChanged.notify();
}
/*******************************************************************************
* End of File
******************************************************************************/
} // namespace editor
} // namespace librepcb