Loading files
This file is pinned to the selected release. Download the original to open it in your design tool.
94ec4b8feThis 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 "schematictab.h"
#include "../../graphics/graphicslayer.h"
#include "../../graphics/graphicslayerlist.h"
#include "../../graphics/slintgraphicsview.h"
#include "../../guiapplication.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/desktopservices.h"
#include "../projectcrossprobe.h"
#include "../projecteditor.h"
#include "fsm/schematiceditorfsm.h"
#include "fsm/schematiceditorstate_addcomponent.h"
#include "fsm/schematiceditorstate_addtext.h"
#include "fsm/schematiceditorstate_drawbus.h"
#include "fsm/schematiceditorstate_drawpolygon.h"
#include "graphicsitems/sgi_symbol.h"
#include "schematiceditor.h"
#include "schematicgraphicsscene.h"
#include <librepcb/core/attribute/attributetype.h>
#include <librepcb/core/attribute/attributeunit.h>
#include <librepcb/core/project/erc/electricalrulecheckmessages.h>
#include <librepcb/core/project/project.h>
#include <librepcb/core/project/schematic/items/si_symbol.h>
#include <librepcb/core/project/schematic/schematic.h>
#include <librepcb/core/project/schematic/schematicpainter.h>
#include <librepcb/core/utils/toolbox.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>
/*******************************************************************************
* Namespace
******************************************************************************/
namespace librepcb {
namespace editor {
static ui::WireMode l2s(SchematicEditorState_DrawWire::WireMode v) noexcept {
if (v == SchematicEditorState_DrawWire::WireMode::HV) {
return ui::WireMode::HV;
} else if (v == SchematicEditorState_DrawWire::WireMode::VH) {
return ui::WireMode::VH;
} else if (v == SchematicEditorState_DrawWire::WireMode::Deg9045) {
return ui::WireMode::Deg9045;
} else if (v == SchematicEditorState_DrawWire::WireMode::Deg4590) {
return ui::WireMode::Deg4590;
} else if (v == SchematicEditorState_DrawWire::WireMode::Straight) {
return ui::WireMode::Straight;
} else {
return ui::WireMode::HV;
}
}
static SchematicEditorState_DrawWire::WireMode s2l(ui::WireMode v) noexcept {
if (v == ui::WireMode::HV) {
return SchematicEditorState_DrawWire::WireMode::HV;
} else if (v == ui::WireMode::VH) {
return SchematicEditorState_DrawWire::WireMode::VH;
} else if (v == ui::WireMode::Deg9045) {
return SchematicEditorState_DrawWire::WireMode::Deg9045;
} else if (v == ui::WireMode::Deg4590) {
return SchematicEditorState_DrawWire::WireMode::Deg4590;
} else if (v == ui::WireMode::Straight) {
return SchematicEditorState_DrawWire::WireMode::Straight;
} else {
return SchematicEditorState_DrawWire::WireMode::HV;
}
}
/*******************************************************************************
* Constructors / Destructor
******************************************************************************/
SchematicTab::SchematicTab(GuiApplication& app, SchematicEditor& editor,
QObject* parent) noexcept
: WindowTab(app, parent),
onDerivedUiDataChanged(*this),
mProjectEditor(editor.getProjectEditor()),
mProject(mProjectEditor.getProject()),
mSchematicEditor(editor),
mSchematic(mSchematicEditor.getSchematic()),
mLayers(
GraphicsLayerList::schematicLayers(&app.getWorkspace().getSettings())),
mPinNumbersLayer(mLayers->get(ColorRole::schematicPinNumbers())),
mView(new SlintGraphicsView(SlintGraphicsView::defaultSchematicSceneRect(),
SlintGraphicsView::defaultEditorMargins(),
this)),
mMsgInstallLibraries(app.getWorkspace(), "EMPTY_SCHEMATIC_NO_LIBRARIES"),
mMsgAddDrawingFrame(app.getWorkspace(), "EMPTY_SCHEMATIC_ADD_FRAME"),
mGridStyle(mApp.getWorkspace().getSettings().schematicGridStyle.get()),
mIgnorePlacementLocks(false),
mFrameIndex(0),
mToolFeatures(),
mTool(ui::EditorTool::Select),
mToolCursorShape(Qt::ArrowCursor),
mToolWireMode(SchematicEditorState_DrawWire::WireMode::HV),
mToolLayers(std::make_shared<slint::VectorModel<slint::SharedString>>()),
mToolLayer(nullptr),
mToolLineWidth(app.getWorkspace().getSettings()),
mToolSize(app.getWorkspace().getSettings()),
mToolFilled(false),
mToolValueSuggestions(
std::make_shared<slint::VectorModel<slint::SharedString>>()),
mToolAttributeUnits(
std::make_shared<slint::VectorModel<slint::SharedString>>()),
mToolAttributeUnit(nullptr) {
Q_ASSERT(&mSchematic.getProject() == &mProject);
// 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,
&SchematicTab::requestRepaint);
connect(mView.get(), &SlintGraphicsView::stateChanged, this,
[this]() { onDerivedUiDataChanged.notify(); });
// Connect schematic editor.
connect(&mSchematicEditor, &SchematicEditor::uiIndexChanged, this,
[this]() { onDerivedUiDataChanged.notify(); });
connect(&mSchematicEditor, &SchematicEditor::aboutToBeDestroyed, this,
&SchematicTab::closeEnforced);
// Connect project editor.
connect(&mProjectEditor, &ProjectEditor::uiIndexChanged, this,
[this]() { onDerivedUiDataChanged.notify(); });
connect(&mProjectEditor, &ProjectEditor::abortBlockingToolsInOtherEditors,
this, [this](const void* source) {
if (source != this) {
// Not so nice...
mFsm->processAbortCommand();
mFsm->processAbortCommand();
mFsm->processAbortCommand();
}
});
connect(&mProjectEditor, &ProjectEditor::ercMarkersInvalidated, this,
&SchematicTab::clearErcMarker);
// Connect undo stack.
connect(&mProjectEditor.getUndoStack(), &UndoStack::stateModified, this,
[this]() { onUiDataChanged.notify(); });
connect(&mProjectEditor, &ProjectEditor::manualModificationsMade, this,
[this]() { onUiDataChanged.notify(); });
// Connect tool values.
connect(&mToolLineWidth, &LengthEditContext::uiDataChanged, this,
[this]() { onDerivedUiDataChanged.notify(); });
// Connect search context.
connect(&mSearchContext, &SearchContext::goToTriggered, this,
&SchematicTab::goToSymbol);
// Setup messages.
connect(&mApp.getWorkspace().getLibraryDb(),
&WorkspaceLibraryDb::scanLibraryListUpdated, this,
&SchematicTab::updateMessages);
connect(&mSchematic, &Schematic::symbolAdded, this,
&SchematicTab::updateMessages);
connect(&mSchematic, &Schematic::symbolRemoved, this,
&SchematicTab::updateMessages);
connect(&mMsgInstallLibraries, &DismissableMessageContext::visibilityChanged,
this, [this]() { onDerivedUiDataChanged.notify(); });
connect(&mMsgAddDrawingFrame, &DismissableMessageContext::visibilityChanged,
this, [this]() { onDerivedUiDataChanged.notify(); });
updateMessages();
// Build the whole schematic editor finite state machine.
SchematicEditorFsm::Context fsmContext{
mApp.getWorkspace(), mProject, mSchematic,
mProjectEditor.getUndoStack(), *this,
};
mFsm.reset(new SchematicEditorFsm(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,
&SchematicTab::applyWorkspaceSettings);
// Restore client settings.
QSettings cs;
if (mPinNumbersLayer) {
mPinNumbersLayer->setVisible(
cs.value("schematic_editor/show_pin_numbers", true).toBool());
}
}
SchematicTab::~SchematicTab() noexcept {
clearErcMarker(); // Avoid dangling pointers.
deactivate();
mView->setEventHandler(nullptr);
// Delete FSM as it may trigger some other methods during destruction.
mFsm.reset();
}
/*******************************************************************************
* General Methods
******************************************************************************/
int SchematicTab::getProjectIndex() const noexcept {
return mProjectEditor.getUiIndex();
}
int SchematicTab::getProjectObjectIndex() const noexcept {
return mProject.getSchematicIndex(mSchematic);
}
ui::TabData SchematicTab::getUiData() const noexcept {
ui::TabFeatures features = {};
features.save = toFs(mProject.getDirectory().isWritable());
features.undo = toFs(mProjectEditor.getUndoStack().canUndo());
features.redo = toFs(mProjectEditor.getUndoStack().canRedo());
features.grid = toFs(mProject.getDirectory().isWritable());
features.zoom = toFs(true);
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.reset_texts = toFs(mToolFeatures.testFlag(Feature::ResetTexts));
features.edit_properties = toFs(mToolFeatures.testFlag(Feature::Properties));
features.find = toFs(true);
return ui::TabData{
ui::TabType::Schematic, // Type
q2s(mSchematicEditor.getDisplayName()), // Title
features, // Features
!mProject.getDirectory().isWritable(), // Read-only
mProjectEditor.hasUnsavedChanges(), // Unsaved changes
q2s(mProjectEditor.getUndoStack().getUndoCmdText()), // Undo text
q2s(mProjectEditor.getUndoStack().getRedoCmdText()), // Redo text
q2s(mSearchContext.getTerm()), // Find term
mSearchContext.getSuggestions(), // Find suggestions
nullptr, // Layers
};
}
void SchematicTab::setUiData(const ui::TabData& data) noexcept {
mSearchContext.setTerm(s2q(data.find_term));
WindowTab::setUiData(data);
onUiDataChanged.notify();
}
ui::SchematicTabData SchematicTab::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::SchematicTabData{
mProjectEditor.getUiIndex(), // Project index
mSchematicEditor.getUiIndex(), // Schematic index
q2s(bgColor), // Background color
q2s(fgColor), // Foreground color
q2s(infoBoxColors.primary), // Overlay color
q2s(infoBoxColors.secondary), // Overlay text color
l2s(mGridStyle), // Grid style
l2s(*mSchematic.getGridInterval()), // Grid interval
l2s(mSchematic.getGridUnit()), // Length unit
mPinNumbersLayer && mPinNumbersLayer->isVisible(), // Show pin numbers
mIgnorePlacementLocks, // Ignore placement locks
mMsgInstallLibraries.getUiData(), // Message "install libraries"
mMsgAddDrawingFrame.getUiData(), // Message "add schematic frame"
mTool, // Tool
q2s(mView->isPanning() ? Qt::ClosedHandCursor
: mToolCursorShape), // Tool cursor
q2s(mToolOverlayText), // Tool overlay text
l2s(mToolWireMode), // Tool wire mode
ui::ComboBoxData{
// Tool layer
mToolLayers, // Items
static_cast<int>(mToolLayersQt.indexOf(mToolLayer)), // Current index
},
mToolLineWidth.getUiData(), // Tool line width
mToolSize.getUiData(), // // Tool size
mToolFilled, // Tool filled
ui::LineEditData{
// Tool value
true, // Enabled
q2s(EditorToolbox::toSingleLine(mToolValue)), // Text
slint::SharedString(), // Placeholder
mToolValueSuggestions, // Suggestions
},
ui::LineEditData{
// Tool attribute value
mToolAttributeValue.has_value(), // Enabled
mToolAttributeValue
? q2s(EditorToolbox::toSingleLine(*mToolAttributeValue))
: slint::SharedString(), // Text
q2s(mToolAttributeValuePlaceholder), // Placeholder
mToolValueSuggestions, // Suggestions
},
ui::ComboBoxData{
// Tool attribute unit
mToolAttributeUnits, // Items
static_cast<int>(mToolAttributeUnitsQt.indexOf(
mToolAttributeUnit)), // Current index
},
q2s(mSceneImagePos), // Scene image position
mFrameIndex, // Frame index
slint::SharedString(), // New bus UUID
};
}
void SchematicTab::setDerivedUiData(const ui::SchematicTabData& data) noexcept {
mSceneImagePos = s2q(data.scene_image_pos);
mGridStyle = s2l(data.grid_style);
const std::optional<PositiveLength> interval = s2plength(data.grid_interval);
if (interval && (*interval != mSchematic.getGridInterval())) {
mSchematic.setGridInterval(*interval);
mProjectEditor.setManualModificationsMade();
}
if (mScene) {
mScene->setGridStyle(mGridStyle);
mScene->setGridInterval(mSchematic.getGridInterval());
}
const LengthUnit unit = s2l(data.unit);
if (unit != mSchematic.getGridUnit()) {
mSchematic.setGridUnit(unit);
mProjectEditor.setManualModificationsMade();
}
if (mPinNumbersLayer &&
(mPinNumbersLayer->isVisible() != data.show_pin_numbers)) {
mPinNumbersLayer->setVisible(data.show_pin_numbers);
QSettings cs;
cs.setValue("schematic_editor/show_pin_numbers", data.show_pin_numbers);
}
// Placement locks
mIgnorePlacementLocks = data.ignore_placement_locks;
// Messages
mMsgInstallLibraries.setUiData(data.install_libraries_msg);
mMsgAddDrawingFrame.setUiData(data.add_drawing_frame_msg);
if (const Layer* layer = mToolLayersQt.value(data.tool_layer.current_index)) {
emit layerRequested(*layer);
}
emit wireModeRequested(s2l(data.tool_wire_mode));
emit filledRequested(data.tool_filled);
mToolLineWidth.setUiData(data.tool_line_width);
mToolSize.setUiData(data.tool_size);
emit valueRequested(EditorToolbox::toMultiLine(s2q(data.tool_value.text)));
// Unit must be set before value, because value may override the unit!
emit attributeUnitRequested(
mToolAttributeUnitsQt.value(data.tool_attribute_unit.current_index));
emit attributeValueRequested(
EditorToolbox::toMultiLine(s2q(data.tool_attribute_value.text)));
// Actions
if (!data.new_bus_uuid.empty()) {
if (auto uuid = Uuid::tryFromString(s2q(data.new_bus_uuid))) {
mFsm->processDrawBus(*uuid);
}
}
requestRepaint();
}
void SchematicTab::highlightErcMessage(
const std::shared_ptr<const ErcMsgBase>& msg, bool zoomTo) noexcept {
if ((!msg) || msg->getLocations().isEmpty()) {
// Position on schematic not known.
clearErcMarker();
} else if (mScene) {
const auto colors = mApp.getWorkspace()
.getSettings()
.schematicColorSchemes.getActive()
.getColors(ColorRole::schematicOverlays());
QPainterPath path = Path::toQPainterPathPx(msg->getLocations(), true);
mErcLocationGraphicsItem.reset(new QGraphicsPathItem());
mErcLocationGraphicsItem->setZValue(
SchematicGraphicsScene::ZValue_ErcLocation);
mErcLocationGraphicsItem->setPen(QPen(colors.primary, 0));
mErcLocationGraphicsItem->setBrush(colors.secondary);
mErcLocationGraphicsItem->setPath(path);
mScene->addItem(*mErcLocationGraphicsItem.get());
const QRectF rect = path.boundingRect();
const qreal boxMargin = Length(1000000).toPx();
mScene->setSceneRectMarker(
rect.adjusted(-boxMargin, -boxMargin, boxMargin, boxMargin));
if (zoomTo) {
const qreal zoomMargin = Length(20000000).toPx();
mView->zoomToSceneRect(
rect.adjusted(-zoomMargin, -zoomMargin, zoomMargin, zoomMargin),
false);
}
}
}
void SchematicTab::activate() noexcept {
mScene.reset(new SchematicGraphicsScene(
mSchematic, *mLayers,
std::shared_ptr<SchematicGraphicsScene::Context>(
new SchematicGraphicsScene::Context{
this, // tab
mProjectEditor.getCrossProbe(), // cross probe
GraphicsLayer::State::Highlighted, // Self-probe mode
mIgnorePlacementLocks, // ignore placement locks
}),
this));
mScene->setGridInterval(mSchematic.getGridInterval());
connect(mScene.get(), &GraphicsScene::changed, this,
&SchematicTab::requestRepaint);
// Initialize search context.
mSearchContext.init();
applyWorkspaceSettings();
mProjectEditor.registerActiveSchematicTab(this);
requestRepaint();
}
void SchematicTab::deactivate() noexcept {
mProjectEditor.unregisterActiveSchematicTab(this);
mSearchContext.deinit();
mErcLocationGraphicsItem.reset();
mScene.reset();
}
void SchematicTab::trigger(ui::TabAction a) noexcept {
mProjectEditor.setCurrentTab(this);
switch (a) {
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::SelectAll: {
mFsm->processSelectAll();
break;
}
case ui::TabAction::Abort: {
if (mErcLocationGraphicsItem) {
clearErcMarker();
} else {
mFsm->processAbortCommand();
}
break;
}
case ui::TabAction::Save: {
mProjectEditor.saveProject();
break;
}
case ui::TabAction::Undo: {
mProjectEditor.undo();
break;
}
case ui::TabAction::Redo: {
mProjectEditor.redo();
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->processAddImage(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(-mSchematic.getGridInterval(), 0))) {
mView->scrollLeft();
}
break;
}
case ui::TabAction::MoveRight: {
if (!mFsm->processMove(Point(*mSchematic.getGridInterval(), 0))) {
mView->scrollRight();
}
break;
}
case ui::TabAction::MoveUp: {
if (!mFsm->processMove(Point(0, *mSchematic.getGridInterval()))) {
mView->scrollUp();
}
break;
}
case ui::TabAction::MoveDown: {
if (!mFsm->processMove(Point(0, -mSchematic.getGridInterval()))) {
mView->scrollDown();
}
break;
}
case ui::TabAction::SnapToGrid: {
mFsm->processSnapToGrid();
break;
}
case ui::TabAction::ResetTexts: {
mFsm->processResetAllTexts();
break;
}
case ui::TabAction::EditProperties: {
mFsm->processEditProperties();
break;
}
case ui::TabAction::GridIntervalIncrease: {
mSchematic.setGridInterval(
PositiveLength(mSchematic.getGridInterval() * 2));
if (mScene) {
mScene->setGridInterval(mSchematic.getGridInterval());
requestRepaint();
}
break;
}
case ui::TabAction::GridIntervalDecrease: {
if ((*mSchematic.getGridInterval() % 2) == 0) {
mSchematic.setGridInterval(
PositiveLength(mSchematic.getGridInterval() / 2));
if (mScene) {
mScene->setGridInterval(mSchematic.getGridInterval());
requestRepaint();
}
}
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::FindRefreshSuggestions: {
QStringList names;
for (const SI_Symbol* sym : mSchematic.getSymbols()) {
names.append(sym->getName());
}
Toolbox::sortNumeric(names);
mSearchContext.setSuggestions(names);
break;
}
case ui::TabAction::FindNext: {
mSearchContext.findNext();
break;
}
case ui::TabAction::FindPrevious: {
mSearchContext.findPrevious();
break;
}
case ui::TabAction::ToolSelect: {
mFsm->processSelect();
break;
}
case ui::TabAction::ToolWire: {
mFsm->processDrawWire();
break;
}
case ui::TabAction::ToolBus: {
mFsm->processDrawBus();
break;
}
case ui::TabAction::ToolLabel: {
mFsm->processAddNetLabel();
break;
}
case ui::TabAction::ToolPolygon: {
mFsm->processDrawPolygon();
break;
}
case ui::TabAction::ToolText: {
mFsm->processAddText();
break;
}
case ui::TabAction::ToolImage: {
mFsm->processAddImage();
break;
}
case ui::TabAction::ToolComponent: {
mFsm->processAddComponent();
break;
}
case ui::TabAction::ToolComponentFrame: {
mFsm->processAddComponent("schematic frame");
break;
}
case ui::TabAction::ToolComponentResistor: {
mFsm->processAddComponent(
Uuid::fromString("ef80cd5e-2689-47ee-8888-31d04fc99174"),
Uuid::fromString(mProjectEditor.getUseIeee315Symbols()
? "d16e1f44-16af-4773-a310-de370f744548"
: "a5995314-f535-45d4-8bd8-2d0b8a0dc42a"));
break;
}
case ui::TabAction::ToolComponentInductor: {
mFsm->processAddComponent(
Uuid::fromString("506bd124-6062-400e-9078-b38bd7e1aaee"),
Uuid::fromString(mProjectEditor.getUseIeee315Symbols()
? "4245d515-6f6d-48cb-9958-a4ea23d0187f"
: "62a7598c-17fe-41cf-8fa1-4ed274c3adc2"));
break;
}
case ui::TabAction::ToolComponentCapacitorBipolar: {
mFsm->processAddComponent(
Uuid::fromString("d167e0e3-6a92-4b76-b013-77b9c230e5f1"),
Uuid::fromString(mProjectEditor.getUseIeee315Symbols()
? "6e639ff1-4e81-423b-9d0e-b28b35693a61"
: "8cd7b37f-e5fa-4af5-a8dd-d78830bba3af"));
break;
}
case ui::TabAction::ToolComponentCapacitorUnipolar: {
mFsm->processAddComponent(
Uuid::fromString("c54375c5-7149-4ded-95c5-7462f7301ee7"),
Uuid::fromString(mProjectEditor.getUseIeee315Symbols()
? "20a01a81-506e-4fee-9dc0-8b50e6537cd4"
: "5412add2-af9c-44b8-876d-a0fb7c201897"));
break;
}
case ui::TabAction::ToolComponentGnd: {
mFsm->processAddComponent(
Uuid::fromString("8076f6be-bfab-4fc1-9772-5d54465dd7e1"),
Uuid::fromString("f09ad258-595b-4ee9-a1fc-910804a203ae"));
break;
}
case ui::TabAction::ToolComponentVcc: {
mFsm->processAddComponent(
Uuid::fromString("58c3c6cd-11eb-4557-aa3f-d3e05874afde"),
Uuid::fromString("afb86b45-68ec-47b6-8d96-153d73567228"));
break;
}
case ui::TabAction::ToolMeasure: {
mFsm->processMeasure();
break;
}
default: {
WindowTab::trigger(a);
break;
}
}
}
slint::Image SchematicTab::renderScene(float width, float height,
int scene) noexcept {
Q_UNUSED(scene);
if (mScene) {
return mView->render(*mScene, width, height);
}
return slint::Image();
}
bool SchematicTab::processScenePointerEvent(const QPointF& pos,
slint::private_api::PointerEvent e,
int scene) noexcept {
Q_UNUSED(scene);
mProjectEditor.setCurrentTab(this);
return mView->pointerEvent(pos, e);
}
bool SchematicTab::processSceneScrolled(
const QPointF& pos, slint::private_api::PointerScrollEvent e,
int scene) noexcept {
Q_UNUSED(scene);
mProjectEditor.setCurrentTab(this);
return mView->scrollEvent(pos, e);
}
bool SchematicTab::processSceneKeyPressed(
const slint::language::KeyEvent& e) noexcept {
mProjectEditor.setCurrentTab(this);
return mView->keyPressed(e);
}
bool SchematicTab::processSceneKeyReleased(
const slint::language::KeyEvent& e) noexcept {
mProjectEditor.setCurrentTab(this);
return mView->keyReleased(e);
}
/*******************************************************************************
* IF_GraphicsViewEventHandler Methods
******************************************************************************/
bool SchematicTab::graphicsSceneKeyPressed(
const GraphicsSceneKeyEvent& e) noexcept {
return mFsm->processKeyPressed(e);
}
bool SchematicTab::graphicsSceneKeyReleased(
const GraphicsSceneKeyEvent& e) noexcept {
return mFsm->processKeyReleased(e);
}
bool SchematicTab::graphicsSceneMouseMoved(
const GraphicsSceneMouseEvent& e) noexcept {
emit cursorCoordinatesChanged(e.scenePos, mSchematic.getGridUnit());
return mFsm->processGraphicsSceneMouseMoved(e);
}
bool SchematicTab::graphicsSceneLeftMouseButtonPressed(
const GraphicsSceneMouseEvent& e) noexcept {
return mFsm->processGraphicsSceneLeftMouseButtonPressed(e);
}
bool SchematicTab::graphicsSceneLeftMouseButtonReleased(
const GraphicsSceneMouseEvent& e) noexcept {
return mFsm->processGraphicsSceneLeftMouseButtonReleased(e);
}
bool SchematicTab::graphicsSceneLeftMouseButtonDoubleClicked(
const GraphicsSceneMouseEvent& e) noexcept {
return mFsm->processGraphicsSceneLeftMouseButtonDoubleClicked(e);
}
bool SchematicTab::graphicsSceneRightMouseButtonReleased(
const GraphicsSceneMouseEvent& e) noexcept {
return mFsm->processGraphicsSceneRightMouseButtonReleased(e);
}
/*******************************************************************************
* SchematicEditorFsmAdapter Methods
******************************************************************************/
QWidget* SchematicTab::fsmGetParentWidget() noexcept {
return getWindow();
}
SchematicGraphicsScene* SchematicTab::fsmGetGraphicsScene() noexcept {
return mScene.get();
}
bool SchematicTab::fsmGetIgnoreLocks() const noexcept {
return mIgnorePlacementLocks;
}
void SchematicTab::fsmSetViewCursor(
const std::optional<Qt::CursorShape>& shape) noexcept {
if (shape) {
mToolCursorShape = *shape;
} else {
mToolCursorShape = Qt::ArrowCursor;
}
onDerivedUiDataChanged.notify();
}
void SchematicTab::fsmSetViewGrayOut(bool grayOut) noexcept {
if (mScene) {
mScene->setGrayOut(grayOut);
}
}
void SchematicTab::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 SchematicTab::fsmSetViewRuler(
const std::optional<std::pair<Point, Point>>& pos) noexcept {
if (mScene) {
mScene->setRulerPositions(pos);
}
}
void SchematicTab::fsmSetSceneCursor(const Point& pos, bool cross,
bool circle) noexcept {
if (mScene) {
mScene->setSceneCursor(pos, cross, circle);
}
}
QPainterPath SchematicTab::fsmCalcPosWithTolerance(
const Point& pos, qreal multiplier) const noexcept {
return mView->calcPosWithTolerance(pos, multiplier);
}
Point SchematicTab::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 SchematicTab::fsmZoomToSceneRect(const QRectF& r,
bool autoFitInView) noexcept {
mView->zoomToSceneRect(r, autoFitInView);
}
void SchematicTab::fsmCrossProbe(
const QSet<const NetSignal*>& nets,
const QSet<const ComponentInstance*>& components,
const QSet<const ComponentSignalInstance*>& cmpSignals,
const QSet<const Bus*>& buses,
GraphicsLayer::State selfProbedState) noexcept {
if (mScene && mProjectEditor.isCurrentTab(this)) {
mScene->setSelfProbedState(selfProbedState);
mProjectEditor.getCrossProbe()->set(this, nets, components, cmpSignals,
buses);
}
}
void SchematicTab::fsmAbortBlockingToolsInOtherEditors() noexcept {
mProjectEditor.abortBlockingToolsInOtherEditors(this);
}
void SchematicTab::fsmSetStatusBarMessage(const QString& message,
int timeoutMs) noexcept {
emit statusBarMessageChanged(message, timeoutMs);
}
void SchematicTab::fsmSetFeatures(Features features) noexcept {
if (features != mToolFeatures) {
mToolFeatures = features;
onUiDataChanged.notify();
}
}
void SchematicTab::fsmToolLeave() noexcept {
while (!mFsmStateConnections.isEmpty()) {
disconnect(mFsmStateConnections.takeLast());
}
mTool = ui::EditorTool::Select;
fsmSetFeatures(Features());
fsmCrossProbe(); // Reset cross-probing
onDerivedUiDataChanged.notify();
}
void SchematicTab::fsmToolEnter(SchematicEditorState_Select& state) noexcept {
Q_UNUSED(state);
mTool = ui::EditorTool::Select;
onDerivedUiDataChanged.notify();
}
void SchematicTab::fsmToolEnter(SchematicEditorState_DrawWire& state) noexcept {
mTool = ui::EditorTool::Wire;
// Wire mode
auto setWireMode = [this](SchematicEditorState_DrawWire::WireMode m) {
mToolWireMode = m;
onDerivedUiDataChanged.notify();
};
setWireMode(state.getWireMode());
mFsmStateConnections.append(
connect(&state, &SchematicEditorState_DrawWire::wireModeChanged, this,
setWireMode));
mFsmStateConnections.append(
connect(this, &SchematicTab::wireModeRequested, &state,
&SchematicEditorState_DrawWire::setWireMode));
onDerivedUiDataChanged.notify();
}
void SchematicTab::fsmToolEnter(SchematicEditorState_DrawBus& state) noexcept {
mTool = ui::EditorTool::Bus;
// Wire mode
auto setWireMode = [this](SchematicEditorState_DrawWire::WireMode m) {
mToolWireMode = m;
onDerivedUiDataChanged.notify();
};
setWireMode(state.getWireMode());
mFsmStateConnections.append(
connect(&state, &SchematicEditorState_DrawBus::wireModeChanged, this,
setWireMode));
mFsmStateConnections.append(
connect(this, &SchematicTab::wireModeRequested, &state,
&SchematicEditorState_DrawBus::setWireMode));
onDerivedUiDataChanged.notify();
}
void SchematicTab::fsmToolEnter(SchematicEditorState_AddLabel& state) noexcept {
Q_UNUSED(state);
mTool = ui::EditorTool::Label;
onDerivedUiDataChanged.notify();
}
void SchematicTab::fsmToolEnter(
SchematicEditorState_AddComponent& state) noexcept {
mTool = ui::EditorTool::Component;
// Value
auto setValue = [this](const QString& value) {
mToolValue = value;
onDerivedUiDataChanged.notify();
};
setValue(state.getValue());
mFsmStateConnections.append(
connect(&state, &SchematicEditorState_AddComponent::valueChanged, this,
setValue));
mFsmStateConnections.append(
connect(this, &SchematicTab::valueRequested, &state,
&SchematicEditorState_AddComponent::setValue));
// Value suggestions
auto setValueSuggestions = [this](const QStringList& suggestions) {
mToolValueSuggestions->clear();
for (const QString& v : suggestions) {
mToolValueSuggestions->push_back(q2s(v));
}
onDerivedUiDataChanged.notify();
};
setValueSuggestions(state.getValueSuggestions());
mFsmStateConnections.append(connect(
&state, &SchematicEditorState_AddComponent::valueSuggestionsChanged, this,
setValueSuggestions));
// Attribute key
auto setAttributeKey = [this](const std::optional<AttributeKey>& key) {
mToolAttributeValuePlaceholder = key ? **key : QString();
onDerivedUiDataChanged.notify();
};
setAttributeKey(state.getValueAttributeKey());
mFsmStateConnections.append(connect(
&state, &SchematicEditorState_AddComponent::valueAttributeKeyChanged,
this, setAttributeKey));
// Attribute value
auto setAttributeValue = [this](const std::optional<QString>& value) {
mToolAttributeValue = value;
onDerivedUiDataChanged.notify();
};
setAttributeValue(state.getValueAttributeValue());
mFsmStateConnections.append(connect(
&state, &SchematicEditorState_AddComponent::valueAttributeValueChanged,
this, setAttributeValue));
mFsmStateConnections.append(
connect(this, &SchematicTab::attributeValueRequested, &state,
[&state](QString value) {
if (const AttributeType* type = state.getValueAttributeType()) {
if (const AttributeUnit* unit =
type->tryExtractUnitFromValue(value)) {
state.setValueAttributeUnit(unit);
}
state.setValueAttributeValue(value);
}
}));
// Attribute units
auto setAttributeUnits = [this](const AttributeType* type) {
mToolAttributeUnitsQt =
type ? type->getAvailableUnits() : QList<const AttributeUnit*>{};
mToolAttributeUnits->clear();
for (const AttributeUnit* unit : mToolAttributeUnitsQt) {
mToolAttributeUnits->push_back(q2s(unit->getSymbolTr()));
}
onDerivedUiDataChanged.notify();
};
setAttributeUnits(state.getValueAttributeType());
mFsmStateConnections.append(connect(
&state, &SchematicEditorState_AddComponent::valueAttributeTypeChanged,
this, setAttributeUnits));
// Attribute unit
auto setAttributeUnit = [this](const AttributeUnit* unit) {
mToolAttributeUnit = unit;
onDerivedUiDataChanged.notify();
};
setAttributeUnit(state.getValueAttributeUnit());
mFsmStateConnections.append(connect(
&state, &SchematicEditorState_AddComponent::valueAttributeUnitChanged,
this, setAttributeUnit));
mFsmStateConnections.append(
connect(this, &SchematicTab::attributeUnitRequested, &state,
&SchematicEditorState_AddComponent::setValueAttributeUnit));
onDerivedUiDataChanged.notify();
}
void SchematicTab::fsmToolEnter(
SchematicEditorState_DrawPolygon& state) noexcept {
mTool = ui::EditorTool::Polygon;
// Layers
mToolLayersQt = Layer::sorted(state.getAvailableLayers());
mToolLayers->clear();
for (const Layer* layer : 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, &SchematicEditorState_DrawPolygon::layerChanged, this, setLayer));
mFsmStateConnections.append(
connect(this, &SchematicTab::layerRequested, &state,
&SchematicEditorState_DrawPolygon::setLayer));
// Line width
mToolLineWidth.configure(state.getLineWidth(),
LengthEditContext::Steps::generic(),
"schematic_editor/draw_polygon/line_width");
mFsmStateConnections.append(
connect(&state, &SchematicEditorState_DrawPolygon::lineWidthChanged,
&mToolLineWidth, &LengthEditContext::setValueUnsigned));
mFsmStateConnections.append(
connect(&mToolLineWidth, &LengthEditContext::valueChangedUnsigned, &state,
&SchematicEditorState_DrawPolygon::setLineWidth));
// Filled
auto setFilled = [this](bool filled) {
mToolFilled = filled;
onDerivedUiDataChanged.notify();
};
setFilled(state.getFilled());
mFsmStateConnections.append(
connect(&state, &SchematicEditorState_DrawPolygon::filledChanged, this,
setFilled));
mFsmStateConnections.append(
connect(this, &SchematicTab::filledRequested, &state,
&SchematicEditorState_DrawPolygon::setFilled));
onDerivedUiDataChanged.notify();
}
void SchematicTab::fsmToolEnter(SchematicEditorState_AddText& state) noexcept {
mTool = ui::EditorTool::Text;
// Layers
mToolLayersQt = Layer::sorted(state.getAvailableLayers());
mToolLayers->clear();
for (const Layer* layer : 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, &SchematicEditorState_AddText::layerChanged, this, setLayer));
mFsmStateConnections.append(connect(this, &SchematicTab::layerRequested,
&state,
&SchematicEditorState_AddText::setLayer));
// Height
mToolSize.configure(state.getHeight(), LengthEditContext::Steps::textHeight(),
"schematic_editor/add_text/size");
mFsmStateConnections.append(
connect(&state, &SchematicEditorState_AddText::heightChanged, &mToolSize,
&LengthEditContext::setValuePositive));
mFsmStateConnections.append(
connect(&mToolSize, &LengthEditContext::valueChangedPositive, &state,
&SchematicEditorState_AddText::setHeight));
// Text
auto setText = [this](const QString& text) {
mToolValue = text;
onDerivedUiDataChanged.notify();
};
setText(state.getText());
mFsmStateConnections.append(connect(
&state, &SchematicEditorState_AddText::textChanged, this, setText));
mFsmStateConnections.append(connect(this, &SchematicTab::valueRequested,
&state,
&SchematicEditorState_AddText::setText));
// Text suggestions
mToolValueSuggestions->clear();
for (const QString& v : state.getTextSuggestions()) {
mToolValueSuggestions->push_back(q2s(v));
}
onDerivedUiDataChanged.notify();
}
void SchematicTab::fsmToolEnter(SchematicEditorState_AddImage& state) noexcept {
Q_UNUSED(state);
mTool = ui::EditorTool::Image;
onDerivedUiDataChanged.notify();
}
void SchematicTab::fsmToolEnter(SchematicEditorState_Measure& state) noexcept {
Q_UNUSED(state);
mTool = ui::EditorTool::Measure;
onDerivedUiDataChanged.notify();
}
/*******************************************************************************
* Private Methods
******************************************************************************/
void SchematicTab::updateMessages() noexcept {
try {
const bool empty = mSchematic.getSymbols().isEmpty();
const bool noLibs = empty
? mApp.getWorkspace().getLibraryDb().getAll<Library>().isEmpty()
: false;
mMsgInstallLibraries.setActive(empty && noLibs);
mMsgAddDrawingFrame.setActive(empty && (!noLibs));
} catch (const Exception& e) {
}
}
void SchematicTab::clearErcMarker() noexcept {
mErcLocationGraphicsItem.reset();
if (mScene) {
mScene->setSceneRectMarker(QRectF());
}
}
void SchematicTab::execGraphicsExportDialog(
GraphicsExportDialog::Output output, const QString& settingsKey) noexcept {
try {
// Determine default file path.
const QString projectName = FilePath::cleanFileName(
*mProject.getName(), FilePath::ReplaceSpaces | FilePath::KeepCase);
const QString projectVersion = FilePath::cleanFileName(
*mProject.getVersion(), FilePath::ReplaceSpaces | FilePath::KeepCase);
const QString relativePath =
QString("output/%1/%2_Schematics").arg(projectVersion, projectName);
const FilePath defaultFilePath = mProject.getPath().getPathTo(relativePath);
// Copy all schematic pages to allow processing them in worker threads.
const int count = mProject.getSchematics().count();
QProgressDialog progress(tr("Preparing schematics..."), tr("Cancel"), 0,
count, getWindow());
progress.setWindowModality(Qt::WindowModal);
progress.setMinimumDuration(100);
QList<std::shared_ptr<GraphicsPagePainter>> pages;
for (int i = 0; i < count; ++i) {
pages.append(std::make_shared<SchematicPainter>(
*mProject.getSchematicByIndex(i), nullptr));
progress.setValue(i + 1);
if (progress.wasCanceled()) {
return;
}
}
// Show dialog, which will do all the work.
GraphicsExportDialog dialog(
GraphicsExportDialog::Mode::Schematic, output, pages,
mProject.getSchematicIndex(mSchematic), *mProject.getName(), 0,
defaultFilePath,
mApp.getWorkspace().getSettings().defaultLengthUnit.get(),
mApp.getWorkspace().getSettings().schematicColorSchemes.getActive(),
"schematic_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());
}
}
void SchematicTab::goToSymbol(const QString& name, int index) noexcept {
QList<SI_Symbol*> symbolCandidates;
foreach (SI_Symbol* symbol, mSchematic.getSymbols().values()) {
if (symbol->getName().startsWith(name, Qt::CaseInsensitive)) {
symbolCandidates.append(symbol);
}
}
// Sort by name for a natural order of results.
Toolbox::sortNumeric(
symbolCandidates,
[](const QCollator& cmp, const SI_Symbol* lhs, const SI_Symbol* rhs) {
return cmp(lhs->getName(), rhs->getName());
},
Qt::CaseInsensitive, false);
if (symbolCandidates.count()) {
while (index < 0) {
index += symbolCandidates.count();
}
index %= symbolCandidates.count();
SI_Symbol* symbol = symbolCandidates[index];
if (mScene) {
mScene->clearSelection();
if (auto item = mScene->getSymbols().value(symbol)) {
item->setSelected(true);
QRectF rect = item->mapRectToScene(item->childrenBoundingRect());
// Zoom to a rectangle relative to the maximum graphics item dimension,
// occupying 1/4th of the screen, but limiting the margin to 10mm.
const qreal margin =
std::min(1.5f * std::max(rect.size().width(), rect.size().height()),
Length::fromMm(10).toPx());
rect.adjust(-margin, -margin, margin, margin);
mView->zoomToSceneRect(rect, false);
}
}
}
}
void SchematicTab::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();
}
void SchematicTab::requestRepaint() noexcept {
++mFrameIndex;
onDerivedUiDataChanged.notify();
}
/*******************************************************************************
* End of File
******************************************************************************/
} // namespace editor
} // namespace librepcb