Loading files
08cd531bbThis 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 "mainwindow.h"
#include "dialogs/directorylockhandlerdialog.h"
#include "editorcommandsetupdater.h"
#include "guiapplication.h"
#include "library/cat/componentcategorytab.h"
#include "library/cat/packagecategorytab.h"
#include "library/cmp/componenttab.h"
#include "library/createlibrarytab.h"
#include "library/dev/devicetab.h"
#include "library/downloadlibrarytab.h"
#include "library/eaglelibraryimportwizard/eaglelibraryimportwizard.h"
#include "library/kicadlibraryimportwizard/kicadlibraryimportwizard.h"
#include "library/lib/librarytab.h"
#include "library/librariesmodel.h"
#include "library/libraryeditor.h"
#include "library/org/organizationtab.h"
#include "library/pkg/packagetab.h"
#include "library/sym/symboltab.h"
#include "mainwindowtestadapter.h"
#include "notificationsmodel.h"
#include "project/board/board2dtab.h"
#include "project/board/board3dtab.h"
#include "project/board/boardeditor.h"
#include "project/library/projectlibrarytab.h"
#include "project/projecteditor.h"
#include "project/projectreadmerenderer.h"
#include "project/schematic/schematiceditor.h"
#include "project/schematic/schematictab.h"
#include "utils/editortoolbox.h"
#include "utils/slinthelpers.h"
#include "utils/standardeditorcommandhandler.h"
#include "windowsection.h"
#include "windowtab.h"
#include "workspace/desktopservices.h"
#include "workspace/filesystemmodel.h"
#include <librepcb/core/fileio/fileutils.h>
#include <librepcb/core/fileio/transactionaldirectory.h>
#include <librepcb/core/fileio/transactionalfilesystem.h>
#include <librepcb/core/library/cat/componentcategory.h>
#include <librepcb/core/library/cat/packagecategory.h>
#include <librepcb/core/library/cmp/component.h>
#include <librepcb/core/library/dev/device.h>
#include <librepcb/core/library/org/organization.h>
#include <librepcb/core/library/pkg/package.h>
#include <librepcb/core/library/sym/symbol.h>
#include <librepcb/core/project/erc/electricalrulecheckmessages.h>
#include <librepcb/core/project/project.h>
#include <librepcb/core/project/schematic/schematic.h>
#include <librepcb/core/workspace/workspace.h>
#include <librepcb/core/workspace/workspacelibrarydb.h>
#include <librepcb/core/workspace/workspacesettings.h>
#include <QtCore>
#include <memory>
/*******************************************************************************
* Namespace
******************************************************************************/
namespace librepcb {
namespace editor {
// Detect window size enforced by environment variable (required for testing).
static std::optional<QSize> getOverrideWindowSize() noexcept {
static const QString value =
qgetenv("LIBREPCB_WINDOW_SIZE").trimmed().toLower();
if (value.isEmpty()) {
return std::nullopt; // Environment variable not set.
}
const QStringList numbers = value.split("x");
const int width = numbers.value(0).toInt();
const int height = numbers.value(1).toInt();
return QSize(width, height);
}
static bool askForRestoringBackup(const FilePath&) {
QMessageBox::StandardButton btn = QMessageBox::question(
qApp->activeWindow(), MainWindow::tr("Restore autosave backup?"),
MainWindow::tr(
"It seems that the application crashed the last time you opened "
"this library element. Do you want to restore the last autosave "
"backup?"),
QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel,
QMessageBox::Cancel);
switch (btn) {
case QMessageBox::Yes:
return true;
case QMessageBox::No:
return false;
default:
throw UserCanceled(__FILE__, __LINE__);
}
}
static LocalizedNameMap copyLibraryElementNames(const LocalizedNameMap& names) {
// Note: We copy only the default locale for now because the UI doesn't show
// the other locales so the user can't edit them.
QString newNameStr = *names.getDefaultValue() % " (" %
MainWindow::tr("Copy", "The noun (a copy of), not the verb (to copy)") %
")";
if (auto newName = parseElementName(newNameStr)) {
return LocalizedNameMap(*newName);
}
newNameStr = *names.getDefaultValue() % " (Copy)";
if (auto newName = parseElementName(newNameStr)) {
return LocalizedNameMap(*newName);
}
return LocalizedNameMap(names.getDefaultValue());
}
template <typename T>
static void duplicateLibraryElement(T& element, const T& other) {
element.duplicateFrom(other);
element.setCreated(QDateTime::currentDateTime());
element.setVersion(Version::fromString("0.1"));
element.setNames(copyLibraryElementNames(other.getNames()));
}
/*******************************************************************************
* Constructors / Destructor
******************************************************************************/
MainWindow::MainWindow(GuiApplication& app,
slint::ComponentHandle<ui::AppWindow> win, int id,
QObject* parent) noexcept
: QObject(parent),
onUiDataChanged(*this),
mId(id),
mSettingsPrefix(QString("window_%1").arg(mId)),
mApp(app),
mWindow(win),
mWidget(static_cast<QWidget*>(slint::cbindgen_private::slint_qt_get_widget(
&mWindow->window().window_handle()))),
mSections(new UiObjectList<WindowSection, ui::WindowSectionData>()),
mProjectPreviewRenderer(new ProjectReadmeRenderer(this)),
mTestAdapter() {
Q_ASSERT(mWidget);
mWidget->setObjectName("mainWindow");
// Register Slint callbacks.
mWindow->window().on_close_requested(
std::bind(&MainWindow::closeRequested, this));
// Prepare file system model.
auto fileSystemModel = std::make_shared<FileSystemModel>(
mApp.getWorkspace(), mApp.getWorkspace().getProjectsPath(),
mSettingsPrefix % "/workspace_tree", &mApp.getQuickAccess());
connect(fileSystemModel.get(), &FileSystemModel::openFileTriggered, &mApp,
[this](const FilePath& fp) { mApp.openFile(fp, mWidget); });
connect(fileSystemModel.get(), &FileSystemModel::newProjectTriggered, &mApp,
[this](const FilePath& parentDir) {
mApp.createProject(parentDir, false, mWidget);
});
// Set global data.
const ui::Data& d = mWindow->global<ui::Data>();
d.set_panel_page(ui::PanelPage::Home);
d.set_sections(mSections);
d.set_current_section_index(0);
d.set_cursor_coordinates(slint::SharedString());
d.set_workspace_folder_tree(fileSystemModel);
d.set_notifications_unread(
mApp.getNotifications().getUnreadNotificationsCount());
d.set_notifications_progress_index(
mApp.getNotifications().getCurrentProgressIndex());
d.set_notifications_shown(false);
d.set_project_preview_rendering(false);
// Bind global data to signals.
connect(&mApp.getNotifications(),
&NotificationsModel::unreadNotificationsCountChanged, this,
[this](int count) {
mWindow->global<ui::Data>().set_notifications_unread(count);
});
connect(&mApp.getNotifications(),
&NotificationsModel::currentProgressIndexChanged, this,
[this](int index) {
mWindow->global<ui::Data>().set_notifications_progress_index(index);
});
connect(mProjectPreviewRenderer.get(), &ProjectReadmeRenderer::runningChanged,
this, [this](bool running) {
mWindow->global<ui::Data>().set_project_preview_rendering(running);
});
connect(mProjectPreviewRenderer.get(), &ProjectReadmeRenderer::finished, this,
[this](const QPixmap& result) {
mWindow->global<ui::Data>().set_project_preview_image(q2s(result));
});
// Register global callbacks.
const ui::Backend& b = mWindow->global<ui::Backend>();
b.on_trigger([this](ui::Action a) {
// if QT_VERSION >= QT_VERSION_CHECK(6, 7, 0): Remove lambda.
QMetaObject::invokeMethod(
this, [this, a]() { trigger(a); }, Qt::QueuedConnection);
});
b.on_trigger_section([this](int section, ui::WindowSectionAction a) {
// if QT_VERSION >= QT_VERSION_CHECK(6, 7, 0): Remove lambda.
QMetaObject::invokeMethod(
this, [this, section, a]() { triggerSection(section, a); },
Qt::QueuedConnection);
});
b.on_trigger_tab([this](int section, int tab, ui::TabAction a) {
// if QT_VERSION >= QT_VERSION_CHECK(6, 7, 0): Remove lambda.
QMetaObject::invokeMethod(
this, [this, section, tab, a]() { triggerTab(section, tab, a); },
Qt::QueuedConnection);
});
b.on_trigger_library([this](slint::SharedString path, ui::LibraryAction a) {
// if QT_VERSION >= QT_VERSION_CHECK(6, 7, 0): Remove lambda.
QMetaObject::invokeMethod(
this, [this, path, a]() { triggerLibrary(path, a); },
Qt::QueuedConnection);
});
b.on_trigger_library_element(
[this](slint::SharedString path, ui::LibraryElementAction a) {
// if QT_VERSION >= QT_VERSION_CHECK(6, 7, 0): Remove lambda.
QMetaObject::invokeMethod(
this, [this, path, a]() { triggerLibraryElement(path, a); },
Qt::QueuedConnection);
});
b.on_trigger_project([this](int index, ui::ProjectAction a) {
// if QT_VERSION >= QT_VERSION_CHECK(6, 7, 0): Remove lambda.
QMetaObject::invokeMethod(
this, [this, index, a]() { triggerProject(index, a); },
Qt::QueuedConnection);
});
b.on_trigger_schematic(
[this](int project, int schematic, ui::SchematicAction a) {
// if QT_VERSION >= QT_VERSION_CHECK(6, 7, 0): Remove lambda.
QMetaObject::invokeMethod(
this,
[this, project, schematic, a]() {
triggerSchematic(project, schematic, a);
},
Qt::QueuedConnection);
});
b.on_trigger_board([this](int project, int board, ui::BoardAction a) {
// if QT_VERSION >= QT_VERSION_CHECK(6, 7, 0): Remove lambda.
QMetaObject::invokeMethod(
this, [this, project, board, a]() { triggerBoard(project, board, a); },
Qt::QueuedConnection);
});
b.on_render_scene([this](int sectionIndex, float width, float height,
int scene, int frameIndex) {
Q_UNUSED(frameIndex);
if (auto section = mSections->value(sectionIndex)) {
const qreal scale = mWidget->devicePixelRatioF();
return section->renderScene(width * scale, height * scale, scene);
} else {
return slint::Image();
}
});
b.on_scene_pointer_event([this](int sectionIndex, float x, float y,
slint::private_api::PointerEvent e,
int scene) {
if (auto section = mSections->value(sectionIndex)) {
const qreal scale = mWidget->devicePixelRatioF();
const QPointF pos(x * scale, y * scale);
// Process pointer events asynchronosuly to avoid focus issues as reported
// in https://github.com/LibrePCB/LibrePCB/issues/1740. We do it at this
// early stage because we need to process *all* pointer events
// asynchronously, not just click events, since otherwise the events may
// be processed out of order and thus causing other issues.
// if QT_VERSION >= QT_VERSION_CHECK(6, 7, 0): Remove lambda.
QMetaObject::invokeMethod(
section.get(),
[s = section.get(), pos, e, scene]() {
s->processScenePointerEvent(pos, e, scene);
},
Qt::QueuedConnection);
}
});
b.on_scene_scrolled([this](int sectionIndex, float x, float y,
slint::private_api::PointerScrollEvent e,
int scene) {
if (auto section = mSections->value(sectionIndex)) {
const qreal scale = mWidget->devicePixelRatioF();
return section->processSceneScrolled(QPointF(x * scale, y * scale), e,
scene);
}
return false;
});
b.on_scene_key_pressed(
[this](int sectionIndex, const slint::language::KeyEvent& e) {
if (auto section = mSections->value(sectionIndex)) {
return section->processSceneKeyPressed(e);
}
return false;
});
b.on_scene_key_released(
[this](int sectionIndex, const slint::language::KeyEvent& e) {
if (auto section = mSections->value(sectionIndex)) {
return section->processSceneKeyReleased(e);
}
return false;
});
b.on_request_project_preview(
[this](const slint::SharedString& fp, float width, const ui::Theme&) {
// Theme is not used, but needs to be a dependency of this callback
// to trigger re-rendering when the theme colors have changed.
const qreal scale = mWidget->devicePixelRatioF();
mProjectPreviewRenderer->request(FilePath(s2q(fp)),
static_cast<int>(width * scale));
return true;
});
// Update UI state.
d.invoke_current_tab_changed();
// Apply UI theme.
auto applyTheme = [this]() {
mWindow->global<ui::Data>().set_theme(l2s(mApp.getTheme()));
mWindow->window().request_redraw(); // Workaround for spurious wrong colors
};
applyTheme();
connect(&mApp.getWorkspace().getSettings().uiTheme,
&WorkspaceSettingsItem::edited, this, applyTheme,
Qt::QueuedConnection);
// Update editor command translations & keyboard shortcuts.
EditorCommandSetUpdater::update(mWindow->global<ui::EditorCommandSet>());
connect(&mApp.getWorkspace().getSettings().keyboardShortcuts,
&WorkspaceSettingsItem_KeyboardShortcuts::edited, this, [this]() {
EditorCommandSetUpdater::update(
mWindow->global<ui::EditorCommandSet>());
});
// Setup test adapter.
if (qgetenv("LIBREPCB_ENABLE_TEST_ADAPTER") == "1") {
mTestAdapter = std::make_unique<MainWindowTestAdapter>(app, *this, mWidget);
connect(
mTestAdapter.get(), &MainWindowTestAdapter::actionTriggered, this,
[this](ui::Action a) { trigger(a); }, Qt::QueuedConnection);
}
// Show window.
mWindow->show();
// Load window state.
QSettings cs;
const QByteArray geometry =
cs.value(mSettingsPrefix % "/geometry").toByteArray();
if (auto size = getOverrideWindowSize()) {
if (!size->isEmpty()) {
qInfo() << "Window size enforced to" << *size;
mWidget->resize(*size);
} else {
qInfo() << "Window resizing explicitly disabled by environment variable.";
}
} else if (geometry.isEmpty()) {
// By default, open the window maximized as this is more intuitive than a
// small window with hardcoded, screen-independent size in the Slint file
// (https://github.com/LibrePCB/LibrePCB/issues/355). However, do this only
// the very first time (if no geometry is stored yet) to avoid interfering
// with tiling window managers.
qInfo() << "No window geometry to restore, thus maximizing.";
mWidget->setWindowState(Qt::WindowMaximized | Qt::WindowActive);
} else if (!mWidget->restoreGeometry(geometry)) {
// This can happen if a tiling window manager is used, which is fine
// since the user wants to control window geometry then.
qInfo() << "Failed to restore window geometry, e.g. due to window manager.";
}
if (float f = cs.value(mSettingsPrefix % "/panel_width_factor").toFloat();
f > 0) {
d.set_panel_width_factor(f);
}
d.set_erc_zoom_to_location(
cs.value(mSettingsPrefix % "/erc_zoom_to_location", true).toBool());
d.set_drc_zoom_to_location(
cs.value(mSettingsPrefix % "/drc_zoom_to_location", true).toBool());
d.set_order_pcb_open_web_browser(
cs.value(mSettingsPrefix % "/order_open_web_browser", true).toBool());
const int sectionCount = cs.beginReadArray(mSettingsPrefix % "/sections");
for (int i = 0; i < sectionCount; ++i) {
addSection(mSections->count(), false);
}
cs.endArray();
if (mSections->isEmpty()) {
addSection(0, true);
}
}
MainWindow::~MainWindow() noexcept {
mWindow->window().on_close_requested(
[]() { return slint::CloseRequestResponse::HideWindow; });
mWindow->hide();
}
/*******************************************************************************
* General Methods
******************************************************************************/
bool MainWindow::isCurrentWindow() const noexcept {
return mWidget->isActiveWindow();
}
void MainWindow::makeCurrentWindow() noexcept {
mWidget->show();
mWidget->raise();
mWidget->activateWindow();
}
void MainWindow::addSection(int newIndex, bool makeCurrent) noexcept {
newIndex = qBound(0, newIndex, mSections->count());
std::shared_ptr<WindowSection> s =
std::make_shared<WindowSection>(mApp, *this);
connect(s.get(), &WindowSection::currentTabChanged, this, [this]() {
const ui::Data& d = mWindow->global<ui::Data>();
d.invoke_current_tab_changed();
});
connect(s.get(), &WindowSection::cursorCoordinatesChanged, this,
[this](const Point& pos, const LengthUnit& unit) {
const ui::Data& d = mWindow->global<ui::Data>();
d.set_cursor_coordinates(
q2s(QString("%1, %2")
.arg(unit.convertToUnit(pos.getX()), 1, 'f',
unit.getReasonableNumberOfDecimals())
.arg(unit.convertToUnit(pos.getY()), 1, 'f',
unit.getReasonableNumberOfDecimals())));
});
connect(s.get(), &WindowSection::statusBarMessageChanged, this,
&MainWindow::showStatusBarMessage);
mSections->insert(newIndex, s);
if (makeCurrent || (mSections->count() == 1)) {
const ui::Data& d = mWindow->global<ui::Data>();
d.set_current_section_index(newIndex);
d.invoke_current_tab_changed();
}
updateHomeTabSection();
}
void MainWindow::addTab(std::shared_ptr<WindowTab> tab, int section, int index,
bool switchToTab, bool switchToSection) noexcept {
// If no section was specified, add it to the end.
const ui::Data& d = mWindow->global<ui::Data>();
if (section < 0) {
section = d.get_current_section_index();
}
// Bound the section to make sure the tab is not lost on invalid indices.
section = qBound(0, section, mSections->count() - 1);
if (auto s = mSections->value(section)) {
s->addTab(tab, index, switchToTab);
if (switchToSection) {
d.set_current_section_index(section);
d.invoke_current_tab_changed();
}
}
}
std::shared_ptr<WindowTab> MainWindow::removeTab(
int section, int tab, bool* wasCurrentTab,
bool* wasCurrentSection) noexcept {
const ui::Data& d = mWindow->global<ui::Data>();
const int currentSection = d.get_current_section_index();
if (wasCurrentSection) {
*wasCurrentSection = (section == currentSection);
}
if (auto s = mSections->value(section)) {
return s->removeTab(tab, wasCurrentTab);
}
return nullptr;
}
void MainWindow::showPanelPage(ui::PanelPage page) noexcept {
mWindow->global<ui::Data>().invoke_set_panel_page(page);
}
void MainWindow::popUpNotifications() noexcept {
if (mApp.getNotifications().row_count() > 0) {
mWindow->global<ui::Data>().set_notifications_shown(true);
}
}
void MainWindow::showStatusBarMessage(const QString& message, int timeoutMs) {
const ui::Data& d = mWindow->global<ui::Data>();
d.set_status_bar_message(q2s(message));
if (timeoutMs > 0) {
QTimer::singleShot(timeoutMs, this, [&d, message]() {
if (s2q(d.get_status_bar_message()) == message) {
d.set_status_bar_message(slint::SharedString());
}
});
}
}
void MainWindow::highlightErcMessage(
ProjectEditor& prjEditor, std::shared_ptr<const RuleCheckMessage> msg,
bool zoomTo) noexcept {
auto ercMsg = std::dynamic_pointer_cast<const ErcMsgBase>(msg);
if (!ercMsg) {
qCritical() << "ERC message is not derived from ErcMsgBase.";
return;
}
std::optional<int> schIndex;
for (int i = 0; i < prjEditor.getSchematics().count(); ++i) {
auto se = prjEditor.getSchematics().at(i);
if (ercMsg->getSchematic() == se->getSchematic().getUuid()) {
schIndex = i;
break;
}
}
if (!schIndex) {
qCritical() << "ERC message from unknown schematic.";
return;
}
if (auto tab = openSchematicTab(prjEditor.getUiIndex(), *schIndex)) {
tab->highlightErcMessage(ercMsg, zoomTo);
}
}
void MainWindow::setCurrentLibrary(int index) noexcept {
const ui::Data& d = mWindow->global<ui::Data>();
d.invoke_set_current_library(index);
}
void MainWindow::setCurrentProject(int index) noexcept {
const ui::Data& d = mWindow->global<ui::Data>();
d.invoke_set_current_project(index);
}
void MainWindow::openLibraryTab(const FilePath& fp, bool wizardMode) noexcept {
if (auto editor = mApp.openLibrary(fp)) {
if (!switchToLibraryElementTab<LibraryTab>(fp)) {
addTab(std::make_shared<LibraryTab>(*editor, wizardMode));
}
}
}
void MainWindow::openComponentCategoryTab(LibraryEditor& editor,
const FilePath& fp) noexcept {
openLibraryElementTab<ComponentCategory, ComponentCategoryTab>(editor, fp);
}
bool MainWindow::openNewComponentCategoryTab(
LibraryEditor& editor, const FilePath& duplicateFromFp,
const ComponentCategory* duplicateFromObj) noexcept {
return openNewLibraryElementTab<ComponentCategory, ComponentCategoryTab>(
editor, duplicateFromFp, duplicateFromObj, [this]() {
return std::make_unique<ComponentCategory>(
Uuid::createRandom(), Version::fromString("0.1"),
mApp.getWorkspace().getSettings().userName.get(),
QDateTime::currentDateTime(), ElementName("New Component Category"),
QString(), QString());
});
}
void MainWindow::openPackageCategoryTab(LibraryEditor& editor,
const FilePath& fp) noexcept {
openLibraryElementTab<PackageCategory, PackageCategoryTab>(editor, fp);
}
bool MainWindow::openNewPackageCategoryTab(
LibraryEditor& editor, const FilePath& duplicateFromFp,
const PackageCategory* duplicateFromObj) noexcept {
return openNewLibraryElementTab<PackageCategory, PackageCategoryTab>(
editor, duplicateFromFp, duplicateFromObj, [this]() {
return std::make_unique<PackageCategory>(
Uuid::createRandom(), Version::fromString("0.1"),
mApp.getWorkspace().getSettings().userName.get(),
QDateTime::currentDateTime(), ElementName("New Package Category"),
QString(), QString());
});
}
void MainWindow::openSymbolTab(LibraryEditor& editor,
const FilePath& fp) noexcept {
openLibraryElementTab<Symbol, SymbolTab>(editor, fp);
}
bool MainWindow::openNewSymbolTab(LibraryEditor& editor,
const FilePath& duplicateFromFp,
const Symbol* duplicateFromObj) noexcept {
return openNewLibraryElementTab<Symbol, SymbolTab>(
editor, duplicateFromFp, duplicateFromObj, [this]() {
return std::make_unique<Symbol>(
Uuid::createRandom(), Version::fromString("0.1"),
mApp.getWorkspace().getSettings().userName.get(),
QDateTime::currentDateTime(), ElementName("New Symbol"), QString(),
QString());
});
}
void MainWindow::openPackageTab(LibraryEditor& editor,
const FilePath& fp) noexcept {
openLibraryElementTab<Package, PackageTab>(editor, fp);
}
bool MainWindow::openNewPackageTab(LibraryEditor& editor,
const FilePath& duplicateFromFp,
const Package* duplicateFromObj) noexcept {
return openNewLibraryElementTab<Package, PackageTab>(
editor, duplicateFromFp, duplicateFromObj, [this]() {
std::unique_ptr<Package> obj(new Package(
Uuid::createRandom(), Version::fromString("0.1"),
mApp.getWorkspace().getSettings().userName.get(),
QDateTime::currentDateTime(), ElementName("New Package"), QString(),
QString(), Package::AssemblyType::Auto));
obj->getFootprints().append(std::make_shared<Footprint>(
Uuid::createRandom(), ElementName("default"), ""));
return obj;
});
}
void MainWindow::openComponentTab(LibraryEditor& editor,
const FilePath& fp) noexcept {
openLibraryElementTab<Component, ComponentTab>(editor, fp);
}
bool MainWindow::openNewComponentTab(
LibraryEditor& editor, const FilePath& duplicateFromFp,
const Component* duplicateFromObj) noexcept {
return openNewLibraryElementTab<Component, ComponentTab>(
editor, duplicateFromFp, duplicateFromObj, [this]() {
std::unique_ptr<Component> obj(
new Component(Uuid::createRandom(), Version::fromString("0.1"),
mApp.getWorkspace().getSettings().userName.get(),
QDateTime::currentDateTime(),
ElementName("New Component"), QString(), QString()));
obj->getSymbolVariants().append(
std::make_shared<ComponentSymbolVariant>(
Uuid::createRandom(), "", ElementName("default"), ""));
return obj;
});
}
void MainWindow::openDeviceTab(LibraryEditor& editor,
const FilePath& fp) noexcept {
openLibraryElementTab<Device, DeviceTab>(editor, fp);
}
bool MainWindow::openNewDeviceTab(LibraryEditor& editor,
const FilePath& duplicateFromFp,
const Device* duplicateFromObj) noexcept {
return openNewLibraryElementTab<Device, DeviceTab>(
editor, duplicateFromFp, duplicateFromObj, [this]() {
return std::make_unique<Device>(
Uuid::createRandom(), Version::fromString("0.1"),
mApp.getWorkspace().getSettings().userName.get(),
QDateTime::currentDateTime(), ElementName("New Device"), QString(),
QString(), Uuid::createRandom(), Uuid::createRandom());
});
}
void MainWindow::openOrganizationTab(LibraryEditor& editor,
const FilePath& fp) noexcept {
openLibraryElementTab<Organization, OrganizationTab>(editor, fp);
}
bool MainWindow::openNewOrganizationTab(
LibraryEditor& editor, const FilePath& duplicateFromFp,
const Organization* duplicateFromObj) noexcept {
return openNewLibraryElementTab<Organization, OrganizationTab>(
editor, duplicateFromFp, duplicateFromObj, [this]() {
return std::make_unique<Organization>(
Uuid::createRandom(), Version::fromString("0.1"),
mApp.getWorkspace().getSettings().userName.get(),
QDateTime::currentDateTime(), ElementName("New Organization"),
QString(), QString());
});
}
std::shared_ptr<SchematicTab> MainWindow::openSchematicTab(int projectIndex,
int index) noexcept {
if (auto tab = switchToProjectTab<SchematicTab>(projectIndex, index)) {
return tab;
} else if (auto prjEditor = mApp.getProjects().value(projectIndex)) {
if (auto schEditor = prjEditor->getSchematics().value(index)) {
auto tab = std::make_shared<SchematicTab>(mApp, *schEditor);
addTab(tab);
return tab;
}
}
return nullptr;
}
void MainWindow::openBoard2dTab(int projectIndex, int index,
bool switchToTab) noexcept {
if (!switchToProjectTab<Board2dTab>(projectIndex, index)) {
if (auto prjEditor = mApp.getProjects().value(projectIndex)) {
if (auto brdEditor = prjEditor->getBoards().value(index)) {
addTab(std::make_shared<Board2dTab>(mApp, *brdEditor), -1, -1,
switchToTab, switchToTab);
}
}
}
}
void MainWindow::openBoard3dTab(int projectIndex, int index) noexcept {
if (!switchToProjectTab<Board3dTab>(projectIndex, index)) {
if (auto prjEditor = mApp.getProjects().value(projectIndex)) {
if (auto brdEditor = prjEditor->getBoards().value(index)) {
addTab(std::make_shared<Board3dTab>(mApp, *brdEditor));
}
}
}
}
void MainWindow::openProjectLibraryTab(int projectIndex) noexcept {
if (!switchToProjectTab<ProjectLibraryTab>(projectIndex, 0)) {
if (auto prjEditor = mApp.getProjects().value(projectIndex)) {
addTab(std::make_shared<ProjectLibraryTab>(mApp, *prjEditor));
}
}
}
void MainWindow::requestDeviceTab(const FilePath& fp) noexcept {
if (auto editor = mApp.openLibrary(fp.getParentDir().getParentDir())) {
openDeviceTab(*editor, fp);
}
}
void MainWindow::requestComponentTab(const FilePath& fp) noexcept {
if (auto editor = mApp.openLibrary(fp.getParentDir().getParentDir())) {
openComponentTab(*editor, fp);
}
}
void MainWindow::requestSymbolTab(const FilePath& fp) noexcept {
if (auto editor = mApp.openLibrary(fp.getParentDir().getParentDir())) {
openSymbolTab(*editor, fp);
}
}
void MainWindow::requestPackageTab(const FilePath& fp) noexcept {
if (auto editor = mApp.openLibrary(fp.getParentDir().getParentDir())) {
openPackageTab(*editor, fp);
}
}
/*******************************************************************************
* Private Methods
******************************************************************************/
slint::CloseRequestResponse MainWindow::closeRequested() noexcept {
// Any message boxes might delay closing the window so we don't want to
// autosave any intermediate window state during this process. The timer
// will be restarted by GuiApplication once the window was closed.
mApp.stopWindowStateAutosaveTimer();
// Ask to close tabs, projects, libraries etc.
if (mApp.getWindowCount() >= 2) {
for (auto section : *mSections) {
if (!section->requestCloseAllTabs()) {
return slint::CloseRequestResponse::KeepWindowShown;
}
}
} else if ((!mApp.requestClosingAllProjects()) ||
(!mApp.requestClosingAllLibraries())) {
return slint::CloseRequestResponse::KeepWindowShown;
}
// Save window state.
QSettings cs;
const ui::Data& d = mWindow->global<ui::Data>();
cs.setValue(mSettingsPrefix % "/geometry", mWidget->saveGeometry());
cs.setValue(mSettingsPrefix % "/panel_width_factor",
d.get_panel_width_factor());
cs.setValue(mSettingsPrefix % "/erc_zoom_to_location",
d.get_erc_zoom_to_location());
cs.setValue(mSettingsPrefix % "/drc_zoom_to_location",
d.get_drc_zoom_to_location());
cs.setValue(mSettingsPrefix % "/order_open_web_browser",
d.get_order_pcb_open_web_browser());
cs.beginWriteArray(mSettingsPrefix % "/sections", mSections->count());
cs.endArray();
emit aboutToClose();
return slint::CloseRequestResponse::HideWindow;
}
void MainWindow::trigger(ui::Action a) noexcept {
switch (a) {
// General
case ui::Action::KeyboardShortcutsReference: {
StandardEditorCommandHandler handler(mApp.getWorkspace().getSettings(),
mWidget);
handler.shortcutsReference();
break;
}
case ui::Action::Quit: {
mApp.quit(mWidget);
break;
}
// Window
case ui::Action::WindowNew: {
mApp.createNewWindow();
break;
}
case ui::Action::WindowClose: {
closeRequested();
break;
}
// Workspace
case ui::Action::WorkspaceOpenFolder: {
StandardEditorCommandHandler handler(mApp.getWorkspace().getSettings(),
mWidget);
handler.fileManager(mApp.getWorkspace().getPath());
break;
}
case ui::Action::WorkspaceSwitch: {
mApp.switchWorkspace(mWidget);
break;
}
case ui::Action::WorkspaceSettings: {
mApp.execWorkspaceSettingsDialog(mWidget);
break;
}
case ui::Action::WorkspaceLibrariesRescan: {
mApp.getWorkspace().getLibraryDb().startLibraryRescan();
break;
}
case ui::Action::ProjectImportExamples: {
mApp.addExampleProjects(mWidget);
break;
}
// Library
case ui::Action::LibraryCreate: {
if (!switchToTab<CreateLibraryTab>()) {
auto tab = std::make_shared<CreateLibraryTab>(mApp);
connect(
tab.get(), &CreateLibraryTab::libraryCreated, this,
[this](const FilePath& fp) { openLibraryTab(fp, true); },
Qt::QueuedConnection);
addTab(tab);
}
break;
}
case ui::Action::LibraryDownload: {
if (!switchToTab<DownloadLibraryTab>()) {
addTab(std::make_shared<DownloadLibraryTab>(mApp));
}
break;
}
case ui::Action::LibraryPanelEnsurePopulated: {
mApp.getLocalLibraries().ensurePopulated(true);
mApp.getRemoteLibraries().ensurePopulated(true);
break;
}
case ui::Action::LibraryPanelCheckForUpdates: {
mApp.getRemoteLibraries().checkForUpdates();
break;
}
case ui::Action::LibraryPanelCancelUpdateCheck: {
mApp.getRemoteLibraries().cancelUpdateCheck();
break;
}
case ui::Action::LibraryPanelToggleAll: {
mApp.getRemoteLibraries().toggleAll();
break;
}
case ui::Action::LibraryPanelApply: {
mApp.getRemoteLibraries().applyChanges();
break;
}
case ui::Action::LibraryPanelCancel: {
mApp.getRemoteLibraries().cancel();
break;
}
// Project
case ui::Action::ProjectImportEagle: {
mApp.createProject(FilePath(), true, mWidget);
break;
}
case ui::Action::ProjectNew: {
mApp.createProject(FilePath(), false, mWidget);
break;
}
case ui::Action::ProjectOpen: {
mApp.openProject(FilePath(), mWidget);
break;
}
default: {
qWarning() << "Unhandled UI action:" << static_cast<int>(a);
break;
}
}
}
void MainWindow::triggerSection(int section,
ui::WindowSectionAction a) noexcept {
switch (a) {
case ui::WindowSectionAction::Split: {
addSection(section + 1, true);
break;
}
case ui::WindowSectionAction::Close: {
if (mSections->count() > 1) {
if (std::shared_ptr<WindowSection> s = mSections->takeAt(section)) {
const ui::Data& d = mWindow->global<ui::Data>();
d.set_current_section_index(qBound(-1, d.get_current_section_index(),
mSections->count() - 1));
updateHomeTabSection();
d.invoke_current_tab_changed();
}
}
break;
}
default: {
qWarning() << "Unhandled section action:" << static_cast<int>(a);
break;
}
}
}
void MainWindow::triggerTab(int section, int tab, ui::TabAction a) noexcept {
if (auto s = mSections->value(section)) {
s->triggerTab(tab, a);
}
}
void MainWindow::triggerLibrary(slint::SharedString path,
ui::LibraryAction a) noexcept {
const FilePath fp(s2q(path));
if ((!fp.isValid()) ||
(!fp.isLocatedInDir(mApp.getWorkspace().getLibrariesPath()))) {
qWarning() << "Invalid path in triggerLibrary():" << s2q(path);
return;
}
switch (a) {
case ui::LibraryAction::Open: {
openLibraryTab(fp, false);
break;
}
case ui::LibraryAction::Uninstall: {
try {
mApp.closeLibrary(fp);
// Abort any currently running library rescan since this can cause
// problems due to concurrent file access.
mApp.getWorkspace().getLibraryDb().cancelLibraryRescan();
FileUtils::removeDirRecursively(fp); // can throw
} catch (const Exception& e) {
// TODO: This should be implemented without message box some day...
QMessageBox::critical(mWidget, tr("Error"), e.getMsg());
}
mApp.getWorkspace().getLibraryDb().startLibraryRescan();
break;
}
case ui::LibraryAction::NewComponentCategory: {
if (auto editor = mApp.getLibrary(fp)) {
openNewComponentCategoryTab(*editor);
}
break;
}
case ui::LibraryAction::NewPackageCategory: {
if (auto editor = mApp.getLibrary(fp)) {
openNewPackageCategoryTab(*editor);
}
break;
}
case ui::LibraryAction::NewSymbol: {
if (auto editor = mApp.getLibrary(fp)) {
openNewSymbolTab(*editor);
}
break;
}
case ui::LibraryAction::NewPackage: {
if (auto editor = mApp.getLibrary(fp)) {
openNewPackageTab(*editor);
}
break;
}
case ui::LibraryAction::NewComponent: {
if (auto editor = mApp.getLibrary(fp)) {
openNewComponentTab(*editor);
}
break;
}
case ui::LibraryAction::NewDevice: {
if (auto editor = mApp.getLibrary(fp)) {
openNewDeviceTab(*editor);
}
break;
}
case ui::LibraryAction::NewOrganization: {
if (auto editor = mApp.getLibrary(fp)) {
openNewOrganizationTab(*editor);
}
break;
}
default: {
qWarning() << "Unhandled action in triggerLibrary():"
<< static_cast<int>(a);
break;
}
}
}
void MainWindow::triggerLibraryElement(slint::SharedString path,
ui::LibraryElementAction a) noexcept {
const FilePath fp(s2q(path));
switch (a) {
case ui::LibraryElementAction::Open: {
if (switchToLibraryElementTab<LibraryTab>(fp)) return;
if (switchToLibraryElementTab<ComponentCategoryTab>(fp)) return;
if (switchToLibraryElementTab<PackageCategoryTab>(fp)) return;
if (switchToLibraryElementTab<SymbolTab>(fp)) return;
if (switchToLibraryElementTab<PackageTab>(fp)) return;
if (switchToLibraryElementTab<ComponentTab>(fp)) return;
if (switchToLibraryElementTab<DeviceTab>(fp)) return;
if (switchToLibraryElementTab<OrganizationTab>(fp)) return;
if (mApp.getLibrary(fp)) {
openLibraryTab(fp, false);
}
break;
}
case ui::LibraryElementAction::Close: {
if (auto lib = mApp.getLibrary(fp)) {
if (lib->requestCloseAllTabs()) {
mApp.closeLibrary(fp);
}
}
break;
}
case ui::LibraryElementAction::OpenFolder: {
DesktopServices ds(mApp.getWorkspace().getSettings());
ds.openLocalPath(fp);
break;
}
case ui::LibraryElementAction::ImportEagleLibrary: {
if (mApp.getLibrary(fp)) {
EagleLibraryImportWizard wiz(mApp.getTheme(), mApp.getWorkspace(), fp,
mWidget);
wiz.exec();
}
break;
}
case ui::LibraryElementAction::ImportKicadLibrary: {
if (mApp.getLibrary(fp)) {
KiCadLibraryImportWizard wiz(mApp.getTheme(), mApp.getWorkspace(), fp,
mWidget);
wiz.exec();
}
break;
}
default: {
qWarning() << "Unhandled action in MainWindow::triggerLibraryElement():"
<< static_cast<int>(a);
break;
}
}
}
void MainWindow::triggerProject(int index, ui::ProjectAction a) noexcept {
std::shared_ptr<ProjectEditor> editor = mApp.getProjects().value(index);
if (!editor) return;
switch (a) {
case ui::ProjectAction::Close: {
if (editor->requestClose()) {
mApp.closeProject(index);
}
break;
}
case ui::ProjectAction::OpenLibraryManager: {
openProjectLibraryTab(index);
break;
}
case ui::ProjectAction::NewSheet: {
if (auto schEditor = editor->execNewSheetDialog()) {
openSchematicTab(index, schEditor->getUiIndex());
}
break;
}
case ui::ProjectAction::NewBoard: {
if (auto brdEditor = editor->execNewBoardDialog(std::nullopt)) {
openBoard2dTab(index, brdEditor->getUiIndex());
}
break;
}
default: {
editor->trigger(a);
break;
}
}
}
void MainWindow::triggerSchematic(int project, int schematic,
ui::SchematicAction a) noexcept {
std::shared_ptr<ProjectEditor> prjEditor = mApp.getProjects().value(project);
if (!prjEditor) return;
switch (a) {
case ui::SchematicAction::Open: {
openSchematicTab(project, schematic);
break;
}
case ui::SchematicAction::Rename: {
prjEditor->execRenameSheetDialog(schematic);
break;
}
case ui::SchematicAction::Delete: {
prjEditor->execDeleteSheetDialog(schematic);
break;
}
default: {
qWarning() << "Unhandled action in MainWindow::triggerSchematic():"
<< static_cast<int>(a);
break;
}
}
}
void MainWindow::triggerBoard(int project, int board,
ui::BoardAction a) noexcept {
std::shared_ptr<ProjectEditor> prjEditor = mApp.getProjects().value(project);
if (!prjEditor) return;
switch (a) {
case ui::BoardAction::Open2d: {
openBoard2dTab(project, board);
break;
}
case ui::BoardAction::Open3d: {
openBoard3dTab(project, board);
break;
}
case ui::BoardAction::Copy: {
if (auto brdEditor = prjEditor->execNewBoardDialog(board)) {
openBoard2dTab(project, brdEditor->getUiIndex());
}
break;
}
case ui::BoardAction::Delete: {
prjEditor->execDeleteBoardDialog(board);
break;
}
case ui::BoardAction::ExportStep: {
if (auto brdEditor = prjEditor->getBoards().value(board)) {
brdEditor->execStepExportDialog();
}
break;
}
case ui::BoardAction::RunQuickCheck: {
if (auto brdEditor = prjEditor->getBoards().value(board)) {
brdEditor->startDrc(true);
}
break;
}
case ui::BoardAction::RunDrc: {
if (auto brdEditor = prjEditor->getBoards().value(board)) {
brdEditor->startDrc(false);
}
break;
}
case ui::BoardAction::OpenSetupDialog: {
if (auto brdEditor = prjEditor->getBoards().value(board)) {
brdEditor->execBoardSetupDialog();
}
break;
}
case ui::BoardAction::OpenDrcSetupDialog: {
if (auto brdEditor = prjEditor->getBoards().value(board)) {
brdEditor->execBoardSetupDialog(true);
}
break;
}
case ui::BoardAction::PrepareOrder: {
if (auto brdEditor = prjEditor->getBoards().value(board)) {
brdEditor->prepareOrderPcb();
}
break;
}
case ui::BoardAction::StartOrder: {
if (auto brdEditor = prjEditor->getBoards().value(board)) {
const ui::Data& d = mWindow->global<ui::Data>();
brdEditor->startOrderPcbUpload(d.get_order_pcb_open_web_browser());
}
break;
}
default: {
qWarning() << "Unhandled action in MainWindow::triggerBoard():"
<< static_cast<int>(a);
break;
}
}
}
void MainWindow::updateHomeTabSection() noexcept {
for (int i = 0; i < mSections->count(); ++i) {
mSections->at(i)->setHomeTabVisible(i == 0);
}
}
template <typename Element, typename Tab>
void MainWindow::openLibraryElementTab(LibraryEditor& editor,
const FilePath& fp) noexcept {
if (!switchToLibraryElementTab<Tab>(fp)) {
try {
auto fs = TransactionalFileSystem::open(
fp, editor.isWritable(), &askForRestoringBackup,
DirectoryLockHandlerDialog::createDirectoryLockCallback());
std::unique_ptr<Element> obj =
Element::open(std::make_unique<TransactionalDirectory>(fs));
addTab(std::make_shared<Tab>(editor, std::move(obj), Tab::Mode::Open));
} catch (const UserCanceled& e) {
} catch (const Exception& e) {
QMessageBox::critical(mWidget, tr("Error"), e.getMsg());
}
}
}
template <typename Element, typename Tab>
bool MainWindow::openNewLibraryElementTab(
LibraryEditor& editor, const FilePath& duplicateFromFp,
const Element* duplicateFromObj,
std::function<std::unique_ptr<Element>()> factory) noexcept {
try {
auto obj = factory();
std::unique_ptr<Element> src;
if ((!duplicateFromObj) && duplicateFromFp.isValid()) {
auto fs = TransactionalFileSystem::openRO(duplicateFromFp,
&askForRestoringBackup);
src = Element::open(std::make_unique<TransactionalDirectory>(fs));
duplicateFromObj = src.get();
}
if (duplicateFromObj) {
duplicateLibraryElement(*obj, *duplicateFromObj);
}
addTab(std::make_shared<Tab>(
editor, std::move(obj),
duplicateFromObj ? Tab::Mode::Duplicate : Tab::Mode::New));
return true;
} catch (const UserCanceled& e) {
} catch (const Exception& e) {
QMessageBox::critical(mWidget, tr("Error"), e.getMsg());
}
return false;
}
template <typename T>
bool MainWindow::switchToTab() noexcept {
for (const auto& section : *mSections) {
if (section->switchToTab<T>()) {
return true;
}
}
return false;
}
template <typename T>
bool MainWindow::switchToLibraryElementTab(const FilePath& fp) noexcept {
for (const auto& section : *mSections) {
if (section->switchToLibraryElementTab<T>(fp)) {
return true;
}
}
return false;
}
template <typename T>
std::shared_ptr<T> MainWindow::switchToProjectTab(int prjIndex,
int objIndex) noexcept {
for (const auto& section : *mSections) {
if (auto tab = section->switchToProjectTab<T>(prjIndex, objIndex)) {
return tab;
}
}
return nullptr;
}
/*******************************************************************************
* End of File
******************************************************************************/
} // namespace editor
} // namespace librepcb