From 630c9523826bcb9167144ac532bcec75c8c1d077 Mon Sep 17 00:00:00 2001 From: Gregory Bednov Date: Wed, 25 Feb 2026 23:25:45 +0300 Subject: [PATCH] =?UTF-8?q?=09=D0=B8=D0=B7=D0=BC=D0=B5=D0=BD=D0=B5=D0=BD?= =?UTF-8?q?=D0=BE:=20=20=20=20=20=20CMakeLists.txt=20=09=D0=BD=D0=BE=D0=B2?= =?UTF-8?q?=D1=8B=D0=B9=20=D1=84=D0=B0=D0=B9=D0=BB:=20=20=20=20cmake/Info.?= =?UTF-8?q?plist.in=20=09=D0=BD=D0=BE=D0=B2=D1=8B=D0=B9=20=D1=84=D0=B0?= =?UTF-8?q?=D0=B9=D0=BB:=20=20=20=20default.nix=20=09=D0=BD=D0=BE=D0=B2?= =?UTF-8?q?=D1=8B=D0=B9=20=D1=84=D0=B0=D0=B9=D0=BB:=20=20=20=20desktop.nix?= =?UTF-8?q?=20=09=D0=BD=D0=BE=D0=B2=D1=8B=D0=B9=20=D1=84=D0=B0=D0=B9=D0=BB?= =?UTF-8?q?:=20=20=20=20packaging/linux/idef0-editor.desktop=20=09=D0=BD?= =?UTF-8?q?=D0=BE=D0=B2=D1=8B=D0=B9=20=D1=84=D0=B0=D0=B9=D0=BB:=20=20=20?= =?UTF-8?q?=20packaging/linux/idef0.xml=20=09=D0=BD=D0=BE=D0=B2=D1=8B?= =?UTF-8?q?=D0=B9=20=D1=84=D0=B0=D0=B9=D0=BB:=20=20=20=20packaging/windows?= =?UTF-8?q?/idef0-file-association.reg.in=20=09=D0=B8=D0=B7=D0=BC=D0=B5?= =?UTF-8?q?=D0=BD=D0=B5=D0=BD=D0=BE:=20=20=20=20=20=20src/MainWindow.cpp?= =?UTF-8?q?=20=09=D0=B8=D0=B7=D0=BC=D0=B5=D0=BD=D0=B5=D0=BD=D0=BE:=20=20?= =?UTF-8?q?=20=20=20=20src/MainWindow.h=20=09=D0=B8=D0=B7=D0=BC=D0=B5?= =?UTF-8?q?=D0=BD=D0=B5=D0=BD=D0=BE:=20=20=20=20=20=20src/items/ArrowItem.?= =?UTF-8?q?cpp=20=09=D0=B8=D0=B7=D0=BC=D0=B5=D0=BD=D0=B5=D0=BD=D0=BE:=20?= =?UTF-8?q?=20=20=20=20=20src/items/ArrowItem.h=20=09=D0=B8=D0=B7=D0=BC?= =?UTF-8?q?=D0=B5=D0=BD=D0=B5=D0=BD=D0=BE:=20=20=20=20=20=20src/items/Bloc?= =?UTF-8?q?kItem.cpp=20=09=D0=B8=D0=B7=D0=BC=D0=B5=D0=BD=D0=B5=D0=BD=D0=BE?= =?UTF-8?q?:=20=20=20=20=20=20src/items/BlockItem.h=20=09=D0=B8=D0=B7?= =?UTF-8?q?=D0=BC=D0=B5=D0=BD=D0=B5=D0=BD=D0=BE:=20=20=20=20=20=20src/item?= =?UTF-8?q?s/DiagramScene.cpp=20=09=D0=B8=D0=B7=D0=BC=D0=B5=D0=BD=D0=B5?= =?UTF-8?q?=D0=BD=D0=BE:=20=20=20=20=20=20src/items/DiagramScene.h=20=09?= =?UTF-8?q?=D0=BD=D0=BE=D0=B2=D1=8B=D0=B9=20=D1=84=D0=B0=D0=B9=D0=BB:=20?= =?UTF-8?q?=20=20=20src/plugins/Manual.md=20=09=D0=BD=D0=BE=D0=B2=D1=8B?= =?UTF-8?q?=D0=B9=20=D1=84=D0=B0=D0=B9=D0=BB:=20=20=20=20src/plugins/Plugi?= =?UTF-8?q?nApi.h=20=09=D0=BD=D0=BE=D0=B2=D1=8B=D0=B9=20=D1=84=D0=B0=D0=B9?= =?UTF-8?q?=D0=BB:=20=20=20=20src/plugins/PluginManager.cpp=20=09=D0=BD?= =?UTF-8?q?=D0=BE=D0=B2=D1=8B=D0=B9=20=D1=84=D0=B0=D0=B9=D0=BB:=20=20=20?= =?UTF-8?q?=20src/plugins/PluginManager.h=20=09=D0=BD=D0=BE=D0=B2=D1=8B?= =?UTF-8?q?=D0=B9=20=D1=84=D0=B0=D0=B9=D0=BB:=20=20=20=20src/plugins/color?= =?UTF-8?q?/ColorsPlugin.cpp=20=09=D0=BD=D0=BE=D0=B2=D1=8B=D0=B9=20=D1=84?= =?UTF-8?q?=D0=B0=D0=B9=D0=BB:=20=20=20=20src/plugins/color/ColorsPlugin.h?= =?UTF-8?q?=20=09=D0=BD=D0=BE=D0=B2=D1=8B=D0=B9=20=D1=84=D0=B0=D0=B9=D0=BB?= =?UTF-8?q?:=20=20=20=20src/plugins/color/translations/colors=5Fen.ts=20?= =?UTF-8?q?=09=D0=BD=D0=BE=D0=B2=D1=8B=D0=B9=20=D1=84=D0=B0=D0=B9=D0=BB:?= =?UTF-8?q?=20=20=20=20src/plugins/color/translations/colors=5Ffr.ts=20=09?= =?UTF-8?q?=D0=BD=D0=BE=D0=B2=D1=8B=D0=B9=20=D1=84=D0=B0=D0=B9=D0=BB:=20?= =?UTF-8?q?=20=20=20src/plugins/color/translations/colors=5Fru.ts=20=09?= =?UTF-8?q?=D0=BD=D0=BE=D0=B2=D1=8B=D0=B9=20=D1=84=D0=B0=D0=B9=D0=BB:=20?= =?UTF-8?q?=20=20=20translations/README.txt=20=09=D0=BD=D0=BE=D0=B2=D1=8B?= =?UTF-8?q?=D0=B9=20=D1=84=D0=B0=D0=B9=D0=BB:=20=20=20=20translations/idef?= =?UTF-8?q?0=5Fen.ts=20=09=D0=BD=D0=BE=D0=B2=D1=8B=D0=B9=20=D1=84=D0=B0?= =?UTF-8?q?=D0=B9=D0=BB:=20=20=20=20translations/idef0=5Ffr.ts=20=09=D0=BD?= =?UTF-8?q?=D0=BE=D0=B2=D1=8B=D0=B9=20=D1=84=D0=B0=D0=B9=D0=BB:=20=20=20?= =?UTF-8?q?=20translations/idef0=5Fru.ts?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CMakeLists.txt | 50 +- cmake/Info.plist.in | 66 ++ default.nix | 74 +++ desktop.nix | 9 + packaging/linux/idef0-editor.desktop | 9 + packaging/linux/idef0.xml | 7 + .../windows/idef0-file-association.reg.in | 18 + src/MainWindow.cpp | 137 +++-- src/MainWindow.h | 7 +- src/items/ArrowItem.cpp | 30 +- src/items/ArrowItem.h | 12 + src/items/BlockItem.cpp | 33 +- src/items/BlockItem.h | 8 +- src/items/DiagramScene.cpp | 136 ++++- src/items/DiagramScene.h | 11 +- src/plugins/Manual.md | 79 +++ src/plugins/PluginApi.h | 37 ++ src/plugins/PluginManager.cpp | 175 ++++++ src/plugins/PluginManager.h | 55 ++ src/plugins/color/ColorsPlugin.cpp | 92 +++ src/plugins/color/ColorsPlugin.h | 2 + src/plugins/color/translations/colors_en.ts | 19 + src/plugins/color/translations/colors_fr.ts | 19 + src/plugins/color/translations/colors_ru.ts | 19 + translations/README.txt | 1 + translations/idef0_en.ts | 567 +++++++++++++++++ translations/idef0_fr.ts | 567 +++++++++++++++++ translations/idef0_ru.ts | 571 ++++++++++++++++++ 28 files changed, 2720 insertions(+), 90 deletions(-) create mode 100644 cmake/Info.plist.in create mode 100644 default.nix create mode 100644 desktop.nix create mode 100644 packaging/linux/idef0-editor.desktop create mode 100644 packaging/linux/idef0.xml create mode 100644 packaging/windows/idef0-file-association.reg.in create mode 100644 src/plugins/Manual.md create mode 100644 src/plugins/PluginApi.h create mode 100644 src/plugins/PluginManager.cpp create mode 100644 src/plugins/PluginManager.h create mode 100644 src/plugins/color/ColorsPlugin.cpp create mode 100644 src/plugins/color/ColorsPlugin.h create mode 100644 src/plugins/color/translations/colors_en.ts create mode 100644 src/plugins/color/translations/colors_fr.ts create mode 100644 src/plugins/color/translations/colors_ru.ts create mode 100644 translations/README.txt create mode 100644 translations/idef0_en.ts create mode 100644 translations/idef0_fr.ts create mode 100644 translations/idef0_ru.ts diff --git a/CMakeLists.txt b/CMakeLists.txt index 70583ac..5d40877 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -8,14 +8,21 @@ find_package(Qt6 REQUIRED COMPONENTS Widgets) qt_standard_project_setup() -qt_add_executable(idef0_editor - src/main.cpp +qt_add_library(idef0_core SHARED src/MainWindow.h src/MainWindow.cpp src/items/DiagramScene.h src/items/DiagramScene.cpp src/items/BlockItem.h src/items/BlockItem.cpp src/items/ArrowItem.h src/items/ArrowItem.cpp src/items/JunctionItem.h src/items/JunctionItem.cpp src/items/HeaderFooterItem.h src/items/HeaderFooterItem.cpp + src/plugins/PluginApi.h + src/plugins/PluginManager.h src/plugins/PluginManager.cpp +) + +option(BUILD_COLORS_PLUGIN "Build the Colors plugin" ON) + +qt_add_executable(idef0_editor + src/main.cpp ) if(APPLE) @@ -36,14 +43,51 @@ endif() target_compile_definitions(idef0_editor PRIVATE $<$>:QT_NO_DEBUG_OUTPUT>) +target_include_directories(idef0_core PRIVATE src) target_include_directories(idef0_editor PRIVATE src) -target_link_libraries(idef0_editor PRIVATE Qt6::Widgets) +target_link_libraries(idef0_core PRIVATE Qt6::Widgets) +target_link_libraries(idef0_editor PRIVATE Qt6::Widgets idef0_core) +set_target_properties(idef0_core PROPERTIES + WINDOWS_EXPORT_ALL_SYMBOLS ON + LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}" + RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}" + INSTALL_RPATH "\$ORIGIN" +) + +if(BUILD_COLORS_PLUGIN) + add_library(colorsplugin MODULE + src/plugins/color/ColorsPlugin.h + src/plugins/color/ColorsPlugin.cpp + ) + target_link_libraries(colorsplugin PRIVATE idef0_core Qt6::Widgets) + target_include_directories(colorsplugin PRIVATE src) + set_target_properties(colorsplugin PROPERTIES + LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/plugins/color" + RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/plugins/color" + INSTALL_RPATH "\$ORIGIN/../../lib" + ) +endif() + +set_target_properties(idef0_editor PROPERTIES + INSTALL_RPATH "\$ORIGIN/../lib" +) install(TARGETS idef0_editor BUNDLE DESTINATION . RUNTIME DESTINATION bin ) +install(TARGETS idef0_core + LIBRARY DESTINATION lib + RUNTIME DESTINATION bin + ARCHIVE DESTINATION lib +) +if(BUILD_COLORS_PLUGIN) + install(TARGETS colorsplugin + LIBRARY DESTINATION plugins/color + RUNTIME DESTINATION plugins/color + ) +endif() if(UNIX AND NOT APPLE) install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/packaging/linux/idef0-editor.desktop diff --git a/cmake/Info.plist.in b/cmake/Info.plist.in new file mode 100644 index 0000000..9890ed5 --- /dev/null +++ b/cmake/Info.plist.in @@ -0,0 +1,66 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + ${EXECUTABLE_NAME} + CFBundleIdentifier + @MACOS_BUNDLE_ID@ + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + @MACOS_BUNDLE_NAME@ + CFBundlePackageType + APPL + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + LSMinimumSystemVersion + 11.0 + NSHighResolutionCapable + + + UTExportedTypeDeclarations + + + UTTypeIdentifier + com.gregorybednov.idef0 + UTTypeDescription + IDEF0 Diagram + UTTypeConformsTo + + public.data + + UTTypeTagSpecification + + public.filename-extension + + idef0 + + public.mime-type + application/x-idef0+json + + + + + CFBundleDocumentTypes + + + CFBundleTypeName + IDEF0 Diagram + CFBundleTypeRole + Editor + LSHandlerRank + Owner + LSItemContentTypes + + com.gregorybednov.idef0 + + + + + diff --git a/default.nix b/default.nix new file mode 100644 index 0000000..ff3acd5 --- /dev/null +++ b/default.nix @@ -0,0 +1,74 @@ +{ pkgs ? import {} +, buildColorsPlugin ? true +}: + +let + inherit (pkgs) lib stdenv cmake pkg-config; + qt = pkgs.qt6; +in +stdenv.mkDerivation rec { + pname = "idef0_editor"; + version = "0.1.0"; + + src = lib.cleanSourceWith { + src = ./.; + filter = path: type: + (lib.cleanSourceFilter path type) + && (builtins.baseNameOf path != "build"); + }; + + nativeBuildInputs = [ + cmake + pkg-config + qt.wrapQtAppsHook + qt.qttools + ]; + + buildInputs = [ + qt.qtbase + ]; + + configurePhase = '' + cmake -S . -B build -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_INSTALL_PREFIX=$out \ + -DBUILD_COLORS_PLUGIN=${if buildColorsPlugin then "ON" else "OFF"} + ''; + + buildPhase = '' + cmake --build build + find . -path "*/translations/*.ts" -print0 | while IFS= read -r -d "" f; do + out_path=$(printf '%s\n' "$f" | sed 's/\.ts$/.qm/') + lrelease "$f" -qm "$out_path" + done + ''; + + installPhase = '' + runHook preInstall + cmake --install build --prefix $out + # app translations + if [ -d translations ]; then + mkdir -p $out/share/idef0/translations + find translations -maxdepth 1 -type f -name '*.qm' -print0 | xargs -0 -r cp -t $out/share/idef0/translations + fi + # plugin translations + find src/plugins -path "*/translations/*.qm" -print0 | while IFS= read -r -d "" f; do + rel="''${f#src/plugins/}" # e.g., color/translations/colors_en.qm + plugdir="''${rel%%/translations/*}" # e.g., color + dest="$out/plugins/$plugdir/translations" + mkdir -p "$dest" + cp "$f" "$dest/" + done + runHook postInstall + ''; + + qtWrapperArgs = [ + "--set" "QT_LOGGING_RULES" "qt.qpa.wayland.textinput=false" + ]; + + meta = with lib; { + description = "IDEF0 diagram editor built with Qt 6 Widgets"; + license = licenses.lgpl3Plus; + mainProgram = "idef0_editor"; + platforms = platforms.linux; + }; +} diff --git a/desktop.nix b/desktop.nix new file mode 100644 index 0000000..eca6518 --- /dev/null +++ b/desktop.nix @@ -0,0 +1,9 @@ +{ pkgs ? import {} }: + +# Desktop-focused Nix entrypoint for NixOS distributions. +# Currently this reuses default.nix, which already installs: +# - binary +# - translations +# - .desktop launcher +# - MIME package (application/x-idef0+json for *.idef0) +import ./default.nix { inherit pkgs; } diff --git a/packaging/linux/idef0-editor.desktop b/packaging/linux/idef0-editor.desktop new file mode 100644 index 0000000..dd53292 --- /dev/null +++ b/packaging/linux/idef0-editor.desktop @@ -0,0 +1,9 @@ +[Desktop Entry] +Type=Application +Name=IDEF0 Editor +Comment=IDEF0 diagram editor +Exec=idef0_editor %f +Terminal=false +Categories=Office;Graphics; +MimeType=application/x-idef0+json; +StartupNotify=true diff --git a/packaging/linux/idef0.xml b/packaging/linux/idef0.xml new file mode 100644 index 0000000..582bce1 --- /dev/null +++ b/packaging/linux/idef0.xml @@ -0,0 +1,7 @@ + + + + IDEF0 diagram + + + diff --git a/packaging/windows/idef0-file-association.reg.in b/packaging/windows/idef0-file-association.reg.in new file mode 100644 index 0000000..1032783 --- /dev/null +++ b/packaging/windows/idef0-file-association.reg.in @@ -0,0 +1,18 @@ +Windows Registry Editor Version 5.00 + +[HKEY_CURRENT_USER\Software\Classes\.idef0] +@="IDEF0Editor.Document" +"Content Type"="application/x-idef0+json" + +[HKEY_CURRENT_USER\Software\Classes\IDEF0Editor.Document] +@="IDEF0 Diagram" + +[HKEY_CURRENT_USER\Software\Classes\IDEF0Editor.Document\DefaultIcon] +@="@APP_EXE@,0" + +[HKEY_CURRENT_USER\Software\Classes\IDEF0Editor.Document\shell] + +[HKEY_CURRENT_USER\Software\Classes\IDEF0Editor.Document\shell\open] + +[HKEY_CURRENT_USER\Software\Classes\IDEF0Editor.Document\shell\open\command] +@="\"@APP_EXE@\" \"%1\"" diff --git a/src/MainWindow.cpp b/src/MainWindow.cpp index 6934a7f..e3c1323 100644 --- a/src/MainWindow.cpp +++ b/src/MainWindow.cpp @@ -32,7 +32,6 @@ #include #include #include -#include #include #include #include @@ -44,10 +43,13 @@ #include #include #include +#include +#include #include "items/BlockItem.h" +#include "plugins/PluginManager.h" -static const char* kDiagramFileFilter = "IDEF0 Diagram (*.idef0);;JSON Diagram (*.json)"; -static const char* kPdfFileFilter = "PDF (*.pdf)"; +static const char* kDiagramFileFilter = QT_TR_NOOP("IDEF0 Diagram (*.idef0);;JSON Diagram (*.json)"); +static const char* kPdfFileFilter = QT_TR_NOOP("PDF (*.pdf)"); static QPageSize::PageSizeId pageSizeFromMeta(const QVariantMap& meta) { const QString size = meta.value("pageSize", "A4").toString(); @@ -59,7 +61,8 @@ static QPageSize::PageSizeId pageSizeFromMeta(const QVariantMap& meta) { static QPageLayout::Orientation pageOrientationFromMeta(const QVariantMap& meta) { const QString orient = meta.value("pageOrientation").toString(); - if (orient == QObject::tr("Portrait") || orient == "Portrait") { + const QString portrait = QCoreApplication::translate("MainWindow", "Portrait"); + if (orient == portrait || orient == "Portrait") { return QPageLayout::Portrait; } return QPageLayout::Landscape; @@ -125,7 +128,9 @@ void MainWindow::setupActions() { auto* editMenu = menuBar()->addMenu(tr("&Edit")); auto* actSwapNums = new QAction(tr("Swap numbers…"), this); + auto* actCallMech = new QAction(tr("Call Mechanism add"), this); editMenu->addAction(actSwapNums); + editMenu->addAction(actCallMech); auto* viewMenu = menuBar()->addMenu(tr("&View")); auto* actFit = new QAction(tr("Fit"), this); @@ -134,6 +139,7 @@ void MainWindow::setupActions() { m_view->fitInView(m_scene->itemsBoundingRect().adjusted(-50,-50,50,50), Qt::KeepAspectRatio); }); viewMenu->addAction(actFit); + auto* pluginsMenu = menuBar()->addMenu(tr("&Plugins")); auto* actZoomIn = new QAction(tr("Scale +"), this); actZoomIn->setShortcut(QKeySequence::ZoomIn); @@ -149,12 +155,6 @@ void MainWindow::setupActions() { }); viewMenu->addAction(actZoomOut); - m_actColorfulMode = new QAction(tr("Colorful mode"), this); - m_actColorfulMode->setCheckable(true); - m_actColorfulMode->setChecked(m_welcomeState.value("colorfulMode", false).toBool()); - viewMenu->addSeparator(); - viewMenu->addAction(m_actColorfulMode); - m_actDarkMode = new QAction(tr("Dark mode"), this); m_actDarkMode->setCheckable(true); m_actDarkMode->setChecked(m_welcomeState.value("darkMode", false).toBool()); @@ -165,67 +165,22 @@ void MainWindow::setupActions() { m_actFollowSystemTheme->setChecked(m_welcomeState.value("followSystemTheme", false).toBool()); viewMenu->addAction(m_actFollowSystemTheme); - m_actArrowColor = new QAction(tr("Arrow color..."), this); - m_actBorderColor = new QAction(tr("Block border color..."), this); - m_actFontColor = new QAction(tr("Block font color..."), this); - viewMenu->addAction(m_actArrowColor); - viewMenu->addAction(m_actBorderColor); - viewMenu->addAction(m_actFontColor); - - auto ensureDefaultColor = [this](const char* key, const QColor& fallback) { - QColor c(m_welcomeState.value(key).toString()); - if (!c.isValid()) m_welcomeState[key] = fallback.name(); - }; - ensureDefaultColor("arrowColor", QColor("#2b6ee6")); - ensureDefaultColor("blockBorderColor", QColor("#0f766e")); - ensureDefaultColor("blockFontColor", QColor("#991b1b")); - auto applyModes = [this](bool mark) { - const bool colorful = m_actColorfulMode->isChecked(); const bool followSystem = m_actFollowSystemTheme->isChecked(); const bool dark = m_actDarkMode->isChecked(); - m_welcomeState["colorfulMode"] = colorful; m_welcomeState["followSystemTheme"] = followSystem; m_welcomeState["darkMode"] = dark; m_welcomeState["resolvedDarkMode"] = effectiveDarkMode(); - - m_actArrowColor->setEnabled(colorful); - m_actBorderColor->setEnabled(colorful); - m_actFontColor->setEnabled(colorful); m_actDarkMode->setEnabled(!followSystem); - applyAppPalette(m_welcomeState.value("resolvedDarkMode").toBool()); m_scene->applyMeta(m_welcomeState); if (mark) markDirty(true); }; - - connect(m_actColorfulMode, &QAction::toggled, this, [applyModes]{ applyModes(true); }); connect(m_actDarkMode, &QAction::toggled, this, [applyModes]{ applyModes(true); }); connect(m_actFollowSystemTheme, &QAction::toggled, this, [applyModes]{ applyModes(true); }); connect(qApp->styleHints(), &QStyleHints::colorSchemeChanged, this, [applyModes](Qt::ColorScheme){ applyModes(false); }); - connect(m_actArrowColor, &QAction::triggered, this, [this, applyModes]{ - const QColor cur(m_welcomeState.value("arrowColor").toString()); - const QColor chosen = QColorDialog::getColor(cur.isValid() ? cur : QColor("#2b6ee6"), this, tr("Arrow color")); - if (!chosen.isValid()) return; - m_welcomeState["arrowColor"] = chosen.name(); - applyModes(true); - }); - connect(m_actBorderColor, &QAction::triggered, this, [this, applyModes]{ - const QColor cur(m_welcomeState.value("blockBorderColor").toString()); - const QColor chosen = QColorDialog::getColor(cur.isValid() ? cur : QColor("#0f766e"), this, tr("Block border color")); - if (!chosen.isValid()) return; - m_welcomeState["blockBorderColor"] = chosen.name(); - applyModes(true); - }); - connect(m_actFontColor, &QAction::triggered, this, [this, applyModes]{ - const QColor cur(m_welcomeState.value("blockFontColor").toString()); - const QColor chosen = QColorDialog::getColor(cur.isValid() ? cur : QColor("#991b1b"), this, tr("Block font color")); - if (!chosen.isValid()) return; - m_welcomeState["blockFontColor"] = chosen.name(); - applyModes(true); - }); applyModes(false); connect(actSwapNums, &QAction::triggered, this, [this]{ @@ -327,6 +282,59 @@ void MainWindow::setupActions() { if (dlg.exec() != QDialog::Accepted) return; exportPdf(scope->currentIndex() == 1, pageNumbers->isChecked()); }); + + connect(actCallMech, &QAction::triggered, this, [this]{ + if (!m_scene) return; + const auto sel = m_scene->selectedItems(); + if (sel.size() != 1) { + QMessageBox::information(this, tr("Call Mechanism"), tr("Select exactly one block to add a call mechanism.")); + return; + } + auto* target = qgraphicsitem_cast(sel.first()); + if (!target) { + QMessageBox::information(this, tr("Call Mechanism"), tr("Select a block to add a call mechanism.")); + return; + } + if (m_scene->hasCallMechanism(target)) { + QMessageBox::information(this, tr("Call Mechanism"), tr("Call mechanism already exists for this block.")); + return; + } + struct Option { QString display; BlockItem* block = nullptr; }; + QVector