Loading files
0f57addc1This 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_netlabel.h"
#include "graphicsitems/sgi_netline.h"
#include "graphicsitems/sgi_symbol.h"
#include "graphicsitems/sgi_symbolpin.h"
#include "schematiceditor.h"
#include "schematicgraphicsscene.h"
#include <librepcb/core/attribute/attributetype.h>
#include <librepcb/core/attribute/attributeunit.h>
#include <librepcb/core/project/circuit/circuit.h>
#include <librepcb/core/project/circuit/componentinstance.h>
#include <librepcb/core/project/circuit/netsignal.h>
#include <librepcb/core/project/erc/electricalrulecheckmessages.h>
#include <librepcb/core/project/project.h>
#include <librepcb/core/project/schematic/items/si_netlabel.h>
#include <librepcb/core/project/schematic/items/si_netline.h>
#include <librepcb/core/project/schematic/items/si_netsegment.h>
#include <librepcb/core/project/schematic/items/si_symbol.h>
#include <librepcb/core/project/schematic/items/si_symbolpin.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>
#include <memory>
/*******************************************************************************
* Namespace
******************************************************************************/
namespace librepcb {
class Library;
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::goToObjects);
// 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.getModel(), // 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 = std::make_unique<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);
}
}
}
bool SchematicTab::startAddingRemainingComponentGates(
const Uuid& cmp, const std::optional<Uuid>& gate) noexcept {
return mFsm->processAddRemainingGates(cmp, gate);
}
void SchematicTab::activate() noexcept {
mScene = std::make_unique<SchematicGraphicsScene>(
mSchematic, *mLayers,
std::make_shared<SchematicGraphicsScene::Context>(
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: {
QVector<SearchContext::Candidate> objects;
for (const SI_Symbol* sym : mSchematic.getSymbols()) {
if (!sym->getComponentInstance().isPureSchematicOnly()) {
objects.append(
{SearchContext::ObjectType::Component, sym->getName()});
}
}
for (const NetSignal* net : mProject.getCircuit().getNetSignals()) {
if (!net->isAnonymous()) {
objects.append({SearchContext::ObjectType::Net, *net->getName()});
}
}
mSearchContext.setCandidates(objects);
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();
}
void SchematicTab::processScenePointerEvent(const QPointF& pos,
slint::private_api::PointerEvent e,
int scene) noexcept {
Q_UNUSED(scene);
mProjectEditor.setCurrentTab(this);
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 {
emit 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 : std::as_const(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 : 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, &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 : 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, &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::goToObjects(
const QVector<SearchContext::Candidate>& objects) noexcept {
if (!mScene) return;
// Cross-probe found objects.
QSet<const NetSignal*> nets;
QSet<const ComponentInstance*> components;
for (const auto& obj : objects) {
if (obj.type == SearchContext::ObjectType::Net) {
if (auto net = mProject.getCircuit().getNetSignalByName(obj.name)) {
nets.insert(net);
}
} else if (obj.type == SearchContext::ObjectType::Component) {
if (auto cmp =
mProject.getCircuit().getComponentInstanceByName(obj.name)) {
components.insert(cmp);
}
}
}
mProjectEditor.getCrossProbe()->set(nullptr, nets, components, {}, {});
// Select objects & zoom to them.
mScene->clearSelection();
QRectF bbox;
auto setSelected = [&bbox](QGraphicsItem* item) {
item->setSelected(true);
bbox |= item->mapRectToScene(item->boundingRect() |
item->childrenBoundingRect());
};
if (!components.isEmpty()) {
for (const auto& item : mScene->getSymbols()) {
if (components.contains(&item->getSymbol().getComponentInstance())) {
setSelected(item.get());
}
}
}
if (!nets.isEmpty()) {
for (const auto& item : mScene->getSymbolPins()) {
const NetSignal* net = item->getPin().getCompSigInstNetSignal();
if (net && nets.contains(net)) {
setSelected(item.get());
}
}
for (const auto& item : mScene->getNetLines()) {
const NetSignal& net = item->getNetLine().getNetSegment().getNetSignal();
if (nets.contains(&net)) {
setSelected(item.get());
}
}
for (const auto& item : mScene->getNetLabels()) {
const NetSignal& net = item->getNetLabel().getNetSegment().getNetSignal();
if (nets.contains(&net)) {
setSelected(item.get());
}
}
}
if (!bbox.isEmpty()) {
// 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(bbox.size().width(), bbox.size().height()),
Length::fromMm(10).toPx());
bbox.adjust(-margin, -margin, margin, margin);
mView->zoomToSceneRect(bbox, false);
}
// Enforce updating the info box for the selected objects.
mFsm->processChangedSelection();
}
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