mirror of
https://github.com/qelectrotech/qelectrotech-source-mirror.git
synced 2026-10-10 14:14:18 +02:00
a3594c6633
Edit > Add > Generic device (also on the Add toolbar, and found by the
command search as diagrameditor.add_generic_device) opens a three-page
wizard for devices used too rarely to draw a symbol for, such as a VSD
or a power supply:
1. Layout: terminals per side (0 for none), a gap every N terminals,
divider marks on gaps, groups lined up across opposite sides, pitch
and extra size.
2. Terminals: a table per side and the same list as one line, where
In{4} is In1..In4, Q{z4} is Q0..Q3, X{3-6} is X3..X6 and an empty
entry is a gap; number pins 1..n counter-clockwise; paste rows of
side, name, function from a spreadsheet or a CSV file.
3. Information: name, label, manufacturer, reference, description.
A preview beside every page shows the box as it will be placed. Finish,
available from the first page, writes an ordinary symbol to the folder
"Generic devices" of the project's collection and puts it on the cursor.
The box sizes itself to its terminals and their names; every terminal
lands on the grid and gets a uuid. No new XML: any QElectroTech reads it.
The layout is pure (sources/genericdevice/genericdevice.cpp) and tested
by tst_genericdevice. ElementPictureFactory gains pictureFromDefinition()
to draw the preview with the same code as a placed element.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
4469 lines
171 KiB
C++
4469 lines
171 KiB
C++
/*
|
|
Copyright 2006-2026 The QElectroTech Team
|
|
This file is part of QElectroTech.
|
|
|
|
QElectroTech 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 2 of the License, or
|
|
(at your option) any later version.
|
|
|
|
QElectroTech 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 QElectroTech. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
#include "qetdiagrameditor.h"
|
|
#include "shownkinds.h"
|
|
#ifdef QET_HAS_SCRIPTING
|
|
#include "scripting/qetscripting.h"
|
|
#include "scripting/scriptlibrary.h"
|
|
#include "scripting/scriptmanagerdialog.h"
|
|
#include "scripting/liveserver.h"
|
|
#include "scripting/macrorecorder.h"
|
|
#include "scripting/assistantinfo.h"
|
|
#endif
|
|
#include <QCoreApplication>
|
|
#include <QToolButton>
|
|
#include <QWidgetAction>
|
|
#include "ElementsCollection/elementscollectionwidget.h"
|
|
#include "ElementsCollection/elementpickerpopup.h"
|
|
#include "shortcutbarsettings.h"
|
|
#include "diagramtoolbarsettings.h"
|
|
#include "qetgraphicsitem/conductor.h"
|
|
#include "itemgroups.h"
|
|
#include "commandsearchpopup.h"
|
|
#include "QWidgetAnimation/qwidgetanimation.h"
|
|
#include "autoNum/ui/autonumberingdockwidget.h"
|
|
#include "conductornumexport.h"
|
|
#include "diagramcommands.h"
|
|
#include "diagramcontent.h"
|
|
#include "diagramevent/diagrameventaddimage.h"
|
|
#ifdef QET_HAS_QTPDF
|
|
#include "diagramevent/diagrameventaddpdf.h"
|
|
#endif
|
|
#include "diagramevent/diagrameventaddshape.h"
|
|
#include "diagramevent/diagrameventaddpath.h"
|
|
#include "diagramevent/diagrameventfillet.h"
|
|
#include "diagramevent/diagrameventaddtext.h"
|
|
#include "diagramevent/diagrameventaddpaste.h"
|
|
#include "diagramview.h"
|
|
#include "elementspanelwidget.h"
|
|
#include "factory/elementpicturefactory.h"
|
|
#include "factory/qetgraphicstablefactory.h"
|
|
#include "print/projectprintwindow.h"
|
|
#include "project/projectpropertieshandler.h"
|
|
#include "projectview.h"
|
|
#include "qetproject.h"
|
|
#include "qetgraphicsitem/ViewItem/qetgraphicstableitem.h"
|
|
#include "qetgraphicsitem/conductortextitem.h"
|
|
#include "qetgraphicsitem/dynamicelementtextitem.h"
|
|
#include "qeticons.h"
|
|
#include "qetmessagebox.h"
|
|
#include "recentfiles.h"
|
|
#include "textgrid.h"
|
|
#include "shortcutmanager.h"
|
|
#include "ui/bomexportdialog.h"
|
|
#include "ui/conductorcolortoolbutton.h"
|
|
#include "ui/diagrambgcolorbutton.h"
|
|
#include "ui/duplicateoffsetdialog.h"
|
|
#include "ui/jumptoelementdialog.h"
|
|
#include "ui/diagrampropertieseditordockwidget.h"
|
|
#include "ui/backupdialog.h"
|
|
#include "ui/dialogwaiting.h"
|
|
#include "undocommand/addelementtextcommand.h"
|
|
#include "undocommand/changeelementinformationcommand.h"
|
|
#include "qetinformation.h"
|
|
#include "utils/qetsettings.h"
|
|
#include "utils/qetutils.h"
|
|
#include "undocommand/groupitemscommand.h"
|
|
#include "undocommand/alignselectioncommand.h"
|
|
#include "undocommand/mirrorselectioncommand.h"
|
|
#include "undocommand/rotateselectioncommand.h"
|
|
#include "undocommand/rotatetextscommand.h"
|
|
#include "diagram.h"
|
|
#include "TerminalStrip/ui/terminalstripeditorwindow.h"
|
|
#include "ui/diagrameditorhandlersizewidget.h"
|
|
#include "TerminalStrip/ui/addterminalstripitemdialog.h"
|
|
#include "wiringlistexport.h"
|
|
#include "ui/wiringlistdialog.h"
|
|
#include "ui/terminalnumberingdialog.h"
|
|
#include "toolbarsettings.h"
|
|
#include <QDateTime>
|
|
#include <QDebug>
|
|
#include <QDir>
|
|
#include <QDesktopServices>
|
|
#include <QTimer>
|
|
#include <QGuiApplication>
|
|
#include <QPushButton>
|
|
#include <QMessageBox>
|
|
#include <QClipboard>
|
|
#include <QPainter>
|
|
#include <QJsonArray>
|
|
#include <QVBoxLayout>
|
|
#include <QListWidget>
|
|
#include <QCheckBox>
|
|
#include <QDockWidget>
|
|
#include <QJsonObject>
|
|
#include <QTime>
|
|
#include <QStatusBar>
|
|
#include <QHBoxLayout>
|
|
#include <QLabel>
|
|
#include <QUrl>
|
|
#include <algorithm>
|
|
#ifdef BUILD_WITHOUT_KF
|
|
# include "ui/nokde/kautosavefile.h"
|
|
#else
|
|
# include <KAutoSaveFile>
|
|
#endif
|
|
|
|
/**
|
|
@brief QETDiagramEditor::QETDiagramEditor
|
|
Constructor
|
|
@param files : list of files to open
|
|
@param parent : parent widget
|
|
*/
|
|
QETDiagramEditor::QETDiagramEditor(const QStringList &files, QWidget *parent) :
|
|
QETMainWindow(parent),
|
|
m_row_column_actions_group (this),
|
|
m_selection_actions_group (this),
|
|
m_align_actions_group (this),
|
|
m_add_item_actions_group (this),
|
|
m_zoom_actions_group (this),
|
|
m_select_actions_group (this),
|
|
m_file_actions_group (this),
|
|
open_dialog_dir (QETApp::documentDir())
|
|
{
|
|
//Trivial property use to set the graphics handler size
|
|
setProperty("graphics_handler_size", 10);
|
|
|
|
activeSubWindowIndex = 0;
|
|
|
|
QSplitter *splitter_ = new QSplitter(this);
|
|
splitter_->setChildrenCollapsible(false);
|
|
splitter_->setOrientation(Qt::Vertical);
|
|
splitter_->addWidget(&m_workspace);
|
|
splitter_->addWidget(&m_search_and_replace_widget);
|
|
setCentralWidget(splitter_);
|
|
m_search_and_replace_widget.setEditor(this);
|
|
|
|
QList<int> s;
|
|
s << m_workspace.maximumHeight() << m_search_and_replace_widget.minimumSizeHint().height();
|
|
splitter_->setSizes(s); //Force the size of the search and replace widget, force have a good animation the first time he is showed
|
|
|
|
auto anim = new QWidgetAnimation(&m_search_and_replace_widget, Qt::Vertical, QWidgetAnimation::lastSize, 250);
|
|
anim->setObjectName("search and replace animator");
|
|
m_search_and_replace_widget.setHidden(true);
|
|
anim->setLastShowSize(m_search_and_replace_widget.minimumSizeHint().height());
|
|
|
|
//Set object name to be retrieved by the stylesheets
|
|
m_workspace.setBackground(QBrush(Qt::NoBrush));
|
|
m_workspace.setObjectName("mdiarea");
|
|
m_workspace.setTabsClosable(true);
|
|
|
|
//Set the signal mapper
|
|
connect(&windowMapper, &QSignalMapper::mappedObject, this, [this](QObject *object) { activateWidget(qobject_cast<QWidget *>(object)); });
|
|
|
|
setWindowTitle(tr("QElectroTech", "window title"));
|
|
setWindowIcon(QET::Icons::QETLogo);
|
|
statusBar() -> showMessage(tr("QElectroTech", "status bar message"));
|
|
|
|
setUpElementsPanel();
|
|
setUpElementsCollectionWidget();
|
|
setUpUndoStack();
|
|
setUpSelectionPropertiesEditor();
|
|
setUpAutonumberingWidget();
|
|
|
|
setUpActions();
|
|
setUpToolBar();
|
|
setUpMenu();
|
|
|
|
tabifyDockWidget(qdw_undo, qdw_pa);
|
|
|
|
//By default the windows is maximised
|
|
setMinimumSize(QSize(500, 350));
|
|
setWindowState(Qt::WindowMaximized);
|
|
|
|
connect(&m_workspace, &QMdiArea::subWindowActivated, this, &QETDiagramEditor::subWindowActivated);
|
|
connect(QApplication::clipboard(), &QClipboard::dataChanged, this, &QETDiagramEditor::slot_updatePasteAction);
|
|
|
|
readSettings(); // restoreGeometry before show()
|
|
show();
|
|
readSettingsState(); // restoreState() must be called after show() in Qt6
|
|
ToolbarSettings::applyTo(this);
|
|
#ifdef QET_HAS_SCRIPTING
|
|
setUpLiveIndicator();
|
|
setUpMacroRecorder();
|
|
//Live mode asks once per run, from the first window to open, and
|
|
//only once that window is on screen to anchor its warning.
|
|
QTimer::singleShot(0, this, [this]() { LiveServer::instance().askAndStart(this); });
|
|
//A toolbar saved as shown while there were scripts stays out of
|
|
//the way while there are none.
|
|
if (m_script_actions.isEmpty()) m_scripts_tool_bar->hide();
|
|
#endif
|
|
|
|
//If valid file path is given as arguments
|
|
uint opened_projects = 0;
|
|
if (files.count())
|
|
{
|
|
//So we open this files
|
|
foreach(QString file, files)
|
|
if (openAndAddProject(file))
|
|
++ opened_projects;
|
|
}
|
|
|
|
slot_updateActions();
|
|
}
|
|
|
|
/**
|
|
Destructeur
|
|
*/
|
|
QETDiagramEditor::~QETDiagramEditor()
|
|
{
|
|
}
|
|
|
|
/**
|
|
@brief QETDiagramEditor::setUpElementsPanel
|
|
Setup the element panel and element panel widget
|
|
*/
|
|
void QETDiagramEditor::setUpElementsPanel()
|
|
{
|
|
//Add the element panel as a QDockWidget
|
|
qdw_pa = new QDockWidget(tr("Projects", "dock title"), this);
|
|
|
|
qdw_pa -> setObjectName ("projects panel");
|
|
qdw_pa -> setAllowedAreas (Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea);
|
|
qdw_pa -> setFeatures (
|
|
QDockWidget::DockWidgetClosable
|
|
|QDockWidget::DockWidgetMovable
|
|
|QDockWidget::DockWidgetFloatable);
|
|
qdw_pa -> setMinimumWidth (160);
|
|
qdw_pa -> setWidget (pa = new ElementsPanelWidget(qdw_pa));
|
|
|
|
addDockWidget(Qt::LeftDockWidgetArea, qdw_pa);
|
|
|
|
connect(pa, &ElementsPanelWidget::requestForProject, this, qOverload<QETProject*>(&QETDiagramEditor::activateProject));
|
|
connect(pa, &ElementsPanelWidget::requestForProjectClosing, this, qOverload<QETProject*>(&QETDiagramEditor::closeProject));
|
|
connect(pa, &ElementsPanelWidget::requestForProjectPropertiesEdition, this, qOverload<QETProject*>(&QETDiagramEditor::editProjectProperties));
|
|
connect(pa, &ElementsPanelWidget::requestForNewDiagram, this, &QETDiagramEditor::addDiagramToProject);
|
|
connect(pa, &ElementsPanelWidget::requestForNewDiagramAt, this, &QETDiagramEditor::addDiagramToProjectAt);
|
|
connect(pa, &ElementsPanelWidget::requestForDiagramPropertiesEdition, this, qOverload<Diagram*>(&QETDiagramEditor::editDiagramProperties));
|
|
connect(pa, &ElementsPanelWidget::requestForDiagramsDeletion, this, &QETDiagramEditor::removeDiagrams);
|
|
connect(pa, &ElementsPanelWidget::requestForDiagramMoveUp, this, &QETDiagramEditor::moveDiagramUp);
|
|
connect(pa, &ElementsPanelWidget::requestForDiagramMoveDown, this, &QETDiagramEditor::moveDiagramDown);
|
|
connect(pa, &ElementsPanelWidget::requestForDiagramMoveUpTop, this, &QETDiagramEditor::moveDiagramUpTop);
|
|
connect(pa, &ElementsPanelWidget::requestForDiagramMoveUpx10, this, &QETDiagramEditor::moveDiagramUpx10);
|
|
connect(pa, &ElementsPanelWidget::requestForDiagramMoveDownx10, this, &QETDiagramEditor::moveDiagramDownx10);
|
|
connect(pa, &ElementsPanelWidget::requestForDiagramMoveUpx100, this, &QETDiagramEditor::moveDiagramUpx100);
|
|
connect(pa, &ElementsPanelWidget::requestForDiagramMoveDownx100, this, &QETDiagramEditor::moveDiagramDownx100);
|
|
}
|
|
|
|
/**
|
|
@brief QETDiagramEditor::setUpElementsCollectionWidget
|
|
Set up the dock widget of element collection
|
|
*/
|
|
void QETDiagramEditor::setUpElementsCollectionWidget()
|
|
{
|
|
m_qdw_elmt_collection = new QDockWidget(tr("Collections"), this);
|
|
m_qdw_elmt_collection->setObjectName("elements_collection_widget");
|
|
m_qdw_elmt_collection->setAllowedAreas(Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea);
|
|
m_qdw_elmt_collection->setFeatures(
|
|
QDockWidget::DockWidgetClosable
|
|
|QDockWidget::DockWidgetMovable
|
|
|QDockWidget::DockWidgetFloatable);
|
|
|
|
m_element_collection_widget = new ElementsCollectionWidget(m_qdw_elmt_collection);
|
|
m_qdw_elmt_collection->setWidget(m_element_collection_widget);
|
|
m_element_collection_widget->expandFirstItems();
|
|
|
|
//The widget does not know which view should receive the element -- it
|
|
//is also used by the picker popup, which has no editor ancestor -- so
|
|
//the host decides.
|
|
connect(m_element_collection_widget,
|
|
&ElementsCollectionWidget::insertElementRequested,
|
|
this, &QETDiagramEditor::insertElementFromCollection);
|
|
|
|
addDockWidget(Qt::RightDockWidgetArea, m_qdw_elmt_collection);
|
|
}
|
|
|
|
/**
|
|
@brief QETDiagramEditor::setUpUndoStack
|
|
Setup the undostack and undo stack widget
|
|
*/
|
|
void QETDiagramEditor::setUpUndoStack()
|
|
{
|
|
|
|
QUndoView *undo_view = new QUndoView(&undo_group, this);
|
|
|
|
undo_view -> setEmptyLabel (tr("No modification"));
|
|
undo_view -> setStatusTip (tr("Click an action to get back in the changes you made to your diagram", "Status tip"));
|
|
undo_view -> setWhatsThis (tr("This panel lists the various changes applied to the current sheet. Clicking a change gets the sheet back to its state right after it was applied.", "\"What's this\" tip"));
|
|
|
|
qdw_undo = new QDockWidget(tr("Undo", "dock title"), this);
|
|
qdw_undo -> setObjectName("diagram_undo");
|
|
|
|
qdw_undo -> setAllowedAreas(Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea);
|
|
qdw_undo -> setFeatures(
|
|
QDockWidget::DockWidgetClosable
|
|
|QDockWidget::DockWidgetMovable
|
|
|QDockWidget::DockWidgetFloatable);
|
|
qdw_undo -> setMinimumWidth(160);
|
|
qdw_undo -> setWidget(undo_view);
|
|
|
|
addDockWidget(Qt::LeftDockWidgetArea, qdw_undo);
|
|
}
|
|
|
|
/**
|
|
@brief QETDiagramEditor::setUpSelectionPropertiesEditor
|
|
Setup the dock for edit the current selection
|
|
*/
|
|
void QETDiagramEditor::setUpSelectionPropertiesEditor()
|
|
{
|
|
m_selection_properties_editor = new DiagramPropertiesEditorDockWidget(this);
|
|
m_selection_properties_editor -> setObjectName("diagram_properties_editor_dock_widget");
|
|
addDockWidget(Qt::RightDockWidgetArea, m_selection_properties_editor);
|
|
}
|
|
|
|
/**
|
|
@brief QETDiagramEditor::setUpAutonumberingWidget
|
|
Setup the dock for AutoNumbering Selection
|
|
*/
|
|
void QETDiagramEditor::setUpAutonumberingWidget()
|
|
{
|
|
m_autonumbering_dock = new AutoNumberingDockWidget(this);
|
|
m_autonumbering_dock -> setAllowedAreas(Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea);
|
|
m_autonumbering_dock -> setFeatures(
|
|
QDockWidget::DockWidgetClosable
|
|
|QDockWidget::DockWidgetMovable
|
|
|QDockWidget::DockWidgetFloatable);
|
|
addDockWidget(Qt::RightDockWidgetArea, m_autonumbering_dock);
|
|
}
|
|
|
|
/**
|
|
@brief QETDiagramEditor::setUpActions
|
|
Set up all Qaction
|
|
*/
|
|
void QETDiagramEditor::setUpActions()
|
|
{
|
|
//Export to another file type (jpeg, dxf etc...)
|
|
m_export_to_images = new QAction(QET::Icons::DocumentExport, tr("&Export"), this);
|
|
ShortcutManager::instance().registerAction(m_export_to_images, "diagrameditor.export_to_images", tr("Diagram editors"), Qt::CTRL | Qt::SHIFT | Qt::Key_X);
|
|
m_export_to_images->setStatusTip(tr("Exports the current sheet to another format", "status bar tip"));
|
|
connect(m_export_to_images, &QAction::triggered, [this]() {
|
|
ProjectView *current_project = currentProjectView();
|
|
if (current_project) {
|
|
current_project -> exportProject();
|
|
}
|
|
});
|
|
|
|
//Print
|
|
m_print = new QAction(QET::Icons::DocumentPrint, tr("Print"), this);
|
|
ShortcutManager::instance().registerAction(m_print, "diagrameditor.print", tr("Diagram editors"), QKeySequence::Print);
|
|
m_print->setStatusTip(tr("Prints one or more sheet of the current project", "status bar tip"));
|
|
connect(m_print, &QAction::triggered, [this]() {
|
|
auto project = currentProject();
|
|
if (project) {
|
|
ProjectPrintWindow::launchDialog(project, QPrinter::NativeFormat ,this);
|
|
}
|
|
});
|
|
|
|
//export to pdf
|
|
m_export_to_pdf = new QAction(QET::Icons::PDF, tr("Export in pdf"), this);
|
|
ShortcutManager::instance().registerAction(m_export_to_pdf, "diagrameditor.export_to_pdf", tr("Diagram editors"), QKeySequence());
|
|
m_export_to_pdf->setStatusTip(tr("Export one or more sheets of the current project", "status bar tip"));
|
|
connect(m_export_to_pdf, &QAction::triggered, [this] () {
|
|
auto project = currentProject();
|
|
if (project) {
|
|
ProjectPrintWindow::launchDialog(project, QPrinter::PdfFormat, this);
|
|
}
|
|
});
|
|
|
|
//Quit editor
|
|
m_quit_editor = new QAction(QET::Icons::ApplicationExit, tr("&Quit"), this);
|
|
ShortcutManager::instance().registerAction(m_quit_editor, "diagrameditor.quit", tr("Diagram editors"), Qt::CTRL | Qt::Key_Q);
|
|
m_quit_editor->setStatusTip(tr("Closes QElectroTech", "status bar tip"));
|
|
connect(m_quit_editor, &QAction::triggered, this, &QETDiagramEditor::close);
|
|
|
|
//Undo
|
|
undo = undo_group.createUndoAction(this, tr("Undo"));
|
|
undo->setIcon(QET::Icons::EditUndo);
|
|
ShortcutManager::instance().registerAction(undo, "diagrameditor.undo", tr("Diagram editors"), QKeySequence::Undo);
|
|
undo->setStatusTip(tr("Undoes the previous action", "status bar tip"));
|
|
//Redo
|
|
redo = undo_group.createRedoAction(this, tr("Redo"));
|
|
redo->setIcon(QET::Icons::EditRedo);
|
|
ShortcutManager::instance().registerAction(redo, "diagrameditor.redo", tr("Diagram editors"), QKeySequence::Redo);
|
|
redo->setStatusTip(tr("Restores the undone action", "status bar tip"));
|
|
|
|
//cut copy past
|
|
m_cut = new QAction(QET::Icons::EditCut, tr("Cu&t"), this);
|
|
m_copy = new QAction(QET::Icons::EditCopy, tr("&Copy"), this);
|
|
m_paste = new QAction(QET::Icons::EditPaste, tr("&Paste"), this);
|
|
m_paste_origin = new QAction(QET::Icons::EditPaste, tr("Paste at the original position"), this);
|
|
m_paste_element_info = new QAction(QET::Icons::EditPaste, tr("Paste element information"), this);
|
|
|
|
ShortcutManager::instance().registerAction(m_cut, "diagrameditor.cut", tr("Diagram editors"), QKeySequence::Cut);
|
|
ShortcutManager::instance().registerAction(m_copy, "diagrameditor.copy", tr("Diagram editors"), QKeySequence::Copy);
|
|
ShortcutManager::instance().registerAction(m_paste, "diagrameditor.paste", tr("Diagram editors"), QKeySequence::Paste);
|
|
ShortcutManager::instance().registerAction(m_paste_origin, "diagrameditor.paste_origin", tr("Diagram editors"), QKeySequence(Qt::CTRL | Qt::SHIFT | Qt::Key_V));
|
|
//No default shortcut: Ctrl+Shift+V is taken by m_paste_origin.
|
|
ShortcutManager::instance().registerAction(m_paste_element_info, "diagrameditor.paste_element_info", tr("Diagram editors"), QKeySequence());
|
|
|
|
m_cut -> setStatusTip(tr("Puts selected elements into the clipboard", "status bar tip"));
|
|
m_copy -> setStatusTip(tr("Copies selected elements", "status bar tip"));
|
|
m_paste -> setStatusTip(tr("Pastes elements from the clipboard into the sheet", "status bar tip"));
|
|
m_paste_origin -> setStatusTip(tr("Places the clipboard's items at their original position and moves the cursor to that point", "status bar tip"));
|
|
m_paste_element_info -> setStatusTip(tr("Copies the filled-in information of the element on the clipboard to the selected elements", "status bar tip"));
|
|
m_paste_element_info -> setEnabled(false);
|
|
|
|
connect(m_cut, &QAction::triggered, [this]() {
|
|
if (currentDiagramView())
|
|
currentDiagramView()->cut();
|
|
});
|
|
connect(m_copy, &QAction::triggered, [this]() {
|
|
if (currentDiagramView())
|
|
currentDiagramView()->copy();
|
|
});
|
|
|
|
//Both paste shortcuts share one starter; they differ only in the
|
|
//placement mode handed to DiagramEventAddPaste.
|
|
const auto start_paste = [this](DiagramEventAddPaste::PastePlacement placement) {
|
|
auto *dv = currentDiagramView();
|
|
if (!dv || !dv->diagram()) return;
|
|
|
|
//Paste as a placement rather than dropping the items straight
|
|
//down. Pasting in place put the copy exactly on top of the
|
|
//original, where it was easy to miss entirely; now it appears
|
|
//under the cursor and follows it until a click, Return, or Escape
|
|
//to cancel -- the same interaction as placing a new element.
|
|
//
|
|
//dv->lastMousePos() (an ordinary Qt mouse-move position), not
|
|
//QCursor::pos() (a global, OS-level cursor query): several
|
|
//window managers and compositors -- Wayland in particular --
|
|
//silently refuse that query, returning a stale or wrong
|
|
//position, which is exactly what made the pasted content land
|
|
//far from the cursor instead of under it.
|
|
const QPoint last_pos = dv->lastMousePos();
|
|
const QPoint view_pos = (last_pos.x() >= 0 && dv->viewport()->rect().contains(last_pos))
|
|
? last_pos
|
|
: dv->viewport()->rect().center();
|
|
const QPointF start_pos = dv->mapToScene(view_pos);
|
|
|
|
dv->diagram()->setEventInterface(
|
|
new DiagramEventAddPaste(dv->diagram(), start_pos, placement));
|
|
};
|
|
connect(m_paste, &QAction::triggered, [start_paste]() {
|
|
start_paste(DiagramEventAddPaste::UnderCursor);
|
|
});
|
|
connect(m_paste_origin, &QAction::triggered, [start_paste]() {
|
|
start_paste(DiagramEventAddPaste::AtOrigin);
|
|
});
|
|
connect(m_paste_element_info, &QAction::triggered,
|
|
this, &QETDiagramEditor::pasteElementInformations);
|
|
|
|
//Duplicate: copy the selection and place it at a configured,
|
|
//grid-step offset immediately -- no interactive follow-the-
|
|
//cursor step, unlike Ctrl+V above. That is deliberate (#991):
|
|
//the point of a duplicate shortcut is repeatable, unattended
|
|
//stamping (configure the offset once, then tap Ctrl+D to lay
|
|
//out a row), which an interactive placement would interrupt on
|
|
//every press.
|
|
m_duplicate = new QAction(QET::Icons::EditCopy, tr("&Duplicate"), this);
|
|
ShortcutManager::instance().registerAction(m_duplicate, "diagrameditor.duplicate", tr("Diagram editors"), Qt::CTRL | Qt::Key_D);
|
|
m_duplicate->setStatusTip(tr("Copies the selection, offset by the configured spacing", "status bar tip"));
|
|
connect(m_duplicate, &QAction::triggered, [this]() {
|
|
auto *dv = currentDiagramView();
|
|
if (!dv || !dv->diagram()) return;
|
|
|
|
//Ask the first time only -- every later press reuses whatever
|
|
//was confirmed then, so the shortcut can be tapped repeatedly
|
|
//without an interruption each time. m_configure_duplicate
|
|
//below is the deliberate way back into this dialog.
|
|
if (!DuplicateOffsetDialog::hasSavedStepOffset()) {
|
|
DuplicateOffsetDialog dialog(this);
|
|
if (dialog.exec() != QDialog::Accepted) return;
|
|
DuplicateOffsetDialog::saveStepOffset(dialog.stepOffset());
|
|
}
|
|
dv->duplicate(DuplicateOffsetDialog::savedStepOffset());
|
|
});
|
|
|
|
//Reopens the dialog above on demand, to change the spacing or
|
|
//direction a later Ctrl+D should use. Enabled unconditionally
|
|
//(see slot_updateComplexActions()): it only ever writes a
|
|
//setting, so it does not need a diagram open or anything
|
|
//selected the way m_duplicate itself does.
|
|
m_configure_duplicate = new QAction(tr("Configure duplication..."), this);
|
|
ShortcutManager::instance().registerAction(m_configure_duplicate, "diagrameditor.configure_duplicate", tr("Diagram editors"), QKeySequence());
|
|
m_configure_duplicate->setStatusTip(tr("Choose the spacing and direction used by Duplicate", "status bar tip"));
|
|
connect(m_configure_duplicate, &QAction::triggered, [this]() {
|
|
DuplicateOffsetDialog dialog(this);
|
|
if (dialog.exec() == QDialog::Accepted) {
|
|
DuplicateOffsetDialog::saveStepOffset(dialog.stepOffset());
|
|
}
|
|
});
|
|
|
|
//Reset conductor path
|
|
m_conductor_reset = new QAction(QET::Icons::ConductorSettings, tr("Reset conductors"), this);
|
|
ShortcutManager::instance().registerAction(m_conductor_reset, "diagrameditor.conductor_reset", tr("Diagram editors"), Qt::CTRL | Qt::Key_K);
|
|
m_conductor_reset->setStatusTip(tr("Reset the conductors path ignoring the user changes", "status bar tip"));
|
|
connect(m_conductor_reset, &QAction::triggered, [this]() {
|
|
if (DiagramView *dv = currentDiagramView())
|
|
dv->resetConductors();
|
|
});
|
|
|
|
//AutoConductor
|
|
m_auto_conductor = new QAction (QET::Icons::Autoconnect, tr("Automatic creation conductor (s)","Tool tip of auto conductor"), this);
|
|
m_auto_conductor->setStatusTip (tr("Using the automatic creation of conductor (s) when possible", "Status tip of auto conductor"));
|
|
m_auto_conductor->setCheckable (true);
|
|
connect(m_auto_conductor, &QAction::triggered, [this](bool ac) {
|
|
if (ProjectView *pv = currentProjectView())
|
|
pv->project()->setAutoConductor(ac);
|
|
});
|
|
//Registered with no default sequence on purpose. This is a
|
|
//setting some people toggle constantly and others never touch,
|
|
//so it earns a place in the Shortcuts page rather than a key of
|
|
//its own taken from the ones still free. Asked for on the forum
|
|
//(viewtopic.php?pid=23296): "est il possible dans les raccourcis
|
|
//d'ajouter un pour création automatique de conducteur ?"
|
|
ShortcutManager::instance().registerAction(m_auto_conductor, "diagrameditor.auto_conductor", tr("Diagram editors"), QKeySequence());
|
|
|
|
//AutoBreakConductor
|
|
m_auto_break_conductor = new QAction (QET::Icons::Conductor, tr("Automatic disconnection of conductor(s)","Tool tip of auto break conductor"), this);
|
|
ShortcutManager::instance().registerAction(m_auto_break_conductor, "diagrameditor.auto_break_conductor", tr("Diagram editors"), QKeySequence());
|
|
m_auto_break_conductor->setStatusTip (tr("Automatically cut existing conductors when placing an element", "Status tip of auto break conductor"));
|
|
m_auto_break_conductor->setCheckable (true);
|
|
{
|
|
QSettings settings;
|
|
m_auto_break_conductor->setChecked(settings.value("diagrameditor/auto_break_conductor", false).toBool());
|
|
}
|
|
connect(m_auto_break_conductor, &QAction::triggered, [this](bool abc) {
|
|
QSettings settings;
|
|
settings.setValue("diagrameditor/auto_break_conductor", abc);
|
|
if (ProjectView *pv = currentProjectView())
|
|
pv->project()->setAutoBreakConductor(abc);
|
|
});
|
|
|
|
//Diagram background color picker
|
|
m_background_color_button = new DiagramBgColorToolButton(this, this);
|
|
|
|
//Draw or not the background grid
|
|
m_draw_grid = new QAction ( QET::Icons::Grid, tr("Display the grid"), this);
|
|
ShortcutManager::instance().registerAction(m_draw_grid, "diagrameditor.draw_grid", tr("Diagram editors"), QKeySequence());
|
|
m_draw_grid->setStatusTip(tr("Display or hide the grid of sheet"));
|
|
QSettings settings;
|
|
m_draw_grid->setCheckable(true);
|
|
m_draw_grid->setChecked(settings.value("diagrameditor/grid_display_startup", true).toBool());
|
|
connect(m_draw_grid, &QAction::triggered, [this](bool checked) {
|
|
foreach (ProjectView *prjv, this->openedProjects())
|
|
foreach (Diagram *d, prjv->project()->diagrams()) {
|
|
d->setDisplayGrid(checked);
|
|
d->update();
|
|
}
|
|
});
|
|
|
|
//Snap step for dragged texts, as a fraction of the folio grid
|
|
m_text_grid_menu = new QMenu(tr("Text grid"), this);
|
|
m_text_grid_menu->setIcon(QET::Icons::Grid);
|
|
m_text_grid_menu->setToolTipsVisible(true);
|
|
m_text_grid_button = new QToolButton(this);
|
|
m_text_grid_button->setMenu(m_text_grid_menu);
|
|
m_text_grid_button->setPopupMode(QToolButton::InstantPopup);
|
|
m_text_grid_button->setToolButtonStyle(Qt::ToolButtonTextOnly);
|
|
m_text_grid_button->setToolTip(tr("Snap grid for texts moved with the mouse.\n"
|
|
"Hold Ctrl while moving to place freely."));
|
|
auto text_grid_group = new QActionGroup(this);
|
|
for (const qreal divisor : TextGrid::divisors)
|
|
{
|
|
QAction *action = m_text_grid_menu->addAction(
|
|
divisor > 0 ? TextGrid::ratioLabel(divisor) : tr("Disabled"));
|
|
action->setCheckable(true);
|
|
action->setData(divisor);
|
|
text_grid_group->addAction(action);
|
|
}
|
|
connect(text_grid_group, &QActionGroup::triggered, this, [](QAction *action) {
|
|
QSettings().setValue(TextGrid::settings_key, action->data());
|
|
emit QETApp::instance()->textGridChanged();
|
|
});
|
|
connect(QETApp::instance(), &QETApp::textGridChanged,
|
|
this, &QETDiagramEditor::updateTextGridButton);
|
|
updateTextGridButton();
|
|
|
|
// Draw or not the custom guides
|
|
m_draw_guides = new QAction ( QIcon::fromTheme("guides"), tr("View the guides"), this);
|
|
ShortcutManager::instance().registerAction(m_draw_guides, "diagrameditor.draw_guides", tr("Diagram editors"), QKeySequence());
|
|
m_draw_guides->setStatusTip(tr("Shows or hides the guides"));
|
|
m_draw_guides->setCheckable(true);
|
|
m_draw_guides->setChecked(settings.value("diagrameditor/guides_display_startup", false).toBool());
|
|
connect(m_draw_guides, &QAction::triggered, [this](bool checked) {
|
|
foreach (ProjectView *prjv, this->openedProjects())
|
|
foreach (Diagram *d, prjv->project()->diagrams()) {
|
|
d->setDisplayGuides(checked);
|
|
}
|
|
});
|
|
|
|
//Keep the column numbers and row letters of the folio in sight
|
|
m_cell_rulers = new QAction(tr("Keep headers visible"), this);
|
|
ShortcutManager::instance().registerAction(m_cell_rulers, "diagrameditor.cell_rulers", tr("Diagram editors"), QKeySequence());
|
|
m_cell_rulers->setStatusTip(tr("Keeps the sheet's column numbers and row letters visible at the edge of the view"));
|
|
m_cell_rulers->setCheckable(true);
|
|
m_cell_rulers->setChecked(settings.value("diagrameditor/cell_rulers", false).toBool());
|
|
connect(m_cell_rulers, &QAction::triggered, [this](bool checked) {
|
|
QSettings().setValue("diagrameditor/cell_rulers", checked);
|
|
foreach (ProjectView *prjv, this->openedProjects())
|
|
foreach (DiagramView *dv, prjv->diagram_views())
|
|
dv->setCellRulersShown(checked);
|
|
});
|
|
|
|
//Draw the limits of the folio columns and rows across the drawing
|
|
m_cell_lines = new QAction(tr("Show cell boundaries"), this);
|
|
ShortcutManager::instance().registerAction(m_cell_lines, "diagrameditor.cell_lines", tr("Diagram editors"), QKeySequence());
|
|
m_cell_lines->setStatusTip(tr("Draws the boundaries of the sheet's columns and rows on the diagram, on screen only"));
|
|
m_cell_lines->setCheckable(true);
|
|
m_cell_lines->setChecked(settings.value("diagrameditor/cell_lines", false).toBool());
|
|
connect(m_cell_lines, &QAction::triggered, [this](bool checked) {
|
|
QSettings().setValue("diagrameditor/cell_lines", checked);
|
|
foreach (ProjectView *prjv, this->openedProjects())
|
|
foreach (DiagramView *dv, prjv->diagram_views())
|
|
dv->setCellLinesShown(checked);
|
|
});
|
|
|
|
//Show or hide whole kinds of items on every folio (bugtracker #301)
|
|
m_shown_kinds_menu = new QMenu(tr("Show"), this);
|
|
const QList<QPair<ShownKinds::Kind, QString>> kinds {
|
|
{ShownKinds::SymbolTexts, tr("Element texts")},
|
|
{ShownKinds::WireNumbers, tr("Conductor texts")},
|
|
{ShownKinds::FreeTexts, tr("Text fields")},
|
|
{ShownKinds::Shapes, tr("Shapes")},
|
|
{ShownKinds::Pictures, tr("Pictures")},
|
|
{ShownKinds::Tables, tr("Tables")},
|
|
{ShownKinds::CrossReferences, tr("Cross-references")}};
|
|
for (const auto &kind : kinds)
|
|
{
|
|
QAction *action = m_shown_kinds_menu->addAction(kind.second);
|
|
action->setCheckable(true);
|
|
action->setData(int(kind.first));
|
|
connect(action, &QAction::triggered, this, [kind](bool checked) {
|
|
ShownKinds::setShown(kind.first, checked);
|
|
for (QETProject *project : QETApp::registeredProjects())
|
|
for (Diagram *diagram : project->diagrams())
|
|
ShownKinds::apply(diagram, kind.first);
|
|
emit QETApp::instance()->shownKindsChanged();
|
|
});
|
|
}
|
|
m_hidden_kinds_label = new QLabel(this);
|
|
m_hidden_kinds_label->setToolTip(tr("See Display > Show"));
|
|
statusBar()->addPermanentWidget(m_hidden_kinds_label);
|
|
connect(QETApp::instance(), &QETApp::shownKindsChanged,
|
|
this, &QETDiagramEditor::updateShownKinds);
|
|
updateShownKinds();
|
|
|
|
//Edit current diagram properties
|
|
m_edit_diagram_properties = new QAction(QET::Icons::DialogInformation, tr("Sheet properties"), this);
|
|
ShortcutManager::instance().registerAction(m_edit_diagram_properties, "diagrameditor.edit_diagram_properties", tr("Diagram editors"), Qt::CTRL | Qt::Key_L);
|
|
m_edit_diagram_properties -> setStatusTip(tr("Edits the properties of the sheet (size, title block information, conductor properties...)", "status bar tip"));
|
|
connect(m_edit_diagram_properties, &QAction::triggered, [this]() {
|
|
if (ProjectView *project_view = currentProjectView())
|
|
{
|
|
activateProject(project_view);
|
|
project_view->editCurrentDiagramProperties();
|
|
}
|
|
});
|
|
|
|
//Edit current project properties
|
|
m_project_edit_properties = new QAction(QET::Icons::ProjectProperties, tr("Project properties"), this);
|
|
ShortcutManager::instance().registerAction(m_project_edit_properties, "diagrameditor.project_edit_properties", tr("Diagram editors"), QKeySequence());
|
|
connect(m_project_edit_properties, &QAction::triggered, [this]() {
|
|
editProjectProperties(currentProjectView());
|
|
});
|
|
|
|
//Add new folio to current project
|
|
m_project_add_diagram = new QAction(QET::Icons::DiagramAdd, tr("Add a sheet"), this);
|
|
ShortcutManager::instance().registerAction(m_project_add_diagram, "diagrameditor.project_add_diagram", tr("Diagram editors"), Qt::CTRL | Qt::Key_T);
|
|
connect(m_project_add_diagram, &QAction::triggered, [this]() {
|
|
if (ProjectView *current_project = currentProjectView()) {
|
|
current_project->project()->addNewDiagram();
|
|
}
|
|
});
|
|
|
|
//Remove current folio from current project
|
|
m_remove_diagram_from_project = new QAction(QET::Icons::DiagramDelete, tr("Delete this sheet"), this);
|
|
ShortcutManager::instance().registerAction(m_remove_diagram_from_project, "diagrameditor.remove_diagram_from_project", tr("Diagram editors"), QKeySequence());
|
|
connect(m_remove_diagram_from_project, &QAction::triggered, this, &QETDiagramEditor::removeDiagramFromProject);
|
|
|
|
//Clean the current project
|
|
m_clean_project = new QAction(QET::Icons::EditClear, tr("Clean project"), this);
|
|
ShortcutManager::instance().registerAction(m_clean_project, "diagrameditor.clean_project", tr("Diagram editors"), QKeySequence());
|
|
connect(m_clean_project, &QAction::triggered, [this]() {
|
|
if (ProjectView *current_project = currentProjectView()) {
|
|
if (current_project->cleanProject()) {
|
|
pa -> reloadAndFilter();
|
|
}
|
|
}
|
|
});
|
|
|
|
//Export nomenclature to CSV
|
|
m_csv_export = new QAction(QET::Icons::DocumentSpreadsheet, tr("Export to CSV"), this);
|
|
ShortcutManager::instance().registerAction(m_csv_export, "diagrameditor.csv_export", tr("Diagram editors"), QKeySequence());
|
|
connect(m_csv_export, &QAction::triggered, [this]() {
|
|
BOMExportDialog bom(currentProjectView()->project(), this);
|
|
bom.exec();
|
|
});
|
|
|
|
//Add a nomenclature item
|
|
m_add_nomenclature = new QAction(QET::Icons::TableOfContent, tr("Add a nomenclature"), this);
|
|
ShortcutManager::instance().registerAction(m_add_nomenclature, "diagrameditor.add_nomenclature", tr("Diagram editors"), QKeySequence());
|
|
connect(m_add_nomenclature, &QAction::triggered, this, [this]() {
|
|
if(this->currentDiagramView()) {
|
|
QetGraphicsTableFactory::createAndAddNomenclature(this->currentDiagramView()->diagram());
|
|
}
|
|
});
|
|
|
|
//Add a summary item
|
|
m_add_summary = new QAction(QET::Icons::TableOfContent, tr("Add a summary"), this);
|
|
ShortcutManager::instance().registerAction(m_add_summary, "diagrameditor.add_summary", tr("Diagram editors"), QKeySequence());
|
|
connect(m_add_summary, &QAction::triggered, this, [this]() {
|
|
if(this->currentDiagramView()) {
|
|
QetGraphicsTableFactory::createAndAddSummary(this->currentDiagramView()->diagram());
|
|
}
|
|
});
|
|
|
|
m_terminal_strip_dialog = new QAction(QET::Icons::TerminalStrip, tr("Terminal block manager (DEV)"), this);
|
|
ShortcutManager::instance().registerAction(m_terminal_strip_dialog, "diagrameditor.terminal_strip_dialog", tr("Diagram editors"), QKeySequence());
|
|
connect(m_terminal_strip_dialog, &QAction::triggered, this, [this]()
|
|
{
|
|
if (auto project = this->currentProject())
|
|
{
|
|
TerminalStripEditorWindow::instance(project, this)->show();
|
|
}
|
|
});
|
|
|
|
//Launch the plugin of terminal generator
|
|
m_project_terminalBloc = new QAction(QET::Icons::TerminalStrip, tr("Launch the terminal block creation plugin"), this);
|
|
ShortcutManager::instance().registerAction(m_project_terminalBloc, "diagrameditor.terminal_strip_plugin", tr("Diagram editors"), QKeySequence());
|
|
connect(m_project_terminalBloc, &QAction::triggered, this, &QETDiagramEditor::generateTerminalBlock);
|
|
|
|
//Export conductor num to csv
|
|
m_project_export_conductor_num = new QAction(QET::Icons::DocumentSpreadsheet, tr("Export the list of names of wires"), this);
|
|
ShortcutManager::instance().registerAction(m_project_export_conductor_num, "diagrameditor.export_conductor_names", tr("Diagram editors"), QKeySequence());
|
|
connect(m_project_export_conductor_num, &QAction::triggered, [this]() {
|
|
QETProject *project = this->currentProject();
|
|
if (project)
|
|
{
|
|
ConductorNumExport wne(project, this);
|
|
wne.toCsv();
|
|
}
|
|
});
|
|
// Export wiring list to CSV
|
|
m_project_export_wiring_list = new QAction(QET::Icons::DocumentSpreadsheet, tr("Export the wiring diagram"), this);
|
|
ShortcutManager::instance().registerAction(m_project_export_wiring_list, "diagrameditor.export_wiring_list", tr("Diagram editors"), QKeySequence());
|
|
connect(m_project_export_wiring_list, &QAction::triggered, [this]() {
|
|
QETProject *project = this->currentProject();
|
|
if (project)
|
|
{
|
|
WiringListExport wle(project, this);
|
|
wle.toCsv();
|
|
}
|
|
});
|
|
|
|
// Show the wiring list read from the project database
|
|
m_project_wiring_list_view = new QAction(QET::Icons::DocumentSpreadsheet, tr("Wiring list (database)"), this);
|
|
ShortcutManager::instance().registerAction(m_project_wiring_list_view, "diagrameditor.wiring_list_view", tr("Diagram editors"), QKeySequence());
|
|
connect(m_project_wiring_list_view, &QAction::triggered, [this]() {
|
|
QETProject *project = this->currentProject();
|
|
if (project)
|
|
{
|
|
WiringListDialog dialog(project, this);
|
|
dialog.exec();
|
|
}
|
|
});
|
|
|
|
// Terminal Numbering
|
|
m_terminal_numbering = new QAction(QET::Icons::TerminalStrip, tr("Automatic numbering of terminals"), this);
|
|
ShortcutManager::instance().registerAction(m_terminal_numbering, "diagrameditor.terminal_numbering", tr("Diagram editors"), QKeySequence());
|
|
connect(m_terminal_numbering, &QAction::triggered, this, &QETDiagramEditor::slot_terminalNumbering);
|
|
|
|
// Reload element drawings from their current definition (bugtracker #802)
|
|
m_reload_element_drawings = new QAction(QET::Icons::ViewRefresh, tr("Reload element drawings"), this);
|
|
ShortcutManager::instance().registerAction(m_reload_element_drawings, "diagrameditor.reload_element_drawings", tr("Diagram editors"), QKeySequence());
|
|
m_reload_element_drawings->setStatusTip(
|
|
tr("Redraws each placed element from its current definition, without "
|
|
"having to close and reopen the project (action cannot be undone)"));
|
|
connect(m_reload_element_drawings, &QAction::triggered, this, &QETDiagramEditor::slot_reloadElementDrawings);
|
|
|
|
#ifdef QET_HAS_SCRIPTING
|
|
// Run a JavaScript macro against the current project (bugtracker #162).
|
|
m_run_script = new QAction(tr("Run a script..."), this);
|
|
ShortcutManager::instance().registerAction(m_run_script, "diagrameditor.run_script", tr("Diagram editors"), QKeySequence());
|
|
m_run_script->setStatusTip(
|
|
tr("Runs a JavaScript script on the current project (see qet.* in "
|
|
"the script for the available API)"));
|
|
connect(m_run_script, &QAction::triggered, this, &QETDiagramEditor::slot_runScript);
|
|
|
|
//Record what the user does, for an assistant to turn into a script
|
|
{
|
|
QPixmap dot(32, 32);
|
|
dot.fill(Qt::transparent);
|
|
QPainter painter(&dot);
|
|
painter.setRenderHint(QPainter::Antialiasing);
|
|
painter.setPen(Qt::NoPen);
|
|
painter.setBrush(QColor(0xd0, 0x30, 0x30));
|
|
painter.drawEllipse(QRectF(6, 6, 20, 20));
|
|
painter.end();
|
|
m_record_macro = new QAction(QIcon(dot), tr("Record a macro"), this);
|
|
}
|
|
m_record_macro->setCheckable(true);
|
|
m_record_macro->setStatusTip(tr("Records what you do on the project, for an AI assistant "
|
|
"to make a script of; click again to stop"));
|
|
connect(m_record_macro, &QAction::triggered, this, [this](bool checked) {
|
|
if (checked) {
|
|
if (!MacroRecorder::instance().start(this)) m_record_macro->setChecked(false);
|
|
} else {
|
|
MacroRecorder::instance().stop();
|
|
}
|
|
});
|
|
ShortcutManager::instance().registerAction(m_record_macro, "diagrameditor.record_macro",
|
|
tr("Diagram editors"), QKeySequence());
|
|
|
|
//Write, try, give an icon to and delete stored scripts
|
|
m_manage_scripts = new QAction(tr("Manage scripts…"), this);
|
|
m_manage_scripts->setStatusTip(tr("Write a script and make it a button with an icon"));
|
|
connect(m_manage_scripts, &QAction::triggered, this, [this]() {
|
|
if (!m_script_manager) {
|
|
m_script_manager = new ScriptManagerDialog(
|
|
[this](const QString &path, const QString &name) {
|
|
runStoredScript(path, name);
|
|
}, this);
|
|
}
|
|
m_script_manager->show();
|
|
m_script_manager->raise();
|
|
m_script_manager->activateWindow();
|
|
});
|
|
ShortcutManager::instance().registerAction(m_manage_scripts, "diagrameditor.manage_scripts",
|
|
tr("Diagram editors"), QKeySequence());
|
|
|
|
//Stored scripts are files in a folder, written by hand, by the
|
|
//script manager or by an assistant: open it to add one.
|
|
m_open_scripts_folder = new QAction(QET::Icons::FolderOpen, tr("Open the scripts folder"), this);
|
|
ShortcutManager::instance().registerAction(m_open_scripts_folder, "diagrameditor.open_scripts_folder", tr("Diagram editors"), QKeySequence());
|
|
m_open_scripts_folder->setStatusTip(
|
|
tr("Every .js file in this folder that starts with a // "
|
|
"==QETScript== header becomes a button"));
|
|
connect(m_open_scripts_folder, &QAction::triggered, this, []() {
|
|
QDir().mkpath(ScriptLibrary::folder());
|
|
QDesktopServices::openUrl(QUrl::fromLocalFile(ScriptLibrary::folder()));
|
|
});
|
|
#endif
|
|
|
|
#ifdef QET_EXPORT_PROJECT_DB
|
|
m_export_project_db = new QAction(QET::Icons::DocumentSpreadsheet, tr("Export the internal project database"), this);
|
|
ShortcutManager::instance().registerAction(m_export_project_db, "diagrameditor.export_project_db", tr("Diagram editors"), QKeySequence());
|
|
connect(m_export_project_db, &QAction::triggered, [this]() {
|
|
projectDataBase::exportDb(this->currentProject()->dataBase(), this);
|
|
});
|
|
#endif
|
|
|
|
//MDI view style
|
|
m_tabbed_view_mode = new QAction(tr("using tabs"), this);
|
|
ShortcutManager::instance().registerAction(m_tabbed_view_mode, "diagrameditor.tabbed_view_mode", tr("Diagram editors"), QKeySequence());
|
|
m_tabbed_view_mode->setStatusTip(tr("Shows the various opened projects in tabs", "status bar tip"));
|
|
m_tabbed_view_mode->setCheckable(true);
|
|
connect(m_tabbed_view_mode, &QAction::triggered, this, &QETDiagramEditor::setTabbedMode);
|
|
|
|
m_windowed_view_mode = new QAction(tr("using windows"), this);
|
|
ShortcutManager::instance().registerAction(m_windowed_view_mode, "diagrameditor.windowed_view_mode", tr("Diagram editors"), QKeySequence());
|
|
m_windowed_view_mode->setStatusTip(tr("Shows the various opened projects in windows", "status bar tip"));
|
|
m_windowed_view_mode->setCheckable(true);
|
|
connect(m_windowed_view_mode, &QAction::triggered, this, &QETDiagramEditor::setWindowedMode);
|
|
|
|
m_group_view_mode = new QActionGroup(this);
|
|
m_group_view_mode -> addAction(m_windowed_view_mode);
|
|
m_group_view_mode -> addAction(m_tabbed_view_mode);
|
|
m_group_view_mode -> setExclusive(true);
|
|
|
|
m_tile_window = new QAction(tr("&Tile"), this);
|
|
ShortcutManager::instance().registerAction(m_tile_window, "diagrameditor.tile_windows", tr("Diagram editors"), QKeySequence());
|
|
m_tile_window->setStatusTip(tr("Arranges windows in a tile pattern", "status bar tip"));
|
|
connect(m_tile_window, &QAction::triggered, &m_workspace, &QMdiArea::tileSubWindows);
|
|
|
|
m_cascade_window = new QAction(tr("&Cascade"), this);
|
|
ShortcutManager::instance().registerAction(m_cascade_window, "diagrameditor.cascade_windows", tr("Diagram editors"), QKeySequence());
|
|
m_cascade_window->setStatusTip(tr("Arranges windows in a cascade pattern", "status bar tip"));
|
|
connect(m_cascade_window, &QAction::triggered, &m_workspace, &QMdiArea::cascadeSubWindows);
|
|
|
|
//Switch selection/view mode
|
|
m_mode_selection = new QAction(QET::Icons::PartSelect, tr("Select"), this);
|
|
ShortcutManager::instance().registerAction(m_mode_selection, "diagrameditor.mode_selection", tr("Diagram editors"), QKeySequence());
|
|
m_mode_selection->setStatusTip(tr("Allows to select elements", "status bar tip"));
|
|
m_mode_selection->setCheckable(true);
|
|
m_mode_selection->setChecked(true);
|
|
connect(m_mode_selection, &QAction::triggered, [this]() {
|
|
if (ProjectView *pv = currentProjectView()) {
|
|
for (DiagramView *dv : pv->diagram_views()) {
|
|
dv->setSelectionMode();
|
|
}
|
|
}
|
|
});
|
|
|
|
m_mode_visualise = new QAction(QET::Icons::ViewMove, tr("Move"), this);
|
|
ShortcutManager::instance().registerAction(m_mode_visualise, "diagrameditor.mode_visualise", tr("Diagram editors"), QKeySequence());
|
|
m_mode_visualise->setStatusTip(tr("Allows to view the sheet without modifying it", "status bar tip"));
|
|
m_mode_visualise->setCheckable(true);
|
|
connect(m_mode_visualise, &QAction::triggered, [this]() {
|
|
if (ProjectView *pv = currentProjectView()) {
|
|
for(DiagramView *dv : pv->diagram_views()) {
|
|
dv->setVisualisationMode();
|
|
}
|
|
}
|
|
});
|
|
|
|
grp_visu_sel = new QActionGroup(this);
|
|
grp_visu_sel->addAction(m_mode_selection);
|
|
grp_visu_sel->addAction(m_mode_visualise);
|
|
grp_visu_sel->setExclusive(true);
|
|
|
|
//Navigate next/previous project
|
|
m_next_window = new QAction(tr("Next project"), this);
|
|
ShortcutManager::instance().registerAction(m_next_window, "diagrameditor.next_window", tr("Diagram editors"), QKeySequence::NextChild);
|
|
m_next_window->setStatusTip(tr("Activates the next project", "status bar tip"));
|
|
connect(m_next_window, &QAction::triggered, &m_workspace, &QMdiArea::activateNextSubWindow);
|
|
|
|
m_previous_window = new QAction(tr("Previous project"), this);
|
|
ShortcutManager::instance().registerAction(m_previous_window, "diagrameditor.previous_window", tr("Diagram editors"), QKeySequence::PreviousChild);
|
|
m_previous_window->setStatusTip(tr("Activates the previous project", "status bar tip"));
|
|
connect(m_previous_window, &QAction::triggered, &m_workspace, &QMdiArea::activatePreviousSubWindow);
|
|
|
|
//Files action
|
|
QAction *new_file = m_file_actions_group.addAction(QET::Icons::ProjectNew, tr("&New"));
|
|
QAction *open_file = m_file_actions_group.addAction(QET::Icons::DocumentOpen, tr("&Open"));
|
|
m_save_file = m_file_actions_group.addAction(QET::Icons::DocumentSave, tr("&Save"));
|
|
m_save_file_as = m_file_actions_group.addAction(QET::Icons::DocumentSaveAs, tr("Save as"));
|
|
m_close_file = m_file_actions_group.addAction(QET::Icons::ProjectClose, tr("&Close"));
|
|
|
|
ShortcutManager::instance().registerAction(new_file, "diagrameditor.new_file", tr("Diagram editors"), QKeySequence::New);
|
|
ShortcutManager::instance().registerAction(open_file, "diagrameditor.open_file", tr("Diagram editors"), QKeySequence::Open);
|
|
ShortcutManager::instance().registerAction(m_close_file, "diagrameditor.close_file", tr("Diagram editors"), QKeySequence::Close);
|
|
ShortcutManager::instance().registerAction(m_save_file, "diagrameditor.save_file", tr("Diagram editors"), QKeySequence::Save);
|
|
ShortcutManager::instance().registerAction(m_save_file_as, "diagrameditor.save_file_as", tr("Diagram editors"), Qt::CTRL | Qt::SHIFT | Qt::Key_S);
|
|
|
|
new_file ->setStatusTip( tr("Creates a new project", "status bar tip") );
|
|
open_file ->setStatusTip( tr("Opens an existing project", "status bar tip") );
|
|
m_close_file ->setStatusTip( tr("Closes the current project", "status bar tip") );
|
|
m_save_file ->setStatusTip( tr("Saves the current project and all its sheets", "status bar tip") );
|
|
m_save_file_as ->setStatusTip( tr("Saves the current project with a different file name", "status bar tip") );
|
|
|
|
connect(m_save_file_as, &QAction::triggered, this, &QETDiagramEditor::saveAs);
|
|
connect(m_save_file, &QAction::triggered, this, &QETDiagramEditor::save);
|
|
connect(new_file, &QAction::triggered, this, &QETDiagramEditor::newProject);
|
|
connect(open_file, &QAction::triggered, this, &QETDiagramEditor::openProject);
|
|
connect(m_close_file, &QAction::triggered, [this]() {
|
|
if (ProjectView *project_view = currentProjectView()) {
|
|
closeProject(project_view);
|
|
}
|
|
});
|
|
|
|
//Rows and Columns
|
|
QAction *add_column = m_row_column_actions_group.addAction( QET::Icons::EditTableInsertColumnRight, tr("Add a column") );
|
|
QAction *remove_column = m_row_column_actions_group.addAction( QET::Icons::EditTableDeleteColumn, tr("Remove a column") );
|
|
QAction *add_row = m_row_column_actions_group.addAction( QET::Icons::EditTableInsertRowUnder, tr("Add row", "Add row") );
|
|
QAction *remove_row = m_row_column_actions_group.addAction( QET::Icons::EditTableDeleteRow, tr("Remove a row","Remove row") );
|
|
|
|
add_column -> setStatusTip( tr("Add a column to the sheet", "status bar tip"));
|
|
remove_column -> setStatusTip( tr("Remove a column from the sheet", "status bar tip"));
|
|
add_row -> setStatusTip( tr("Expands the sheet height", "status bar tip"));
|
|
remove_row -> setStatusTip( tr("Shrinks the sheet height", "status bar tip"));
|
|
|
|
add_column ->setData("add_column");
|
|
remove_column->setData("remove_column");
|
|
add_row ->setData("add_row");
|
|
remove_row ->setData("remove_row");
|
|
|
|
for (QAction *action : m_row_column_actions_group.actions()) {
|
|
ShortcutManager::instance().registerAction(
|
|
action, "diagrameditor." + action->data().toString(),
|
|
tr("Diagram editors"), QKeySequence());
|
|
}
|
|
|
|
connect(&m_row_column_actions_group, &QActionGroup::triggered, this, &QETDiagramEditor::rowColumnGroupTriggered);
|
|
|
|
//Selections Actions (related to a selected item)
|
|
m_delete_selection = m_selection_actions_group.addAction( QET::Icons::EditDelete, tr("Delete") );
|
|
m_rotate_selection = m_selection_actions_group.addAction( QET::Icons::TransformRotate, tr("Rotate") );
|
|
m_rotate_group_selection = m_selection_actions_group.addAction( QET::Icons::TransformRotate, tr("Rotate the group") );
|
|
m_rotate_texts = m_selection_actions_group.addAction( QET::Icons::ObjectRotateRight, tr("Choose texts orientation") );
|
|
m_mirror_horizontal = m_selection_actions_group.addAction( QET::Icons::ImageFlipHorizontal, tr("Horizontal mirror") );
|
|
m_mirror_vertical = m_selection_actions_group.addAction( QET::Icons::ImageFlipVertical, tr("Vertical mirror") );
|
|
m_find_element = m_selection_actions_group.addAction( QET::Icons::ZoomDraw, tr("Find in the panel") );
|
|
m_edit_selection = m_selection_actions_group.addAction( QET::Icons::ElementEdit, tr("Edit the selected item") );
|
|
m_group_selected_texts = m_selection_actions_group.addAction( QET::Icons::textGroup, tr("Group selected texts"));
|
|
m_group_selection = m_selection_actions_group.addAction( tr("Group", "fr: Grouper") );
|
|
m_ungroup_selection = m_selection_actions_group.addAction( tr("Ungroup") );
|
|
|
|
ShortcutManager::instance().registerAction(m_delete_selection, "diagrameditor.delete_selection", tr("Diagram editors"), Qt::Key_Delete);
|
|
ShortcutManager::instance().registerAction(m_rotate_selection, "diagrameditor.rotate_selection", tr("Diagram editors"), Qt::Key_Space);
|
|
ShortcutManager::instance().registerAction(m_rotate_group_selection, "diagrameditor.rotate_group_selection", tr("Diagram editors"), Qt::SHIFT | Qt::Key_Space);
|
|
ShortcutManager::instance().registerAction(m_rotate_texts, "diagrameditor.rotate_texts", tr("Diagram editors"), Qt::CTRL | Qt::Key_Space);
|
|
//M and F, the keys of the same two actions in the element editor
|
|
ShortcutManager::instance().registerAction(m_mirror_horizontal, "diagrameditor.mirror_horizontal", tr("Diagram editors"), Qt::Key_M);
|
|
ShortcutManager::instance().registerAction(m_mirror_vertical, "diagrameditor.mirror_vertical", tr("Diagram editors"), Qt::Key_F);
|
|
ShortcutManager::instance().registerAction(m_find_element, "diagrameditor.find_element", tr("Diagram editors"), QKeySequence());
|
|
ShortcutManager::instance().registerAction(m_group_selected_texts, "diagrameditor.group_selected_texts", tr("Diagram editors"), QKeySequence());
|
|
ShortcutManager::instance().registerAction(m_edit_selection, "diagrameditor.edit_selection", tr("Diagram editors"), Qt::CTRL | Qt::Key_E);
|
|
|
|
//Re-enter placement mode with the element placed last. Bare A rather
|
|
//than Space: Space already rotates the pending element *inside*
|
|
//placement mode (diagrameventaddelement.cpp), and is taken three times
|
|
//over in this editor besides. A matches KiCad's add-symbol key and
|
|
//reads correctly in the source language ("Ajouter"). ShortcutManager
|
|
//makes it a default, not a commitment -- it appears in the Shortcuts
|
|
//preference page like every other binding.
|
|
m_insert_last_element = new QAction(QET::Icons::ElementNew,
|
|
tr("Insert the last element"), this);
|
|
m_insert_last_element->setStatusTip(
|
|
tr("Places the last inserted element again", "status bar tip"));
|
|
m_insert_last_element->setData("insert_last_element");
|
|
m_insert_last_element->setEnabled(false);
|
|
ShortcutManager::instance().registerAction(
|
|
m_insert_last_element, "diagrameditor.insert_last_element",
|
|
tr("Diagram editors"), Qt::Key_A);
|
|
connect(m_insert_last_element, &QAction::triggered,
|
|
this, &QETDiagramEditor::insertLastElement);
|
|
addAction(m_insert_last_element);
|
|
//Type to find and run any command, as SolidWorks' "Search Commands"
|
|
//and the command palette of many editors. Ctrl+Shift+P, the key those
|
|
//editors use, is taken by the autonumbering dock; M for "menu".
|
|
m_command_search = new QAction(tr("Search for a command…"), this);
|
|
m_command_search->setStatusTip(
|
|
tr("Type part of a command name and press Enter to launch it",
|
|
"status bar tip"));
|
|
ShortcutManager::instance().registerAction(
|
|
m_command_search, "diagrameditor.command_search",
|
|
tr("Diagram editors"), Qt::CTRL | Qt::SHIFT | Qt::Key_M);
|
|
connect(m_command_search, &QAction::triggered, this, [this]() {
|
|
if (!m_command_search_popup) {
|
|
m_command_search_popup = new CommandSearchPopup(this);
|
|
}
|
|
const QRect area = geometry();
|
|
m_command_search_popup->popUpAt(
|
|
area.contains(QCursor::pos()) ? QCursor::pos()
|
|
: area.center());
|
|
});
|
|
addAction(m_command_search);
|
|
|
|
//Cursor-anchored picker. Insert is unbound anywhere in the tree and
|
|
//reads correctly for the action, which keeps A free for the far more
|
|
//frequent "place the same symbol again".
|
|
m_show_element_picker = new QAction(QET::Icons::Add,
|
|
tr("Insert an element…"), this);
|
|
m_show_element_picker->setStatusTip(
|
|
tr("Opens the element picker at the cursor position",
|
|
"status bar tip"));
|
|
m_show_element_picker->setData("show_element_picker");
|
|
ShortcutManager::instance().registerAction(
|
|
m_show_element_picker, "diagrameditor.show_element_picker",
|
|
tr("Diagram editors"), Qt::Key_Insert);
|
|
connect(m_show_element_picker, &QAction::triggered,
|
|
this, &QETDiagramEditor::showElementPicker);
|
|
addAction(m_show_element_picker);
|
|
|
|
//The picker with a row of commands above it, chosen by what is
|
|
//selected -- the SolidWorks "S" shortcut bar. S is unbound in this
|
|
//editor.
|
|
m_show_shortcut_bar = new QAction(tr("Shortcut bar"), this);
|
|
m_show_shortcut_bar->setStatusTip(
|
|
tr("Opens, at the cursor position, the commands useful for the selection, and the element picker",
|
|
"status bar tip"));
|
|
ShortcutManager::instance().registerAction(
|
|
m_show_shortcut_bar, "diagrameditor.show_shortcut_bar",
|
|
tr("Diagram editors"), Qt::Key_S);
|
|
connect(m_show_shortcut_bar, &QAction::triggered,
|
|
this, &QETDiagramEditor::showShortcutBar);
|
|
addAction(m_show_shortcut_bar);
|
|
|
|
//Enter on the folio repeats it (DiagramView::keyPressEvent). That is
|
|
//handled by the view rather than bound here, so Enter keeps working
|
|
//in the search fields, the collection tree and every dialog.
|
|
m_repeat_last_command = new QAction(tr("Repeat the last command"), this);
|
|
m_repeat_last_command->setStatusTip(
|
|
tr("Relaunches the last drawing tool or element insertion (Enter on the sheet)",
|
|
"status bar tip"));
|
|
m_repeat_last_command->setEnabled(false);
|
|
ShortcutManager::instance().registerAction(
|
|
m_repeat_last_command, "diagrameditor.repeat_last_command",
|
|
tr("Diagram editors"), QKeySequence());
|
|
connect(m_repeat_last_command, &QAction::triggered,
|
|
this, &QETDiagramEditor::repeatLastCommand);
|
|
addAction(m_repeat_last_command);
|
|
|
|
m_delete_selection->setStatusTip( tr("Removes selected elements from the sheet", "status bar tip"));
|
|
m_rotate_selection->setStatusTip( tr("Rotate selected elements and texts", "status bar tip"));
|
|
m_rotate_group_selection->setStatusTip( tr("Rotates the selection as a group around its centre, rather than each element in place", "status bar tip"));
|
|
m_rotate_texts ->setStatusTip( tr("Rotate selected texts to a specific angle", "status bar tip"));
|
|
m_mirror_horizontal->setStatusTip( tr("Flips the selected elements from left to right", "status bar tip"));
|
|
m_mirror_vertical ->setStatusTip( tr("Flips the selected elements from top to bottom", "status bar tip"));
|
|
m_find_element ->setStatusTip( tr("Find the selected item in the panel", "status bar tip"));
|
|
|
|
m_delete_selection ->setData("delete_selection");
|
|
m_rotate_selection ->setData("rotate_selection");
|
|
m_rotate_group_selection->setData("rotate_group_selection");
|
|
m_rotate_texts ->setData("rotate_selected_text");
|
|
m_mirror_horizontal ->setData("mirror_horizontal");
|
|
m_mirror_vertical ->setData("mirror_vertical");
|
|
m_find_element ->setData("find_selected_element");
|
|
m_edit_selection ->setData("edit_selected_element");
|
|
m_group_selected_texts->setData("group_selected_texts");
|
|
m_group_selection ->setData("group_selection");
|
|
m_ungroup_selection ->setData("ungroup_selection");
|
|
|
|
//No default key: Ctrl+G is "jump to element". A user can bind one.
|
|
ShortcutManager::instance().registerAction(m_group_selection, "diagrameditor.group_selection", tr("Diagram editors"), QKeySequence());
|
|
ShortcutManager::instance().registerAction(m_ungroup_selection, "diagrameditor.ungroup_selection", tr("Diagram editors"), QKeySequence());
|
|
m_group_selection ->setStatusTip(tr("Groups the selected elements, texts, shapes and images: they are selected, moved and copied together", "status bar tip"));
|
|
m_ungroup_selection->setStatusTip(tr("Breaks up the selected groups", "status bar tip"));
|
|
|
|
connect(&m_selection_actions_group, &QActionGroup::triggered, this, &QETDiagramEditor::selectionGroupTriggered);
|
|
|
|
//Align actions. No default shortcut: they are reached from the
|
|
//Edit menu, the selection's context menu and the command search,
|
|
//and a user can bind one if they want.
|
|
QAction *snap_to_grid = m_align_actions_group.addAction(QET::Icons::SnapToGrid, tr("Align to grid"));
|
|
ShortcutManager::instance().registerAction(snap_to_grid, "diagrameditor.snap_selection_to_grid", tr("Diagram editors"), QKeySequence());
|
|
snap_to_grid->setStatusTip(tr("Puts the selected elements, images and texts back on the grid", "status bar tip"));
|
|
snap_to_grid->setData("snap_selection_to_grid");
|
|
|
|
const struct {
|
|
const char *id;
|
|
const QIcon &icon;
|
|
QString text;
|
|
QString tip;
|
|
} align_actions[] = {
|
|
{"align_left", QET::Icons::AlignLeft, tr("Align left"), tr("Aligns the left edges of the selected items", "status bar tip")},
|
|
{"align_hcenter", QET::Icons::AlignHCenter, tr("Centre horizontally"), tr("Aligns the selected items on one vertical line, by their origin point for elements", "status bar tip")},
|
|
{"align_right", QET::Icons::AlignRight, tr("Align right"), tr("Aligns the right edges of the selected items", "status bar tip")},
|
|
{"align_top", QET::Icons::AlignTop, tr("Align top"), tr("Aligns the top edges of the selected items", "status bar tip")},
|
|
{"align_vcenter", QET::Icons::AlignVCenter, tr("Centre vertically"), tr("Aligns the selected items on one horizontal line, by their origin point for elements", "status bar tip")},
|
|
{"align_bottom", QET::Icons::AlignBottom, tr("Align bottom"), tr("Aligns the bottom edges of the selected items", "status bar tip")}
|
|
};
|
|
for (const auto &a : align_actions)
|
|
{
|
|
QAction *action = m_align_actions_group.addAction(a.icon, a.text);
|
|
ShortcutManager::instance().registerAction(action, QStringLiteral("diagrameditor.") + QLatin1String(a.id), tr("Diagram editors"), QKeySequence());
|
|
action->setStatusTip(a.tip);
|
|
action->setData(QString::fromLatin1(a.id));
|
|
}
|
|
connect(&m_align_actions_group, &QActionGroup::triggered, this, &QETDiagramEditor::alignGroupTriggered);
|
|
|
|
//Select Action
|
|
QAction *select_all = m_select_actions_group.addAction( QET::Icons::EditSelectAll, tr("Select All") );
|
|
QAction *select_nothing = m_select_actions_group.addAction( QET::Icons::EditSelectNone, tr("Select none") );
|
|
QAction *select_invert = m_select_actions_group.addAction( QET::Icons::EditSelectInvert, tr("Invert selection") );
|
|
QAction *select_all_conductors = m_select_actions_group.addAction( QET::Icons::Conductor, tr("Select all conductors") );
|
|
QAction *select_all_text_fields = m_select_actions_group.addAction( QET::Icons::PartTextField, tr("Select all text fields") );
|
|
|
|
ShortcutManager::instance().registerAction(select_all, "diagrameditor.select_all", tr("Diagram editors"), QKeySequence::SelectAll);
|
|
ShortcutManager::instance().registerAction(select_nothing, "diagrameditor.select_nothing", tr("Diagram editors"), QKeySequence::Deselect);
|
|
ShortcutManager::instance().registerAction(select_invert, "diagrameditor.select_invert", tr("Diagram editors"), Qt::CTRL | Qt::Key_I);
|
|
//No default sequence for these two: they are menu actions, and the
|
|
//point of registering them is so a user can bind one if they want.
|
|
ShortcutManager::instance().registerAction(select_all_conductors, "diagrameditor.select_all_conductors", tr("Diagram editors"), QKeySequence());
|
|
ShortcutManager::instance().registerAction(select_all_text_fields, "diagrameditor.select_all_text_fields", tr("Diagram editors"), QKeySequence());
|
|
|
|
select_all ->setStatusTip( tr("Selects all elements on the sheet", "status bar tip") );
|
|
select_nothing->setStatusTip( tr("Deselect all elements on the sheet", "status bar tip") );
|
|
select_invert ->setStatusTip( tr("Inverts selection of elements", "status bar tip") );
|
|
select_all_conductors ->setStatusTip( tr("Selects all conductors on the sheet, deselects the rest", "status bar tip") );
|
|
select_all_text_fields->setStatusTip( tr("Selects all text fields on the sheet, deselects the rest", "status bar tip") );
|
|
|
|
select_all ->setData("select_all");
|
|
select_nothing->setData("deselect");
|
|
select_invert ->setData("invert_selection");
|
|
select_all_conductors ->setData("select_all_conductors");
|
|
select_all_text_fields->setData("select_all_text_fields");
|
|
|
|
connect(&m_select_actions_group, &QActionGroup::triggered, this, &QETDiagramEditor::selectGroupTriggered);
|
|
|
|
//Zoom actions
|
|
QAction *zoom_in = m_zoom_actions_group.addAction( QET::Icons::ZoomIn, tr("Zoom In"));
|
|
QAction *zoom_out = m_zoom_actions_group.addAction( QET::Icons::ZoomOut, tr("Zoom Out"));
|
|
QAction *zoom_content = m_zoom_actions_group.addAction( QET::Icons::ZoomDraw, tr("Zoom content"));
|
|
QAction *zoom_fit = m_zoom_actions_group.addAction( QET::Icons::ZoomFitBest, tr("Fit in view"));
|
|
QAction *zoom_reset = m_zoom_actions_group.addAction( QET::Icons::ZoomOriginal, tr("Reset zoom"));
|
|
QAction *center_on_cursor = m_zoom_actions_group.addAction(tr("Centre on the cursor"));
|
|
m_zoom_action_toolBar << zoom_content << zoom_fit << zoom_reset;
|
|
|
|
ShortcutManager::instance().registerAction(zoom_in, "diagrameditor.zoom_in", tr("Diagram editors"), QKeySequence::ZoomIn);
|
|
ShortcutManager::instance().registerAction(zoom_out, "diagrameditor.zoom_out", tr("Diagram editors"), QKeySequence::ZoomOut);
|
|
ShortcutManager::instance().registerAction(zoom_content, "diagrameditor.zoom_content", tr("Diagram editors"), Qt::CTRL | Qt::Key_8);
|
|
ShortcutManager::instance().registerAction(zoom_fit, "diagrameditor.zoom_fit", tr("Diagram editors"), Qt::CTRL | Qt::Key_9);
|
|
ShortcutManager::instance().registerAction(zoom_reset, "diagrameditor.zoom_reset", tr("Diagram editors"), Qt::CTRL | Qt::Key_0);
|
|
ShortcutManager::instance().registerAction(center_on_cursor, "diagrameditor.center_on_cursor", tr("Diagram editors"), QKeySequence());
|
|
|
|
zoom_in ->setStatusTip(tr("Expand the sheet", "status bar tip"));
|
|
zoom_out ->setStatusTip(tr("Shrinks the sheet", "status bar tip"));
|
|
zoom_content->setStatusTip(tr("Adjusts the zoom to display all the contents of sheet regardless of context"));
|
|
zoom_fit ->setStatusTip(tr("Adjusts the zoom on exactly the part of the sheet", "status bar tip"));
|
|
zoom_reset ->setStatusTip(tr("Restores default zoom level", "status bar tip"));
|
|
center_on_cursor->setStatusTip(tr("Centres the sheet on the point under the mouse cursor, without changing the zoom", "status bar tip"));
|
|
|
|
zoom_in ->setData("zoom_in");
|
|
zoom_out ->setData("zoom_out");
|
|
zoom_content->setData("zoom_content");
|
|
zoom_fit ->setData("zoom_fit");
|
|
zoom_reset ->setData("zoom_reset");
|
|
center_on_cursor->setData("center_on_cursor");
|
|
|
|
connect(&m_zoom_actions_group, &QActionGroup::triggered, this, &QETDiagramEditor::zoomGroupTriggered);
|
|
|
|
//Adding action (add text, image, shape...)
|
|
// Exclusive (the default) prevents the active action from ever being
|
|
// unchecked by clicking it again -- Qt only lets you switch to a
|
|
// different one. That's exactly why clicking an already-active
|
|
// tool's own icon never actually deactivated it: confirmed by
|
|
// logging every addItemGroupTriggered() call and finding
|
|
// isChecked()==true on *every* click, including the one meant to
|
|
// turn the tool off -- the click-handling code's own "was this an
|
|
// uncheck?" check was correct, its precondition just could never
|
|
// occur. ExclusiveOptional allows exactly one more transition:
|
|
// clicking the currently-checked action unchecks it, leaving none
|
|
// checked, which is required for "click the active tool to turn it
|
|
// off" to mean anything at the QAction level at all.
|
|
m_add_item_actions_group.setExclusionPolicy(QActionGroup::ExclusionPolicy::ExclusiveOptional);
|
|
QAction *add_text = m_add_item_actions_group.addAction(QET::Icons::PartTextField, tr("Add a textfield"));
|
|
QAction *add_image = m_add_item_actions_group.addAction(QET::Icons::adding_image, tr("Add a picture"));
|
|
#ifdef QET_HAS_QTPDF
|
|
QAction *add_pdf = m_add_item_actions_group.addAction(QET::Icons::adding_pdf, tr("Add a PDF"));
|
|
#endif
|
|
QAction *add_line = m_add_item_actions_group.addAction(QET::Icons::PartLine, tr("Add line", "Draw line"));
|
|
QAction *add_rectangle = m_add_item_actions_group.addAction(QET::Icons::PartRectangle, tr("Add a rectangle"));
|
|
QAction *add_ellipse = m_add_item_actions_group.addAction(QET::Icons::PartEllipse, tr("Add an ellipse"));
|
|
QAction *add_arc = m_add_item_actions_group.addAction(QET::Icons::PartArc, tr("Add an arc"));
|
|
QAction *add_polyline = m_add_item_actions_group.addAction(QET::Icons::PartPolygon, tr("Add a polygon"));
|
|
QAction *add_path = m_add_item_actions_group.addAction(QET::Icons::PartBezier, tr("Add a curve"));
|
|
QAction *add_fillet = m_add_item_actions_group.addAction(QET::Icons::DrawFillet, tr("Add a fillet"));
|
|
QAction *add_terminal_strip = m_add_item_actions_group.addAction(QET::Icons::TerminalStrip, tr("Add a terminal plan"));
|
|
QAction *add_generic_device = m_add_item_actions_group.addAction(QET::Icons::GenericDevice, tr("Add a generic device…"));
|
|
|
|
add_text ->setStatusTip(tr("Adds a text field to the current sheet"));
|
|
add_image ->setStatusTip(tr("Add an image to the current sheet"));
|
|
#ifdef QET_HAS_QTPDF
|
|
add_pdf ->setStatusTip(tr("Adds a PDF page to the current sheet"));
|
|
#endif
|
|
add_line ->setStatusTip(tr("Add a line to the current sheet"));
|
|
add_rectangle->setStatusTip(tr("Adds a rectangle on the current sheet"));
|
|
add_ellipse ->setStatusTip(tr("Add an ellipse to the current sheet"));
|
|
add_arc ->setStatusTip(tr("Adds an arc on the current sheet"));
|
|
add_polyline ->setStatusTip(tr("Adds a polyline to the current sheet"));
|
|
add_path ->setStatusTip(tr("Adds a Bézier curve to the current sheet"));
|
|
add_fillet ->setStatusTip(tr("Rounds the corner between two lines of the current sheet"));
|
|
add_terminal_strip->setStatusTip(tr("Adds a terminal plan to the current sheet"));
|
|
add_generic_device->setStatusTip(tr("Makes a box symbol with terminals on any side and places it on the current sheet"));
|
|
|
|
add_text ->setData(QStringLiteral("text"));
|
|
add_image ->setData(QStringLiteral("image"));
|
|
#ifdef QET_HAS_QTPDF
|
|
add_pdf ->setData(QStringLiteral("pdf"));
|
|
#endif
|
|
add_line ->setData(QStringLiteral("line"));
|
|
add_rectangle->setData(QStringLiteral("rectangle"));
|
|
add_ellipse ->setData(QStringLiteral("ellipse"));
|
|
add_arc ->setData(QStringLiteral("arc"));
|
|
add_polyline ->setData(QStringLiteral("polyline"));
|
|
add_path ->setData(QStringLiteral("path"));
|
|
add_fillet ->setData(QStringLiteral("fillet"));
|
|
add_terminal_strip->setData(QStringLiteral("terminal_strip"));
|
|
add_generic_device->setData(QStringLiteral("generic_device"));
|
|
|
|
add_text->setCheckable(true);
|
|
add_line->setCheckable(true);
|
|
add_rectangle->setCheckable(true);
|
|
add_ellipse->setCheckable(true);
|
|
add_arc->setCheckable(true);
|
|
add_polyline->setCheckable(true);
|
|
add_path->setCheckable(true);
|
|
add_fillet->setCheckable(true);
|
|
|
|
connect(&m_add_item_actions_group, &QActionGroup::triggered, this, &QETDiagramEditor::addItemGroupTriggered);
|
|
//No default key, but an id: they can then be found by the command
|
|
//search, bound in the Shortcuts page and placed on the shortcut bar,
|
|
//like every other command.
|
|
for (QAction *action : m_add_item_actions_group.actions()) {
|
|
ShortcutManager::instance().registerAction(
|
|
action, "diagrameditor.add_" + action->data().toString(),
|
|
tr("Diagram editors"), QKeySequence());
|
|
}
|
|
|
|
//Depth action
|
|
m_depth_action_group = QET::depthActionGroup(this);
|
|
m_depth_action_group->setDisabled(true);
|
|
|
|
connect(m_depth_action_group, &QActionGroup::triggered, [this](QAction *action) {
|
|
this->currentDiagramView()->diagram()->changeZValue(action->data().value<QET::DepthOption>());
|
|
});
|
|
|
|
m_find = new QAction(tr("Search / Replace"), this);
|
|
ShortcutManager::instance().registerAction(m_find, "diagrameditor.find", tr("Diagram editors"), QKeySequence::Find);
|
|
connect(m_find, &QAction::triggered, [this]()
|
|
{
|
|
if (auto animator = m_search_and_replace_widget.findChild<QWidgetAnimation *>("search and replace animator")) {
|
|
animator->setHidden(!m_search_and_replace_widget.isHidden());
|
|
} else {
|
|
this->m_search_and_replace_widget.setHidden(!m_search_and_replace_widget.isHidden());
|
|
}
|
|
});
|
|
|
|
m_jump_to_element = new QAction(tr("Navigate to an element"), this);
|
|
ShortcutManager::instance().registerAction(m_jump_to_element, "diagrameditor.jump_to_element", tr("Diagram editors"), Qt::CTRL | Qt::Key_G);
|
|
m_jump_to_element->setStatusTip(tr("Quickly search for and select an element in the sheet", "status bar tip"));
|
|
connect(m_jump_to_element, &QAction::triggered, [this]()
|
|
{
|
|
DiagramView *diagram_view = this->currentDiagramView();
|
|
if (!diagram_view || !diagram_view->diagram()) {
|
|
return;
|
|
}
|
|
JumpToElementDialog dialog(diagram_view->diagram(), this);
|
|
dialog.exec();
|
|
});
|
|
}
|
|
|
|
/**
|
|
@brief QETDiagramEditor::setUpToolBar
|
|
*/
|
|
void QETDiagramEditor::setUpToolBar()
|
|
{
|
|
main_tool_bar = new QToolBar(tr("Tools"), this);
|
|
main_tool_bar -> setObjectName("toolbar");
|
|
|
|
view_tool_bar = new QToolBar(tr("Display"), this);
|
|
view_tool_bar -> setObjectName("display");
|
|
|
|
diagram_tool_bar = new QToolBar(tr("Diagram"), this);
|
|
diagram_tool_bar -> setObjectName("diagram");
|
|
|
|
m_add_item_tool_bar = new QToolBar(tr("Add"), this);
|
|
m_add_item_tool_bar->setObjectName("adding");
|
|
|
|
m_depth_tool_bar = new QToolBar(tr("Depth", "toolbar title"));
|
|
m_depth_tool_bar->setObjectName("diagram_depth_toolbar");
|
|
|
|
//The toolbar buttons that are widgets, not commands. Each is held by
|
|
//an action of this window, so it survives being taken off a toolbar.
|
|
auto add_widget = [this](const QString &id, QWidget *widget) {
|
|
auto *action = new QWidgetAction(this);
|
|
action->setDefaultWidget(widget);
|
|
action->setText(DiagramToolbarSettings::widgetTitle(id));
|
|
m_toolbar_widgets.insert(id, action);
|
|
};
|
|
add_widget(QStringLiteral("widget:handler_size"), new DiagramEditorHandlerSizeWidget(this));
|
|
add_widget(QStringLiteral("widget:text_grid"), m_text_grid_button);
|
|
add_widget(QStringLiteral("widget:background_color"), m_background_color_button);
|
|
//Sits with the conductor actions it works alongside: it colours
|
|
//the selected conductors and sets the colour of the next one drawn.
|
|
m_conductor_color_button = new ConductorColorToolButton(this, this);
|
|
add_widget(QStringLiteral("widget:conductor_color"), m_conductor_color_button);
|
|
|
|
addToolBar(Qt::TopToolBarArea, main_tool_bar);
|
|
addToolBar(Qt::TopToolBarArea, view_tool_bar);
|
|
addToolBar(Qt::TopToolBarArea, diagram_tool_bar);
|
|
addToolBar(Qt::TopToolBarArea, m_add_item_tool_bar);
|
|
addToolBar(Qt::TopToolBarArea, m_depth_tool_bar);
|
|
|
|
DiagramToolbarSettings::markWindow(this);
|
|
rebuildToolBars();
|
|
|
|
m_scripts_tool_bar = new QToolBar(tr("Scripts", "toolbar title"), this);
|
|
m_scripts_tool_bar->setObjectName("scripts");
|
|
addToolBar(Qt::TopToolBarArea, m_scripts_tool_bar);
|
|
#ifndef QET_HAS_SCRIPTING
|
|
m_scripts_tool_bar->toggleViewAction()->setVisible(false);
|
|
m_scripts_tool_bar->hide();
|
|
#endif
|
|
}
|
|
|
|
/**
|
|
@brief QETDiagramEditor::rebuildToolBars
|
|
Fill every toolbar from DiagramToolbarSettings, adding the user's own
|
|
toolbars that this window does not have yet and removing those that
|
|
were deleted. Called when the window is built, when the settings
|
|
change, and when the stored scripts change (a script can be on any
|
|
toolbar).
|
|
*/
|
|
void QETDiagramEditor::rebuildToolBars()
|
|
{
|
|
const QList<DiagramToolbarSettings::Toolbar> custom = DiagramToolbarSettings::customToolbars();
|
|
QStringList custom_names;
|
|
for (const DiagramToolbarSettings::Toolbar &toolbar : custom) {
|
|
custom_names << toolbar.name;
|
|
}
|
|
for (QToolBar *toolbar : m_custom_tool_bars) {
|
|
if (!custom_names.contains(toolbar->objectName())) {
|
|
removeToolBar(toolbar);
|
|
toolbar->deleteLater();
|
|
}
|
|
}
|
|
m_custom_tool_bars.erase(std::remove_if(m_custom_tool_bars.begin(), m_custom_tool_bars.end(),
|
|
[&custom_names](QToolBar *toolbar) { return !custom_names.contains(toolbar->objectName()); }),
|
|
m_custom_tool_bars.end());
|
|
|
|
for (const DiagramToolbarSettings::Toolbar &toolbar : custom)
|
|
{
|
|
auto existing = std::find_if(m_custom_tool_bars.cbegin(), m_custom_tool_bars.cend(),
|
|
[&toolbar](QToolBar *t) { return t->objectName() == toolbar.name; });
|
|
if (existing != m_custom_tool_bars.cend()) {
|
|
(*existing)->setWindowTitle(toolbar.title);
|
|
continue;
|
|
}
|
|
auto *new_tool_bar = new QToolBar(toolbar.title, this);
|
|
new_tool_bar->setObjectName(toolbar.name);
|
|
new_tool_bar->setMovable(!ToolbarSettings::locked());
|
|
addToolBar(Qt::TopToolBarArea, new_tool_bar);
|
|
m_custom_tool_bars << new_tool_bar;
|
|
}
|
|
|
|
auto resolve = [this](const QString &id) -> QAction * {
|
|
if (DiagramToolbarSettings::isWidget(id)) {
|
|
return m_toolbar_widgets.value(id);
|
|
}
|
|
return ShortcutManager::instance().action(id, this);
|
|
};
|
|
const QList<QToolBar *> built_in {main_tool_bar, view_tool_bar, diagram_tool_bar,
|
|
m_add_item_tool_bar, m_depth_tool_bar};
|
|
for (QToolBar *toolbar : built_in + m_custom_tool_bars) {
|
|
DiagramToolbarSettings::fill(toolbar, DiagramToolbarSettings::ids(toolbar->objectName()), resolve);
|
|
}
|
|
}
|
|
|
|
/**
|
|
@brief QETDiagramEditor::setUpMenu
|
|
*/
|
|
void QETDiagramEditor::setUpMenu()
|
|
{
|
|
|
|
QMenu* menu_fichier = new QMenu(tr("&File"), this);
|
|
QMenu* menu_edition = new QMenu(tr("&Edit"), this);
|
|
QMenu* menu_project = new QMenu(tr("&Project"), this);
|
|
QMenu* menu_affichage = new QMenu(tr("Displ&ay"), this);
|
|
// QMenu *menu_outils = new QMenu(tr("O&utils"), this);
|
|
windows_menu = new QMenu(tr("Wi&ndows"), this);
|
|
|
|
insertMenu(settings_menu_, menu_fichier);
|
|
insertMenu(settings_menu_, menu_edition);
|
|
insertMenu(settings_menu_, menu_project);
|
|
insertMenu(settings_menu_, menu_affichage);
|
|
insertMenu(help_menu_, windows_menu);
|
|
|
|
// File menu
|
|
// Add the RecentFiles menu itself, not a copy of the actions it
|
|
// happened to hold at construction time. RecentFiles::buildMenu()
|
|
// clears that menu and creates fresh QActions every time a file is
|
|
// opened, so a snapshot taken here never gained an entry again and
|
|
// the list only looked right after a restart (bugtracker #97).
|
|
//
|
|
// The menu is an application-wide singleton and several editor
|
|
// windows may exist. QMenu::addMenu() adds the submenu's menuAction()
|
|
// rather than reparenting it, so every window shows the same live
|
|
// menu, which is the intent.
|
|
QMenu *recentfile = QETApp::projectsRecentFiles()->menu();
|
|
recentfile->setTitle(tr("&Latest files"));
|
|
recentfile->setIcon(QET::Icons::DocumentOpenRecent);
|
|
menu_fichier->addMenu(recentfile);
|
|
connect(QETApp::projectsRecentFiles(), &RecentFiles::fileOpeningRequested, this, &QETDiagramEditor::openRecentFile);
|
|
menu_fichier -> addActions(m_file_actions_group.actions());
|
|
menu_fichier -> addSeparator();
|
|
//menu_fichier -> addAction(import_diagram);
|
|
menu_fichier -> addAction(m_export_to_images);
|
|
menu_fichier -> addAction(m_export_to_pdf);
|
|
menu_fichier -> addAction(m_print);
|
|
menu_fichier -> addSeparator();
|
|
menu_fichier -> addAction(m_quit_editor);
|
|
|
|
// menu Edition
|
|
menu_edition -> addAction(undo);
|
|
menu_edition -> addAction(redo);
|
|
menu_edition -> addSeparator();
|
|
menu_edition -> addAction(m_cut);
|
|
menu_edition -> addAction(m_copy);
|
|
menu_edition -> addAction(m_paste);
|
|
menu_edition -> addAction(m_paste_origin);
|
|
menu_edition -> addAction(m_paste_element_info);
|
|
menu_edition -> addAction(m_duplicate);
|
|
menu_edition -> addAction(m_configure_duplicate);
|
|
menu_edition -> addSeparator();
|
|
menu_edition -> addAction(m_insert_last_element);
|
|
menu_edition -> addAction(m_show_element_picker);
|
|
//The same actions the "Ajouter" toolbar holds. They were toolbar-only,
|
|
//which left them unreachable for anyone working without a mouse: a
|
|
//toolbar button has no key, so text fields, images and every drawing
|
|
//shape simply could not be added. m_depth_action_group below has
|
|
//always been in both places; this brings these into line with it.
|
|
m_add_item_menu = menu_edition -> addMenu(tr("&Add"));
|
|
m_add_item_menu -> setIcon(QET::Icons::Add);
|
|
m_add_item_menu -> addActions(m_add_item_actions_group.actions());
|
|
menu_edition -> addSeparator();
|
|
//The menu had grown to over forty entries and no longer fitted on a
|
|
//laptop screen (issue #1336). Whole families now sit one level down,
|
|
//as Ajouter and Aligner already did; every action and its shortcut
|
|
//is unchanged.
|
|
QMenu *select_menu = menu_edition -> addMenu(QET::Icons::EditSelectAll, tr("Selection"));
|
|
select_menu -> addActions(m_select_actions_group.actions());
|
|
menu_edition -> addSeparator();
|
|
menu_edition -> addAction(m_delete_selection);
|
|
menu_edition -> addAction(m_rotate_selection);
|
|
menu_edition -> addAction(m_rotate_group_selection);
|
|
menu_edition -> addAction(m_rotate_texts);
|
|
menu_edition -> addAction(m_find_element);
|
|
menu_edition -> addAction(m_edit_selection);
|
|
QMenu *group_menu = menu_edition -> addMenu(QET::Icons::textGroup, tr("Group", "fr: Grouper"));
|
|
group_menu -> addAction(m_group_selection);
|
|
group_menu -> addAction(m_ungroup_selection);
|
|
group_menu -> addSeparator();
|
|
group_menu -> addAction(m_group_selected_texts);
|
|
m_align_menu = menu_edition -> addMenu(tr("Align"));
|
|
//Snap to grid, then the horizontal three, then the vertical three,
|
|
//in the order setUpActions() adds them
|
|
m_align_menu -> addAction(m_align_actions_group.actions().first());
|
|
m_align_menu -> addSeparator();
|
|
m_align_menu -> addActions(m_align_actions_group.actions().mid(1, 3));
|
|
m_align_menu -> addSeparator();
|
|
m_align_menu -> addActions(m_align_actions_group.actions().mid(4));
|
|
menu_edition -> addSeparator();
|
|
menu_edition -> addAction(m_conductor_reset);
|
|
menu_edition -> addSeparator();
|
|
menu_edition -> addAction(m_edit_diagram_properties);
|
|
//Shared with the folio's context menu (see
|
|
//DiagramView::contextMenuActions()).
|
|
m_row_column_menu = menu_edition -> addMenu(tr("Rows and columns"));
|
|
m_row_column_menu -> setIcon(QET::Icons::EditTableInsertColumnRight);
|
|
m_row_column_menu -> addActions(m_row_column_actions_group.actions());
|
|
QMenu *depth_menu = menu_edition -> addMenu(QET::Icons::BringForward, tr("Depth"));
|
|
depth_menu -> addActions(m_depth_action_group->actions());
|
|
menu_edition -> addSeparator();
|
|
menu_edition -> addAction(m_find);
|
|
menu_edition -> addAction(m_jump_to_element);
|
|
menu_edition -> addAction(m_command_search);
|
|
menu_edition -> addSeparator();
|
|
menu_edition -> addAction(m_show_shortcut_bar);
|
|
menu_edition -> addAction(m_repeat_last_command);
|
|
|
|
// menu Projet
|
|
menu_project -> addAction(m_project_edit_properties);
|
|
menu_project -> addAction(m_auto_conductor);
|
|
//Sits beside m_auto_conductor, the setting it pairs with. It was
|
|
//toolbar-only and so had no keyboard route at all.
|
|
menu_project -> addAction(m_auto_break_conductor);
|
|
menu_project -> addSeparator();
|
|
menu_project -> addAction(m_project_add_diagram);
|
|
menu_project -> addAction(m_remove_diagram_from_project);
|
|
menu_project -> addAction(m_clean_project);
|
|
menu_project -> addSeparator();
|
|
menu_project -> addAction(m_add_summary);
|
|
menu_project -> addAction(m_add_nomenclature);
|
|
menu_project -> addAction(m_csv_export);
|
|
menu_project -> addAction(m_project_export_conductor_num);
|
|
menu_project -> addAction(m_terminal_strip_dialog);
|
|
menu_project -> addAction(m_project_terminalBloc);
|
|
menu_project -> addAction(m_project_export_wiring_list);
|
|
menu_project -> addAction(m_project_wiring_list_view);
|
|
menu_project -> addAction(m_terminal_numbering);
|
|
menu_project -> addAction(m_reload_element_drawings);
|
|
#ifdef QET_HAS_SCRIPTING
|
|
m_scripts_menu = menu_project -> addMenu(tr("Scripts"));
|
|
rebuildScriptActions();
|
|
connect(&ScriptLibrary::instance(), &ScriptLibrary::changed,
|
|
this, &QETDiagramEditor::rebuildScriptActions);
|
|
AssistantInfo::watch();
|
|
#endif
|
|
#ifdef QET_EXPORT_PROJECT_DB
|
|
menu_project -> addSeparator();
|
|
menu_project -> addAction(m_export_project_db);
|
|
#endif
|
|
|
|
main_tool_bar -> toggleViewAction() -> setStatusTip(tr("Display or hide the main toolbar"));
|
|
view_tool_bar -> toggleViewAction() -> setStatusTip(tr("Display or hide the Display toolbar"));
|
|
diagram_tool_bar -> toggleViewAction() -> setStatusTip(tr("Display or hide the Diagram toolbar"));
|
|
qdw_pa -> toggleViewAction() -> setStatusTip(tr("Show or hide the elements panel"));
|
|
qdw_undo -> toggleViewAction() -> setStatusTip(tr("Display or hide the undo list"));
|
|
|
|
|
|
// menu Affichage
|
|
QMenu *projects_view_mode = menu_affichage -> addMenu(QET::Icons::ConfigureToolbars, tr("Display projects"));
|
|
projects_view_mode -> setTearOffEnabled(true);
|
|
projects_view_mode -> addAction(m_windowed_view_mode);
|
|
projects_view_mode -> addAction(m_tabbed_view_mode);
|
|
|
|
menu_affichage -> addSeparator();
|
|
menu_affichage -> addAction(m_mode_selection);
|
|
menu_affichage -> addAction(m_mode_visualise);
|
|
menu_affichage -> addSeparator();
|
|
menu_affichage -> addAction(m_draw_grid);
|
|
menu_affichage -> addMenu(m_text_grid_menu);
|
|
menu_affichage -> addAction(m_draw_guides);
|
|
menu_affichage -> addAction(m_cell_rulers);
|
|
menu_affichage -> addAction(m_cell_lines);
|
|
menu_affichage -> addMenu(m_shown_kinds_menu);
|
|
menu_affichage -> addMenu(m_background_color_button->menu());
|
|
menu_affichage -> addSeparator();
|
|
menu_affichage -> addActions(m_zoom_actions_group.actions());
|
|
|
|
// menu Fenetres
|
|
slot_updateWindowsMenu();
|
|
}
|
|
|
|
/**
|
|
Permet de quitter l'application lors de la fermeture de la fenetre principale
|
|
@param qce Le QCloseEvent correspondant a l'evenement de fermeture
|
|
*/
|
|
void QETDiagramEditor::closeEvent(QCloseEvent *qce)
|
|
{
|
|
// quitte directement s'il n'y a aucun projet ouvert
|
|
bool can_quit = true;
|
|
if (openedProjects().count()) {
|
|
// s'assure que la fenetre soit visible s'il y a des projets a fermer
|
|
if (!isVisible() || isMinimized()) {
|
|
if (isMaximized()) showMaximized();
|
|
else showNormal();
|
|
}
|
|
// sinon demande la permission de fermer chaque projet
|
|
foreach(ProjectView *project, openedProjects()) {
|
|
if (!closeProject(project)) {
|
|
can_quit = false;
|
|
qce -> ignore();
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (can_quit) {
|
|
writeSettings();
|
|
setAttribute(Qt::WA_DeleteOnClose);
|
|
qce -> accept();
|
|
}
|
|
}
|
|
|
|
/**
|
|
@brief QETDiagramEditor::event
|
|
Reimplemented to :
|
|
-Load elements collection when WindowActivate.
|
|
@param e
|
|
@return
|
|
*/
|
|
bool QETDiagramEditor::event(QEvent *e)
|
|
{
|
|
if (m_first_show && e->type() == QEvent::WindowActivate)
|
|
{
|
|
m_first_show = false;
|
|
QTimer::singleShot(250, m_element_collection_widget, &ElementsCollectionWidget::reload);
|
|
}
|
|
return(QETMainWindow::event(e));
|
|
}
|
|
|
|
/**
|
|
@brief QETDiagramEditor::save
|
|
Ask the current active project to save
|
|
*/
|
|
void QETDiagramEditor::save()
|
|
{
|
|
if (ProjectView *project_view = currentProjectView()) {
|
|
QETResult saved = project_view -> save();
|
|
|
|
if (saved.isOk()) {
|
|
//save_file -> setDisabled(true);
|
|
QETApp::projectsRecentFiles() -> fileWasOpened(project_view -> project() -> filePath());
|
|
|
|
QString title = (project_view -> project() -> title ());
|
|
if (title.isEmpty()) title = "QElectroTech ";
|
|
QString filePath = (project_view -> project() -> filePath ());
|
|
statusBar()-> showMessage(tr("Project %1 registered in the directory: %2.").arg(title).arg (filePath), 2000);
|
|
m_element_collection_widget->highlightUnusedElement();
|
|
}
|
|
else {
|
|
showError(saved);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
@brief QETDiagramEditor::saveAs
|
|
Ask the current active project to save as
|
|
*/
|
|
void QETDiagramEditor::saveAs()
|
|
{
|
|
if (ProjectView *project_view = currentProjectView()) {
|
|
QETResult save_file = project_view -> saveAs();
|
|
if (save_file.isOk()) {
|
|
QETApp::projectsRecentFiles() -> fileWasOpened(project_view -> project() -> filePath());
|
|
|
|
QString title = (project_view -> project() -> title ());
|
|
if (title.isEmpty()) title = "QElectroTech ";
|
|
QString filePath = (project_view -> project() -> filePath ());
|
|
statusBar()->showMessage(tr("Project %1 registered in the directory: %2.").arg(title).arg (filePath), 2000);
|
|
m_element_collection_widget->highlightUnusedElement();
|
|
}
|
|
else {
|
|
showError(save_file);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
@brief QETDiagramEditor::newProject
|
|
Create a new project with an empty diagram
|
|
@return
|
|
*/
|
|
bool QETDiagramEditor::newProject()
|
|
{
|
|
auto new_project = new QETProject(this);
|
|
|
|
// add new diagram
|
|
new_project -> addNewDiagram();
|
|
|
|
return addProject(new_project);
|
|
}
|
|
|
|
/**
|
|
Slot utilise pour ouvrir un fichier recent.
|
|
Transfere filepath au slot openAndAddDiagram seulement si cet editeur est
|
|
actif
|
|
@param filepath Fichier a ouvrir
|
|
@see openAndAddDiagram
|
|
*/
|
|
bool QETDiagramEditor::openRecentFile(const QString &filepath)
|
|
{
|
|
// small hack to prevent all diagram editors from trying to topen the required
|
|
// recent file at the same time
|
|
if (qApp -> activeWindow() != this) return(false);
|
|
return(openAndAddProject(filepath));
|
|
}
|
|
|
|
/**
|
|
Cette fonction demande un nom de fichier a ouvrir a l'utilisateur
|
|
@return true si l'ouverture a reussi, false sinon
|
|
*/
|
|
bool QETDiagramEditor::openProject()
|
|
{
|
|
// demande un chemin de fichier a ouvrir a l'utilisateur
|
|
QString filepath = QFileDialog::getOpenFileName(
|
|
this,
|
|
tr("Open a file"),
|
|
open_dialog_dir.absolutePath(),
|
|
tr("QElectroTech project(*.qet);;XML Files (*.xml);;All files (*)")
|
|
);
|
|
if (filepath.isEmpty()) return(false);
|
|
|
|
// retient le dossier contenant le dernier projet ouvert
|
|
open_dialog_dir = QDir(filepath);
|
|
|
|
// ouvre le fichier
|
|
return(openAndAddProject(filepath));
|
|
}
|
|
|
|
/**
|
|
Ferme un projet
|
|
@param project_view Projet a fermer
|
|
@return true si la fermeture du projet a reussi, false sinon
|
|
Note : cette methode renvoie true si project est nul
|
|
*/
|
|
bool QETDiagramEditor::closeProject(ProjectView *project_view)
|
|
{
|
|
if (project_view) {
|
|
activateProject(project_view);
|
|
if (QMdiSubWindow *sub_window = subWindowForWidget(project_view)){
|
|
return(sub_window -> close());
|
|
}
|
|
}
|
|
return(true);
|
|
}
|
|
|
|
/**
|
|
Ferme un projet
|
|
@param project projet a fermer
|
|
@return true si la fermeture du fichier a reussi, false sinon
|
|
Note : cette methode renvoie true si project est nul
|
|
*/
|
|
bool QETDiagramEditor::closeProject(QETProject *project)
|
|
{
|
|
if (ProjectView *project_view = findProject(project)) {
|
|
return(closeProject(project_view));
|
|
}
|
|
return(true);
|
|
}
|
|
|
|
/**
|
|
Ouvre un projet depuis un fichier et l'ajoute a cet editeur
|
|
@param filepath Chemin du projet a ouvrir
|
|
@param interactive true pour afficher des messages a l'utilisateur, false sinon
|
|
@return true si l'ouverture a reussi, false sinon
|
|
*/
|
|
bool QETDiagramEditor::openAndAddProject(
|
|
const QString &filepath,
|
|
bool interactive)
|
|
{
|
|
if (filepath.isEmpty()) return(false);
|
|
|
|
QFileInfo filepath_info(filepath);
|
|
|
|
//Check if project is not open in another editor
|
|
if (QETDiagramEditor *diagram_editor = QETApp::diagramEditorForFile(filepath))
|
|
{
|
|
if (diagram_editor == this)
|
|
{
|
|
if (ProjectView *project_view = viewForFile(filepath))
|
|
{
|
|
activateWidget(project_view);
|
|
show();
|
|
activateWindow();
|
|
}
|
|
return(false);
|
|
}
|
|
else
|
|
{
|
|
//Ask to the other editor to display the file
|
|
return(diagram_editor -> openAndAddProject(filepath));
|
|
}
|
|
}
|
|
|
|
// check the file exists
|
|
if (!filepath_info.exists())
|
|
{
|
|
if (interactive)
|
|
{
|
|
QET::QetMessageBox::critical(
|
|
this,
|
|
tr("Unable to open file", "message box title"),
|
|
QString(
|
|
tr("It appears the %1 file which you are trying to open does "
|
|
"not exist or does not exist anymore.")
|
|
).arg(filepath)
|
|
);
|
|
}
|
|
return(false);
|
|
}
|
|
|
|
//Check if file readable
|
|
if (!filepath_info.isReadable())
|
|
{
|
|
if (interactive) {
|
|
QET::QetMessageBox::critical(
|
|
this,
|
|
tr("Unable to open file", "message box title"),
|
|
tr("The file you try to open does not seem readable and can "
|
|
"not be opened. Please check the file permissions.")
|
|
);
|
|
}
|
|
return(false);
|
|
}
|
|
|
|
//Check if file is read only
|
|
if (!filepath_info.isWritable())
|
|
{
|
|
if (interactive) {
|
|
QET::QetMessageBox::warning(
|
|
this,
|
|
tr("Opening the file read-only", "message box title"),
|
|
tr("The project you try to open does not seem writable. It "
|
|
"will be opened read-only.")
|
|
);
|
|
}
|
|
}
|
|
|
|
//Create the project
|
|
DialogWaiting::instance(this);
|
|
|
|
//Per-project window for the font counters reported below; the folios
|
|
//(and with them the stored font descriptions) are built between here
|
|
//and the end of addProject(). RAII, because DialogWaiting pumps the
|
|
//event loop during the load: a nested openAndAddProject() gets its
|
|
//own window and this one resumes unharmed.
|
|
QETUtils::FontRestorationScope font_scope;
|
|
|
|
QETProject *project = new QETProject(filepath);
|
|
if (project -> state() != QETProject::Ok)
|
|
{
|
|
if (interactive && project -> state() != QETProject::FileOpenDiscard)
|
|
{
|
|
QET::QetMessageBox::warning(
|
|
this,
|
|
tr("Unable to open project", "message box title"),
|
|
QString(
|
|
tr(
|
|
"The file %1 does not appear to be a QElectroTech "
|
|
"project file. Thus it cannot be opened.",
|
|
"message box content"
|
|
)
|
|
).arg(filepath)
|
|
);
|
|
}
|
|
delete project;
|
|
DialogWaiting::dropInstance();
|
|
return(false);
|
|
}
|
|
|
|
QETApp::projectsRecentFiles() -> fileWasOpened(filepath);
|
|
addProject(project);
|
|
DialogWaiting::dropInstance();
|
|
|
|
//Report font descriptions which could not be read as-is (written by
|
|
//an incompatible Qt version or corrupted), so the user learns about
|
|
//it from somewhere else than the console. See issue #553.
|
|
const int salvaged_fonts = font_scope.salvaged();
|
|
const int unreadable_fonts = font_scope.unreadable();
|
|
if (salvaged_fonts || unreadable_fonts)
|
|
{
|
|
qInfo().nospace() << "Project font descriptions: "
|
|
<< salvaged_fonts << " salvaged from a foreign format, "
|
|
<< unreadable_fonts << " unreadable (default font applies)";
|
|
}
|
|
if (interactive && (salvaged_fonts || unreadable_fonts))
|
|
{
|
|
QStringList details;
|
|
if (salvaged_fonts) {
|
|
details << tr("%n font descriptions written in a foreign or "
|
|
"corrupted format have been restored. They will be "
|
|
"rewritten in a stable format the next time the "
|
|
"project is saved.",
|
|
"message box content",
|
|
salvaged_fonts);
|
|
}
|
|
if (unreadable_fonts) {
|
|
details << tr("%n font descriptions could not be read; the "
|
|
"default font will be used for these texts.",
|
|
"message box content",
|
|
unreadable_fonts);
|
|
}
|
|
QET::QetMessageBox::information(
|
|
this,
|
|
tr("Project fonts", "message box title"),
|
|
details.join("\n\n")
|
|
);
|
|
}
|
|
|
|
//Report wires left out of the load because a terminal they join
|
|
//was not found: they would otherwise vanish on the next save
|
|
//without the user ever being told.
|
|
QStringList lost_wires;
|
|
for (Diagram *diagram : project->diagrams()) {
|
|
for (const QString &wire : diagram->wiresNotReconnected()) {
|
|
lost_wires << tr("Sheet %1: %2").arg(diagram->folioIndex() + 1).arg(wire);
|
|
}
|
|
}
|
|
if (interactive && !lost_wires.isEmpty())
|
|
{
|
|
QMessageBox box(QMessageBox::Warning,
|
|
tr("Conductors not loaded", "message box title"),
|
|
tr("%n conductors could not be joined to their terminals and "
|
|
"were not loaded. The element's definition in the project "
|
|
"was probably replaced by another one with different "
|
|
"terminals.\n"
|
|
"\n"
|
|
"If you save the project, these conductors will disappear "
|
|
"from the file. Close it without saving to keep the file as "
|
|
"it is.",
|
|
"message box content",
|
|
lost_wires.size()),
|
|
QMessageBox::Ok,
|
|
this);
|
|
box.setDetailedText(lost_wires.join(QLatin1Char('\n')));
|
|
box.exec();
|
|
}
|
|
|
|
//Not when nobody is there to answer: an assistant opening a
|
|
//project in live mode would wait on a window it cannot see
|
|
BackupDialog backup_dialog(this);
|
|
if (interactive && backup_dialog.exec() == QDialog::Accepted)
|
|
{
|
|
QString backup_path = filepath_info.absolutePath() + QDir::separator() +
|
|
QDateTime::currentDateTime().toString("yyyy-MM-dd-hh-mm") + "_" +
|
|
filepath_info.fileName();
|
|
QFile::copy(filepath, backup_path);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
Ajoute un projetmoveDiagramUp(
|
|
@param project projet a ajouter
|
|
@param update_panel Whether the elements panel should be warned this
|
|
project has been added. Defaults to true.
|
|
*/
|
|
bool QETDiagramEditor::addProject(QETProject *project, bool update_panel)
|
|
{
|
|
// enregistre le projet
|
|
QETApp::registerProject(project);
|
|
|
|
// cree un ProjectView pour visualiser le projet
|
|
ProjectView *project_view = new ProjectView(project);
|
|
addProjectView(project_view);
|
|
|
|
undo_group.addStack(project -> undoStack());
|
|
|
|
connect(project, &QETProject::projectModified, this, [this](QETProject *modified_project, bool) {
|
|
if (modified_project == currentProject()) {
|
|
updateWindowModifiedState();
|
|
}
|
|
});
|
|
|
|
m_element_collection_widget->addProject(project);
|
|
|
|
// met a jour le panel d'elements
|
|
if (update_panel) {
|
|
pa -> elementsPanel().projectWasOpened(project);
|
|
if (currentDiagramView() != nullptr)
|
|
m_autonumbering_dock->setProject(project, project_view);
|
|
}
|
|
|
|
return(true);
|
|
}
|
|
|
|
/**
|
|
@return la liste des projets ouverts dans cette fenetre
|
|
*/
|
|
QList<ProjectView *> QETDiagramEditor::openedProjects() const
|
|
{
|
|
QList<ProjectView *> result;
|
|
QList<QMdiSubWindow *> window_list(m_workspace.subWindowList());
|
|
foreach(QMdiSubWindow *window, window_list) {
|
|
if (ProjectView *project_view = qobject_cast<ProjectView *>(window -> widget())) {
|
|
result << project_view;
|
|
}
|
|
}
|
|
return(result);
|
|
}
|
|
|
|
/**
|
|
@return Le projet actuellement edite (= qui a le focus dans l'interface
|
|
MDI) ou 0 s'il n'y en a pas
|
|
*/
|
|
ProjectView *QETDiagramEditor::currentProjectView() const
|
|
{
|
|
QMdiSubWindow *current_window = m_workspace.activeSubWindow();
|
|
if (!current_window) return(nullptr);
|
|
|
|
QWidget *current_widget = current_window -> widget();
|
|
if (!current_widget) return(nullptr);
|
|
|
|
if (ProjectView *project_view = qobject_cast<ProjectView *>(current_widget)) {
|
|
return(project_view);
|
|
}
|
|
return(nullptr);
|
|
}
|
|
|
|
/**
|
|
@brief QETDiagramEditor::templateSaved
|
|
List a template saved from a folio in this editor's templates tab.
|
|
@param location : the saved .qetmak file
|
|
*/
|
|
void QETDiagramEditor::templateSaved(const ElementsLocation &location)
|
|
{
|
|
if (m_element_collection_widget)
|
|
m_element_collection_widget->addTemplate(location);
|
|
}
|
|
|
|
/**
|
|
@brief QETDiagramEditor::currentProject
|
|
@return the current edited project.
|
|
This function can return nullptr.
|
|
*/
|
|
QETProject *QETDiagramEditor::currentProject() const
|
|
{
|
|
ProjectView *view = currentProjectView();
|
|
if (view) {
|
|
return view->project();
|
|
}
|
|
else {
|
|
return nullptr;
|
|
}
|
|
}
|
|
|
|
/**
|
|
@return Le schema actuellement edite (= l'onglet ouvert dans le projet
|
|
courant) ou 0 s'il n'y en a pas
|
|
*/
|
|
DiagramView *QETDiagramEditor::currentDiagramView() const
|
|
{
|
|
if (ProjectView *project_view = currentProjectView()) {
|
|
return(project_view -> currentDiagram());
|
|
}
|
|
return(nullptr);
|
|
}
|
|
|
|
/**
|
|
@return the selected element in the current diagram view, or 0 if:
|
|
* no diagram is being viewed in this editor.
|
|
* no element is selected
|
|
* more than one element is selected
|
|
*/
|
|
Element *QETDiagramEditor::currentElement() const
|
|
{
|
|
DiagramView *dv = currentDiagramView();
|
|
if (!dv)
|
|
return(nullptr);
|
|
|
|
QList<Element *> selected_elements = DiagramContent(dv->diagram()).m_elements;
|
|
if (selected_elements.count() != 1)
|
|
return(nullptr);
|
|
|
|
return(selected_elements.first());
|
|
}
|
|
|
|
/**
|
|
Cette methode permet de retrouver le projet contenant un schema donne.
|
|
@param diagram_view Schema dont il faut retrouver
|
|
@return la vue sur le projet contenant ce schema ou 0 s'il n'y en a pas
|
|
*/
|
|
ProjectView *QETDiagramEditor::findProject(DiagramView *diagram_view) const
|
|
{
|
|
foreach(ProjectView *project_view, openedProjects()) {
|
|
if (project_view -> diagram_views().contains(diagram_view)) {
|
|
return(project_view);
|
|
}
|
|
}
|
|
return(nullptr);
|
|
}
|
|
|
|
/**
|
|
Cette methode permet de retrouver le projet contenant un schema donne.
|
|
@param diagram Schema dont il faut retrouver
|
|
@return la vue sur le projet contenant ce schema ou 0 s'il n'y en a pas
|
|
*/
|
|
ProjectView *QETDiagramEditor::findProject(Diagram *diagram) const
|
|
{
|
|
foreach(ProjectView *project_view, openedProjects()) {
|
|
foreach(DiagramView *diagram_view, project_view -> diagram_views()) {
|
|
if (diagram_view -> diagram() == diagram) {
|
|
return(project_view);
|
|
}
|
|
}
|
|
}
|
|
return(nullptr);
|
|
}
|
|
|
|
/**
|
|
@param project Projet dont il faut trouver la vue
|
|
@return la vue du projet passe en parametre
|
|
*/
|
|
ProjectView *QETDiagramEditor::findProject(QETProject *project) const
|
|
{
|
|
foreach(ProjectView *opened_project, openedProjects()) {
|
|
if (opened_project -> project() == project) {
|
|
return(opened_project);
|
|
}
|
|
}
|
|
return(nullptr);
|
|
}
|
|
|
|
/**
|
|
@param filepath Chemin de fichier d'un projet
|
|
@return le ProjectView correspondant au chemin passe en parametre, ou 0 si
|
|
celui-ci n'a pas ete trouve
|
|
*/
|
|
ProjectView *QETDiagramEditor::findProject(const QString &filepath) const
|
|
{
|
|
foreach(ProjectView *opened_project, openedProjects()) {
|
|
if (QETProject *project = opened_project -> project()) {
|
|
if (project -> filePath() == filepath) {
|
|
return(opened_project);
|
|
}
|
|
}
|
|
}
|
|
return(nullptr);
|
|
}
|
|
|
|
/**
|
|
@param widget Widget a rechercher dans la zone MDI
|
|
@return La sous-fenetre accueillant le widget passe en parametre, ou 0 si
|
|
celui-ci n'a pas ete trouve.
|
|
*/
|
|
QMdiSubWindow *QETDiagramEditor::subWindowForWidget(QWidget *widget) const
|
|
{
|
|
foreach(QMdiSubWindow *sub_window, m_workspace.subWindowList()) {
|
|
if (sub_window -> widget() == widget) {
|
|
return(sub_window);
|
|
}
|
|
}
|
|
return(nullptr);
|
|
}
|
|
|
|
/**
|
|
@param widget Widget a activer
|
|
*/
|
|
void QETDiagramEditor::activateWidget(QWidget *widget) {
|
|
QMdiSubWindow *sub_window = subWindowForWidget(widget);
|
|
if (sub_window) {
|
|
m_workspace.setActiveSubWindow(sub_window);
|
|
}
|
|
}
|
|
|
|
void QETDiagramEditor::zoomGroupTriggered(QAction *action)
|
|
{
|
|
QString value = action->data().toString();
|
|
DiagramView *dv = currentDiagramView();
|
|
|
|
if (!dv || value.isEmpty()) return;
|
|
|
|
if (value == "zoom_in")
|
|
dv->zoom(1.15);
|
|
else if (value == "zoom_out")
|
|
dv->zoom(0.85);
|
|
else if (value == "zoom_content")
|
|
dv->zoomContent();
|
|
else if (value == "zoom_fit")
|
|
dv->zoomFit();
|
|
else if (value == "zoom_reset")
|
|
dv->zoomReset();
|
|
else if (value == "center_on_cursor")
|
|
dv->centerOnCursor();
|
|
}
|
|
|
|
/**
|
|
@brief QETDiagramEditor::selectGroupTriggered
|
|
This slot is called when selection need to change.
|
|
@param action : Action that describes what to do.
|
|
*/
|
|
void QETDiagramEditor::selectGroupTriggered(QAction *action)
|
|
{
|
|
if (!currentDiagramView() || !currentDiagramView()->diagram())
|
|
return;
|
|
|
|
auto value = action->data().toString();
|
|
if (value.isEmpty())
|
|
return;
|
|
|
|
auto diagram = currentDiagramView()->diagram();
|
|
|
|
if (value == "select_all")
|
|
diagram->selectAll();
|
|
else if (value == "deselect")
|
|
diagram->deselectAll();
|
|
else if (value == "invert_selection")
|
|
diagram->invertSelection();
|
|
else if (value == "select_all_conductors")
|
|
diagram->selectAllConductors();
|
|
else if (value == "select_all_text_fields")
|
|
diagram->selectAllTextFields();
|
|
}
|
|
|
|
/**
|
|
@brief QETDiagramEditor::addItemGroupTriggered
|
|
This slot is called when an item must be added to the current diagram,
|
|
this slot use the DVEventInterface to add item
|
|
@param action : Action that describe the item to add.
|
|
*/
|
|
void QETDiagramEditor::addItemGroupTriggered(QAction *action)
|
|
{
|
|
QString value = action->data().toString();
|
|
|
|
if (Q_UNLIKELY (!currentDiagramView() || !currentDiagramView()->diagram() || value.isEmpty())) return;
|
|
|
|
Diagram *d = currentDiagramView()->diagram();
|
|
|
|
// This action group allows deselecting the currently-active tool by
|
|
// clicking its own icon again, not just switching between tools --
|
|
// that click still fires this slot (QActionGroup::triggered fires on
|
|
// every click in the group, checked-state-changing or not), so
|
|
// without this check it would unconditionally construct *another*
|
|
// instance of the same tool and reactivate it, leaving the tool
|
|
// fully active in the canvas while its own toolbar button shows
|
|
// unchecked -- exactly backwards from what the click was for.
|
|
// Scoped to checkable actions specifically: image/pdf/terminal_strip
|
|
// are one-shot actions (pick a file, open a dialog) with no
|
|
// persistent "active tool" state at all, and aren't even checkable
|
|
// -- isChecked() on them is always false, so without this guard
|
|
// they'd hit the branch above on every single click and never
|
|
// reach their own handling below.
|
|
if (action->isCheckable() && !action->isChecked())
|
|
{
|
|
d->clearEventInterface();
|
|
return;
|
|
}
|
|
|
|
setLastCommand(action);
|
|
|
|
DiagramEventInterface *diagram_event = nullptr;
|
|
|
|
if (value == "line")
|
|
diagram_event = new DiagramEventAddShape (d, QetShapeItem::Line);
|
|
else if (value == "rectangle")
|
|
diagram_event = new DiagramEventAddShape (d, QetShapeItem::Rectangle);
|
|
else if (value == "ellipse")
|
|
diagram_event = new DiagramEventAddShape (d, QetShapeItem::Ellipse);
|
|
else if (value == "arc")
|
|
diagram_event = new DiagramEventAddShape (d, QetShapeItem::Ellipse, true);
|
|
else if (value == "polyline")
|
|
diagram_event = new DiagramEventAddShape (d, QetShapeItem::Polygon);
|
|
else if (value == "path")
|
|
{
|
|
diagram_event = new DiagramEventAddPath (d);
|
|
}
|
|
else if (value == "fillet")
|
|
diagram_event = new DiagramEventFillet (d);
|
|
else if (value == "image")
|
|
{
|
|
DiagramEventAddImage *deai = new DiagramEventAddImage(d);
|
|
if (deai->isNull())
|
|
{
|
|
delete deai;
|
|
return;
|
|
}
|
|
else
|
|
diagram_event = deai;
|
|
}
|
|
#ifdef QET_HAS_QTPDF
|
|
else if (value == "pdf")
|
|
{
|
|
DiagramEventAddPdf *deap = new DiagramEventAddPdf(d);
|
|
if (deap->isNull())
|
|
{
|
|
delete deap;
|
|
return;
|
|
}
|
|
else
|
|
diagram_event = deap;
|
|
}
|
|
#endif
|
|
else if (value == "text")
|
|
{
|
|
diagram_event = new DiagramEventAddText(d);
|
|
}
|
|
else if (value == QLatin1String("generic_device"))
|
|
{
|
|
currentDiagramView()->addGenericDevice();
|
|
}
|
|
else if (value == QLatin1String("terminal_strip"))
|
|
{
|
|
const auto diagram_view{currentDiagramView()};
|
|
if (diagram_view)
|
|
{
|
|
AddTerminalStripItemDialog::openDialog(diagram_view->diagram(), this);
|
|
}
|
|
}
|
|
|
|
if (diagram_event)
|
|
{
|
|
d->setEventInterface(diagram_event);
|
|
connect(diagram_event, &DiagramEventInterface::destroyed, [action]() {action->setChecked(false);});
|
|
// Defensive: on this style/theme, the toolbar button bound to an
|
|
// exclusive-group action doesn't reliably repaint its checked
|
|
// appearance after the group briefly had *no* action checked at
|
|
// all (the gap between unchecking one tool and checking one
|
|
// again, even the same one) -- confirmed by tracing a real
|
|
// recording frame by frame: the status bar correctly showed this
|
|
// tool's "before first click" hint (which can only appear from
|
|
// inside a freshly-constructed tool's own constructor, so the
|
|
// action's checked state and the tool's activation were both
|
|
// genuinely correct) while the button itself stayed visually
|
|
// unchecked for a sustained period. Forcing an explicit repaint
|
|
// here makes the button's appearance match its actual state
|
|
// regardless of whether Qt's own change notification fired
|
|
// correctly.
|
|
|
|
if (DiagramView *dv = currentDiagramView())
|
|
dv->setFocus(); // so the view (and the active tool) actually receives Escape etc
|
|
|
|
if (QWidget *button = m_add_item_tool_bar->widgetForAction(action))
|
|
button->update();
|
|
}
|
|
}
|
|
|
|
/**
|
|
@brief QETDiagramEditor::selectionGroupTriggered
|
|
This slot is called when an action should be made on the current selection
|
|
@param action : Action that describe the action to do.
|
|
*/
|
|
void QETDiagramEditor::selectionGroupTriggered(QAction *action)
|
|
{
|
|
QString value = action->data().toString();
|
|
DiagramView *dv = currentDiagramView();
|
|
Diagram *diagram = dv->diagram();
|
|
DiagramContent dc(diagram);
|
|
|
|
if (!dv || value.isEmpty()) return;
|
|
|
|
if (value == "delete_selection")
|
|
{
|
|
if (DeleteQGraphicsItemCommand::hasNonDeletableTerminal(dc)) {
|
|
QET::QetMessageBox::information(this,
|
|
tr("Unable to delete the terminal"),
|
|
tr("The deletion cannot be performed because the selection contains one or more terminals "
|
|
"that are bridged and/or belong to a multi-level terminal.\n"
|
|
"Unbridge and/or remove the levels from the affected terminals so that they can be "
|
|
"deleted"));
|
|
} else {
|
|
diagram->clearSelection();
|
|
diagram->undoStack().push(new DeleteQGraphicsItemCommand(diagram, dc));
|
|
dv->adjustSceneRect();
|
|
}
|
|
}
|
|
else if (value == "rotate_selection")
|
|
{
|
|
//A selection that is exactly one whole group turns as one piece,
|
|
//as "Pivoter le groupe" does, rather than each member in place
|
|
//(discussion #1070). Wires follow their symbols either way.
|
|
QList<QGraphicsItem *> members;
|
|
for (QGraphicsItem *item : diagram->selectedItems()) {
|
|
if (item->type() != Conductor::Type) {
|
|
members << item;
|
|
}
|
|
}
|
|
const bool whole_group = !ItemGroups::soleWholeGroup(members).isNull();
|
|
RotateSelectionCommand *c = new RotateSelectionCommand(diagram, 90, nullptr, whole_group);
|
|
if(c->isValid())
|
|
diagram->undoStack().push(c);
|
|
}
|
|
else if (value == "mirror_horizontal" || value == "mirror_vertical")
|
|
{
|
|
auto *c = new MirrorSelectionCommand(
|
|
diagram,
|
|
value == "mirror_horizontal" ? Qt::Horizontal : Qt::Vertical);
|
|
if (c->isValid())
|
|
diagram->undoStack().push(c);
|
|
else
|
|
delete c;
|
|
}
|
|
else if (value == "rotate_group_selection")
|
|
{
|
|
RotateSelectionCommand *c = new RotateSelectionCommand(diagram, 90, nullptr, true);
|
|
if(c->isValid())
|
|
diagram->undoStack().push(c);
|
|
}
|
|
else if (value == "rotate_selected_text")
|
|
{
|
|
//Ask for the angle first, then build the command: the command
|
|
//itself no longer opens a dialog. Guarding on the selection keeps
|
|
//the previous behaviour of showing no dialog when there is
|
|
//nothing to rotate.
|
|
if (RotateTextsCommand::hasSelectedTexts(diagram))
|
|
{
|
|
qreal rotation = RotateTextsCommand::currentRotation(diagram);
|
|
if (RotateTextsCommand::askRotation(rotation))
|
|
diagram->undoStack().push(new RotateTextsCommand(diagram, rotation));
|
|
}
|
|
}
|
|
else if (value == "find_selected_element" && currentElement())
|
|
findElementInPanel(currentElement()->location());
|
|
else if (value == "edit_selected_element")
|
|
dv->editSelection();
|
|
else if (value == "group_selection" || value == "ungroup_selection")
|
|
{
|
|
GroupItemsCommand *command = value == "group_selection"
|
|
? GroupItemsCommand::group(diagram)
|
|
: GroupItemsCommand::ungroup(diagram);
|
|
if (command) {
|
|
diagram->undoStack().push(command);
|
|
}
|
|
}
|
|
else if (value == "group_selected_texts")
|
|
{
|
|
QList<DynamicElementTextItem *> deti_list = dc.m_element_texts.values();
|
|
if(deti_list.size() <= 1)
|
|
return;
|
|
|
|
diagram->undoStack().push(new AddTextsGroupCommand(deti_list.first()->parentElement(), tr("Group"), deti_list));
|
|
}
|
|
}
|
|
|
|
/**
|
|
@brief QETDiagramEditor::alignGroupTriggered
|
|
Run the align action @a action on the selection of the current diagram,
|
|
and say in the status bar what it did, including when there was nothing
|
|
to do or when locked items were left in place.
|
|
@param action
|
|
*/
|
|
void QETDiagramEditor::alignGroupTriggered(QAction *action)
|
|
{
|
|
DiagramView *dv = currentDiagramView();
|
|
if (!dv)
|
|
return;
|
|
|
|
static const QHash<QString, AlignSelectionCommand::Mode> modes {
|
|
{QStringLiteral("snap_selection_to_grid"), AlignSelectionCommand::SnapToGrid},
|
|
{QStringLiteral("align_left"), AlignSelectionCommand::AlignLeft},
|
|
{QStringLiteral("align_hcenter"), AlignSelectionCommand::AlignHCenter},
|
|
{QStringLiteral("align_right"), AlignSelectionCommand::AlignRight},
|
|
{QStringLiteral("align_top"), AlignSelectionCommand::AlignTop},
|
|
{QStringLiteral("align_vcenter"), AlignSelectionCommand::AlignVCenter},
|
|
{QStringLiteral("align_bottom"), AlignSelectionCommand::AlignBottom}
|
|
};
|
|
const QString id = action->data().toString();
|
|
if (!modes.contains(id))
|
|
return;
|
|
const AlignSelectionCommand::Mode mode = modes.value(id);
|
|
|
|
Diagram *diagram = dv->diagram();
|
|
auto *command = new AlignSelectionCommand(diagram, mode);
|
|
const int locked = command->lockedCount();
|
|
|
|
QString message;
|
|
if (command->isValid())
|
|
{
|
|
message = mode == AlignSelectionCommand::SnapToGrid
|
|
? tr("%n items put back on the grid", "", command->movedCount())
|
|
: tr("%n items aligned", "", command->movedCount());
|
|
diagram->undoStack().push(command);
|
|
}
|
|
else
|
|
{
|
|
if (mode == AlignSelectionCommand::SnapToGrid)
|
|
message = tr("The selection is already on the grid");
|
|
else if (command->itemCount() < 2)
|
|
message = tr("Select at least two unlocked elements, images, texts, shapes or groups");
|
|
else
|
|
message = tr("The selection is already aligned, to the nearest grid step");
|
|
delete command;
|
|
}
|
|
if (locked)
|
|
message += QLatin1Char(' ') + tr("(%n locked objects left in place)", "", locked);
|
|
|
|
//Queued, not shown at once: when the action comes from a menu or the
|
|
//command search, closing it queues events that would clear a message
|
|
//shown right now.
|
|
QTimer::singleShot(0, this, [this, message]() {
|
|
statusBar()->showMessage(message, 5000);
|
|
});
|
|
}
|
|
|
|
void QETDiagramEditor::rowColumnGroupTriggered(QAction *action)
|
|
{
|
|
QString value = action->data().toString();
|
|
DiagramView *dv = currentDiagramView();
|
|
|
|
if (!dv || value.isEmpty() || dv->diagram()->isReadOnly()) return;
|
|
|
|
Diagram *d = dv->diagram();
|
|
BorderProperties old_bp = d->border_and_titleblock.exportBorder();
|
|
BorderProperties new_bp = d->border_and_titleblock.exportBorder();
|
|
|
|
if (value == "add_column")
|
|
new_bp.columns_count += 1;
|
|
else if (value == "remove_column")
|
|
new_bp.columns_count -= 1;
|
|
else if (value == "add_row")
|
|
new_bp.rows_count += 1;
|
|
else if (value == "remove_row")
|
|
new_bp.rows_count -= 1;
|
|
|
|
d->undoStack().push(new ChangeBorderCommand(d, old_bp, new_bp));
|
|
}
|
|
|
|
/**
|
|
@brief QETDiagramEditor::slot_updateActions
|
|
Manage actions
|
|
*/
|
|
void QETDiagramEditor::slot_updateActions()
|
|
{
|
|
DiagramView *dv = currentDiagramView();
|
|
ProjectView *pv = currentProjectView();
|
|
|
|
bool opened_project = pv;
|
|
bool opened_diagram = dv;
|
|
bool editable_project = (pv && !pv -> project() -> isReadOnly());
|
|
|
|
m_close_file-> setEnabled(opened_project);
|
|
m_save_file-> setEnabled(opened_project);
|
|
m_save_file_as-> setEnabled(opened_project);
|
|
m_rotate_texts-> setEnabled(editable_project);
|
|
m_export_to_images-> setEnabled(opened_diagram);
|
|
m_print-> setEnabled(opened_diagram);
|
|
m_export_to_pdf-> setEnabled(opened_diagram);
|
|
m_edit_diagram_properties-> setEnabled(opened_diagram);
|
|
m_zoom_actions_group. setEnabled(opened_diagram);
|
|
m_select_actions_group. setEnabled(opened_diagram);
|
|
m_add_item_actions_group. setEnabled(editable_project);
|
|
m_insert_last_element-> setEnabled(opened_diagram && editable_project && !m_last_inserted_element.isNull());
|
|
m_show_element_picker-> setEnabled(opened_diagram && editable_project);
|
|
m_show_shortcut_bar-> setEnabled(opened_diagram && editable_project);
|
|
m_repeat_last_command-> setEnabled(opened_diagram && editable_project
|
|
&& m_last_command && m_last_command->isEnabled());
|
|
m_row_column_actions_group. setEnabled(editable_project);
|
|
m_background_color_button-> setEnabled(opened_diagram);
|
|
m_draw_grid-> setEnabled(opened_diagram);
|
|
m_draw_guides-> setEnabled(opened_diagram);
|
|
m_cell_rulers-> setEnabled(opened_diagram);
|
|
m_cell_lines-> setEnabled(opened_diagram);
|
|
|
|
//Project menu
|
|
m_project_edit_properties -> setEnabled(opened_project);
|
|
m_project_add_diagram -> setEnabled(editable_project);
|
|
m_remove_diagram_from_project -> setEnabled(editable_project);
|
|
m_clean_project -> setEnabled(editable_project);
|
|
m_add_summary -> setEnabled(editable_project);
|
|
m_add_nomenclature -> setEnabled(editable_project);
|
|
m_csv_export -> setEnabled(editable_project);
|
|
m_project_export_conductor_num-> setEnabled(opened_project);
|
|
m_terminal_strip_dialog -> setEnabled(editable_project);
|
|
m_project_export_wiring_list -> setEnabled(opened_project);
|
|
m_project_wiring_list_view -> setEnabled(opened_project);
|
|
m_terminal_numbering -> setEnabled(editable_project);
|
|
m_reload_element_drawings -> setEnabled(opened_project);
|
|
#ifdef QET_HAS_SCRIPTING
|
|
m_run_script -> setEnabled(opened_project);
|
|
#endif
|
|
#ifdef QET_EXPORT_PROJECT_DB
|
|
m_export_project_db -> setEnabled(editable_project);
|
|
#endif
|
|
m_project_terminalBloc -> setEnabled(editable_project);
|
|
|
|
|
|
slot_updateUndoStack();
|
|
slot_updateModeActions();
|
|
slot_updatePasteAction();
|
|
slot_updateComplexActions();
|
|
slot_updateAutoNumDock();
|
|
}
|
|
|
|
/**
|
|
@brief QETDiagramEditor::slot_updateAutoNumDock
|
|
Update Auto Num Dock Widget when changing Project
|
|
*/
|
|
void QETDiagramEditor::slot_updateAutoNumDock()
|
|
{
|
|
if ( m_workspace.subWindowList().indexOf(m_workspace.activeSubWindow()) != activeSubWindowIndex) {
|
|
activeSubWindowIndex = m_workspace.subWindowList().indexOf(m_workspace.activeSubWindow());
|
|
if (currentProjectView() != nullptr && currentDiagramView() != nullptr) {
|
|
m_autonumbering_dock->setProject(currentProjectView()->project(),currentProjectView());
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
@brief QETDiagramEditor::slot_updateUndoStack
|
|
Update the undo stack view
|
|
*/
|
|
void QETDiagramEditor::slot_updateUndoStack()
|
|
{
|
|
if(currentProjectView())
|
|
undo_group.setActiveStack(currentProjectView()->project()->undoStack());
|
|
}
|
|
|
|
/**
|
|
@brief QETDiagramEditor::slot_updateComplexActions
|
|
Manage the actions that need some conditions to be enabled or not.
|
|
This method does nothing if there is no project opened
|
|
*/
|
|
void QETDiagramEditor::slot_updateComplexActions()
|
|
{
|
|
#ifdef QET_HAS_SCRIPTING
|
|
updateScriptActions();
|
|
#endif
|
|
DiagramView *dv = currentDiagramView();
|
|
if(!dv)
|
|
{
|
|
QList <QAction *> action_list;
|
|
action_list << m_conductor_reset
|
|
<< m_find_element
|
|
<< m_cut
|
|
<< m_copy
|
|
<< m_paste_element_info
|
|
<< m_duplicate
|
|
<< m_delete_selection
|
|
<< m_rotate_selection
|
|
<< m_rotate_group_selection
|
|
<< m_mirror_horizontal
|
|
<< m_mirror_vertical
|
|
<< m_edit_selection
|
|
<< m_group_selected_texts
|
|
<< m_group_selection
|
|
<< m_ungroup_selection;
|
|
for(QAction *action : action_list)
|
|
action->setEnabled(false);
|
|
m_align_actions_group.setEnabled(false);
|
|
|
|
return;
|
|
}
|
|
|
|
Diagram *diagram_ = dv->diagram();
|
|
DiagramContent dc(diagram_);
|
|
bool ro = diagram_->isReadOnly();
|
|
|
|
|
|
//Number of selected conductors
|
|
int selected_conductors_count = diagram_->selectedConductors().count();
|
|
m_conductor_reset->setEnabled(!ro && selected_conductors_count);
|
|
|
|
// number of selected elements
|
|
int selected_elements_count = dc.count(DiagramContent::Elements);
|
|
m_find_element->setEnabled(selected_elements_count == 1);
|
|
m_mirror_horizontal->setEnabled(!ro && selected_elements_count);
|
|
m_mirror_vertical->setEnabled(!ro && selected_elements_count);
|
|
|
|
//Actions that need items (elements, conductors, texts...) selected, to be enabled
|
|
bool copiable_items = dc.hasCopiableItems();
|
|
bool deletable_items = dc.hasDeletableItems();
|
|
m_cut -> setEnabled(!ro && copiable_items);
|
|
m_copy -> setEnabled(copiable_items);
|
|
m_duplicate -> setEnabled(!ro && copiable_items);
|
|
updatePasteElementInfoAction();
|
|
m_delete_selection -> setEnabled(!ro && deletable_items);
|
|
m_rotate_selection -> setEnabled(!ro && diagram_->canRotateSelection());
|
|
m_rotate_group_selection -> setEnabled(!ro && diagram_->canRotateSelection());
|
|
|
|
//Action that need selected texts or texts group
|
|
QList<DiagramTextItem *> texts = DiagramContent(diagram_).selectedTexts();
|
|
QList<ElementTextItemGroup *> groups = DiagramContent(diagram_).selectedTextsGroup();
|
|
int selected_texts = texts.count();
|
|
int selected_conductor_texts = 0;
|
|
for(DiagramTextItem *dti : texts)
|
|
{
|
|
if(dti->type() == ConductorTextItem::Type)
|
|
selected_conductor_texts++;
|
|
}
|
|
int selected_dynamic_elmt_text = 0;
|
|
for(DiagramTextItem *dti : texts)
|
|
{
|
|
if(dti->type() == DynamicElementTextItem::Type)
|
|
selected_dynamic_elmt_text++;
|
|
}
|
|
m_rotate_texts->setEnabled(!ro && (selected_texts || groups.size()));
|
|
|
|
//Action that need only element text selected
|
|
QList<DynamicElementTextItem *> deti_list = dc.m_element_texts.values();
|
|
if(deti_list.size() > 1 && dc.count() == deti_list.count())
|
|
{
|
|
Element *elmt = deti_list.first()->parentElement();
|
|
bool ok = true;
|
|
for(DynamicElementTextItem *deti : deti_list)
|
|
{
|
|
if(elmt != deti->parentElement())
|
|
ok = false;
|
|
}
|
|
m_group_selected_texts->setEnabled(!ro && ok);
|
|
}
|
|
else
|
|
m_group_selected_texts->setDisabled(true);
|
|
|
|
m_group_selection ->setEnabled(GroupItemsCommand::canGroup(diagram_));
|
|
m_ungroup_selection->setEnabled(GroupItemsCommand::canUngroup(diagram_));
|
|
|
|
// actions need only one editable item
|
|
int selected_image = dc.count(DiagramContent::Images);
|
|
|
|
int selected_shape = dc.count(DiagramContent::Shapes);
|
|
int selected_editable = selected_elements_count
|
|
+ (selected_texts
|
|
- selected_conductor_texts
|
|
- selected_dynamic_elmt_text)
|
|
+ selected_image
|
|
+ selected_shape
|
|
+ selected_conductors_count;
|
|
|
|
if (selected_editable == 1)
|
|
{
|
|
m_edit_selection -> setEnabled(true);
|
|
//edit element
|
|
if (selected_elements_count)
|
|
{
|
|
m_edit_selection -> setText(tr("Edit the element",
|
|
"edit element"));
|
|
m_edit_selection -> setIcon(QET::Icons::ElementEdit);
|
|
}
|
|
//edit text field
|
|
else if (selected_texts)
|
|
{
|
|
m_edit_selection -> setText(tr("Edit the text field",
|
|
"edit text field"));
|
|
m_edit_selection -> setIcon(QET::Icons::EditText);
|
|
}
|
|
//edit image
|
|
else if (selected_image)
|
|
{
|
|
m_edit_selection -> setText(tr("Edit the image",
|
|
"edit image"));
|
|
m_edit_selection -> setIcon(QET::Icons::resize_image);
|
|
}
|
|
//edit conductor
|
|
else if (selected_conductors_count)
|
|
{
|
|
m_edit_selection -> setText(tr("Edit conductor",
|
|
"edit conductor"));
|
|
m_edit_selection -> setIcon(QET::Icons::ConductorEdit);
|
|
}
|
|
}
|
|
//not an editable item
|
|
else
|
|
{
|
|
m_edit_selection -> setText(tr("Edit the selected object",
|
|
"edit selected item"));
|
|
m_edit_selection -> setIcon(QET::Icons::ElementEdit);
|
|
m_edit_selection -> setEnabled(false);
|
|
}
|
|
|
|
//Actions for edit Z value
|
|
QList<QGraphicsItem *> list = dc.items(
|
|
DiagramContent::SelectedOnly
|
|
| DiagramContent::Elements
|
|
| DiagramContent::Shapes
|
|
| DiagramContent::Images);
|
|
m_depth_action_group->setEnabled(list.isEmpty()? false : true);
|
|
|
|
//Align actions: symbols, pictures, free texts and shapes take part,
|
|
//counted the way the command counts them, a group as one.
|
|
//Snapping needs one of them, lining them up needs two.
|
|
const int alignable = AlignSelectionCommand::unitCount(dc);
|
|
m_align_actions_group.setEnabled(!ro && alignable);
|
|
const QList<QAction *> align_actions = m_align_actions_group.actions();
|
|
for (QAction *action : align_actions.mid(1))
|
|
action->setEnabled(!ro && alignable >= 2);
|
|
}
|
|
|
|
/**
|
|
@brief QETDiagramEditor::slot_updateModeActions
|
|
Manage action who need an opened diagram or project to be updated
|
|
*/
|
|
void QETDiagramEditor::slot_updateModeActions()
|
|
{
|
|
DiagramView *dv = currentDiagramView();
|
|
|
|
if (!dv)
|
|
grp_visu_sel -> setEnabled(false);
|
|
else
|
|
{
|
|
switch((int)(dv -> dragMode()))
|
|
{
|
|
case QGraphicsView::NoDrag:
|
|
grp_visu_sel -> setEnabled(false);
|
|
break;
|
|
case QGraphicsView::ScrollHandDrag:
|
|
grp_visu_sel -> setEnabled(true);
|
|
m_mode_visualise -> setChecked(true);
|
|
break;
|
|
case QGraphicsView::RubberBandDrag:
|
|
grp_visu_sel -> setEnabled(true);
|
|
m_mode_selection -> setChecked(true);
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (ProjectView *pv = currentProjectView())
|
|
{
|
|
m_auto_conductor -> setEnabled (true);
|
|
m_auto_conductor -> setChecked (pv -> project() -> autoConductor());
|
|
m_auto_break_conductor -> setEnabled (true);
|
|
m_auto_break_conductor -> setChecked (pv -> project() -> autoBreakConductor());
|
|
}
|
|
else
|
|
{
|
|
m_auto_conductor -> setDisabled(true);
|
|
m_auto_break_conductor -> setDisabled(true);
|
|
}
|
|
|
|
if (m_conductor_color_button) {
|
|
m_conductor_color_button->updateEnabledState();
|
|
}
|
|
}
|
|
|
|
namespace {
|
|
/**
|
|
@return true if @p element has information the user can edit, the
|
|
same element kinds ElementPropertiesWidget gives an ElementInfoWidget.
|
|
Plain slaves and folio reports take theirs from what they are linked to.
|
|
*/
|
|
bool takesElementInformations(const Element *element)
|
|
{
|
|
switch (element->linkType()) {
|
|
case Element::Simple:
|
|
case Element::Thumbnail:
|
|
case Element::Master:
|
|
case Element::Terminale:
|
|
return true;
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
@return the elements of @p diagram's selection which take information
|
|
*/
|
|
QList<Element *> selectedInformationElements(Diagram *diagram)
|
|
{
|
|
QList<Element *> list;
|
|
for (Element *element : DiagramContent(diagram).m_elements) {
|
|
if (takesElementInformations(element))
|
|
list << element;
|
|
}
|
|
return list;
|
|
}
|
|
} // namespace
|
|
|
|
/**
|
|
@brief QETDiagramEditor::slot_updatePasteAction
|
|
Gere les actions ayant besoin du presse-papier
|
|
*/
|
|
void QETDiagramEditor::slot_updatePasteAction()
|
|
{
|
|
DiagramView *dv = currentDiagramView();
|
|
bool editable_diagram = (dv && !dv -> diagram() -> isReadOnly());
|
|
|
|
// pour coller, il faut un schema ouvert et un schema dans le presse-papier
|
|
const bool may_be_diagram = Diagram::clipboardMayContainDiagram();
|
|
m_paste -> setEnabled(editable_diagram && may_be_diagram);
|
|
m_paste_origin -> setEnabled(editable_diagram && may_be_diagram);
|
|
|
|
//Read the clipboard once here rather than on every selection
|
|
//change: it can hold a whole folio.
|
|
m_clipboard_element_info.clear();
|
|
m_clipboard_has_element = false;
|
|
if (may_be_diagram)
|
|
{
|
|
QDomDocument document;
|
|
if (document.setContent(QApplication::clipboard()->text()))
|
|
{
|
|
const QList<QDomElement> elements = QET::findInDomElement(
|
|
document.documentElement(),
|
|
QStringLiteral("elements"),
|
|
QStringLiteral("element"));
|
|
if (elements.size() == 1)
|
|
{
|
|
m_clipboard_element_info.fromXml(
|
|
elements.first().firstChildElement(QStringLiteral("elementInformations")),
|
|
QStringLiteral("elementInformation"));
|
|
m_clipboard_has_element = true;
|
|
}
|
|
}
|
|
}
|
|
updatePasteElementInfoAction();
|
|
}
|
|
|
|
/**
|
|
@brief QETDiagramEditor::updatePasteElementInfoAction
|
|
Enable "paste element information" when the clipboard holds exactly one
|
|
element and the selection of an editable folio has an element to give
|
|
its information to.
|
|
*/
|
|
void QETDiagramEditor::updatePasteElementInfoAction()
|
|
{
|
|
DiagramView *dv = currentDiagramView();
|
|
m_paste_element_info->setEnabled(
|
|
m_clipboard_has_element
|
|
&& dv && dv->diagram() && !dv->diagram()->isReadOnly()
|
|
&& !selectedInformationElements(dv->diagram()).isEmpty());
|
|
}
|
|
|
|
/**
|
|
@brief QETDiagramEditor::pasteElementInformations
|
|
Copy the information of the element on the clipboard onto the selected
|
|
elements, in one undo step (#1375). Every field filled in on the copied
|
|
element replaces the same field; a field it leaves empty is left alone,
|
|
since an empty field is not written to the clipboard and cannot be told
|
|
from one the copied element does not have.
|
|
The label and its formula travel together: one without the other would
|
|
have the formula rebuild a label the user did not copy.
|
|
*/
|
|
void QETDiagramEditor::pasteElementInformations()
|
|
{
|
|
DiagramView *dv = currentDiagramView();
|
|
if (!m_clipboard_has_element || !dv || !dv->diagram()
|
|
|| dv->diagram()->isReadOnly())
|
|
return;
|
|
|
|
const DiagramContext &source = m_clipboard_element_info;
|
|
const bool takes_label = source.contains(QETInformation::ELMT_LABEL)
|
|
|| source.contains(QETInformation::ELMT_FORMULA);
|
|
//A numbering scheme id is only kept if it names a scheme of this
|
|
//project; one copied from another project would name nothing.
|
|
QETProject *project = dv->diagram()->project();
|
|
const QUuid scheme_id(source.value(QETInformation::ELMT_FORMULA_ID).toString());
|
|
const bool keep_scheme_id = project && !scheme_id.isNull()
|
|
&& !project->elementAutoNumTitle(scheme_id).isEmpty();
|
|
|
|
QMap<QPointer<Element>, QPair<DiagramContext, DiagramContext>> changes;
|
|
for (Element *element : selectedInformationElements(dv->diagram()))
|
|
{
|
|
const DiagramContext old_info = element->elementInformations();
|
|
DiagramContext new_info = old_info;
|
|
if (takes_label) {
|
|
new_info.remove(QETInformation::ELMT_LABEL);
|
|
new_info.remove(QETInformation::ELMT_FORMULA);
|
|
new_info.remove(QETInformation::ELMT_FORMULA_ID);
|
|
}
|
|
for (const QString &key : source.keys()) {
|
|
if (key == QETInformation::ELMT_FORMULA_ID && !keep_scheme_id)
|
|
continue;
|
|
new_info.addValue(key, source.value(key), source.keyMustShow(key));
|
|
}
|
|
if (new_info != old_info)
|
|
changes.insert(element, qMakePair(old_info, new_info));
|
|
}
|
|
if (changes.isEmpty())
|
|
return;
|
|
|
|
auto *undo = new ChangeElementInformationCommand(changes);
|
|
undo->setText(tr("Paste element information"));
|
|
dv->diagram()->undoStack().push(undo);
|
|
}
|
|
|
|
/**
|
|
@brief QETDiagramEditor::addProjectView
|
|
Add a new project view to workspace and build the connection between
|
|
the projectview / project and this QETDiagramEditor.
|
|
@param project_view : project view to add
|
|
*/
|
|
void QETDiagramEditor::addProjectView(ProjectView *project_view)
|
|
{
|
|
if (!project_view) return;
|
|
|
|
foreach(DiagramView *dv, project_view -> diagram_views())
|
|
diagramWasAdded(dv);
|
|
|
|
//Manage the close event of project
|
|
connect(project_view, &ProjectView::projectClosed, this, &QETDiagramEditor::projectWasClosed);
|
|
//Manage the adding of diagram
|
|
connect(project_view, qOverload<DiagramView*>(&ProjectView::diagramAdded), this, &QETDiagramEditor::diagramWasAdded);
|
|
|
|
if (QETProject *project = project_view -> project())
|
|
connect(project, &QETProject::readOnlyChanged, this, &QETDiagramEditor::slot_updateActions);
|
|
|
|
//Manage request for edit or find element and titleblock
|
|
connect (project_view, &ProjectView::findElementRequired,
|
|
this, &QETDiagramEditor::findElementInPanel);
|
|
|
|
// display error messages sent by the project view
|
|
connect(project_view, &ProjectView::errorEncountered, this, qOverload<const QString&>(&QETDiagramEditor::showError));
|
|
|
|
//Highlight the current page
|
|
connect(project_view, &ProjectView::diagramActivated, this, [this](DiagramView *dv) {
|
|
if (dv && dv->diagram() && pa) {
|
|
// 1. Find the item in the tree that corresponds to this diagram
|
|
QTreeWidgetItem *item = pa->elementsPanel().getItemForDiagram(dv->diagram());
|
|
|
|
// 2. If you find it, select it
|
|
if (item) {
|
|
pa->elementsPanel().setCurrentItem(item);
|
|
}
|
|
}
|
|
});
|
|
|
|
//Highlight the current page in projectView on project activation
|
|
connect(this, &QETDiagramEditor::syncElementsPanel, this, [this]() {
|
|
if (pa && currentDiagramView()) {
|
|
// In the tree, find the element that corresponds to the diagram of the selected project.
|
|
QTreeWidgetItem *item = pa->elementsPanel().getItemForDiagram(currentDiagramView()->diagram());
|
|
if (item) {
|
|
// select the diagram
|
|
pa->elementsPanel().setCurrentItem(item);
|
|
}
|
|
}
|
|
});
|
|
|
|
//We maximise the new window if the current window is inexistent or maximized
|
|
QWidget *current_window = m_workspace.activeSubWindow();
|
|
bool maximise = ((!current_window)
|
|
|| (current_window -> windowState()
|
|
& Qt::WindowMaximized));
|
|
|
|
//Add the new window
|
|
QMdiSubWindow *sub_window = m_workspace.addSubWindow(project_view);
|
|
sub_window -> setWindowIcon(project_view -> windowIcon());
|
|
sub_window -> systemMenu() -> clear();
|
|
|
|
//By default QMdiSubWindow have a QAction "close" with shortcut QKeySequence::Close
|
|
//But the QAction m_close_file of this class have the same shortcut too.
|
|
//We remove the shortcut of the QAction of QMdiSubWindow for avoid conflic
|
|
for(QAction *act : sub_window->actions())
|
|
{
|
|
if(act->shortcut() == QKeySequence::Close)
|
|
act->setShortcut(QKeySequence());
|
|
}
|
|
|
|
//Display the new window
|
|
if (maximise) project_view -> showMaximized();
|
|
else project_view -> show();
|
|
}
|
|
|
|
/**
|
|
@return la liste des fichiers edites par cet editeur de schemas
|
|
*/
|
|
QList<QString> QETDiagramEditor::editedFiles() const
|
|
{
|
|
QList<QString> edited_files_list;
|
|
foreach (ProjectView *project_view, openedProjects()) {
|
|
QString diagram_file(project_view -> project() -> filePath());
|
|
if (!diagram_file.isEmpty()) {
|
|
edited_files_list << QFileInfo(diagram_file).canonicalFilePath();
|
|
}
|
|
}
|
|
return(edited_files_list);
|
|
}
|
|
|
|
/**
|
|
@param filepath Un chemin de fichier
|
|
Note : si filepath est une chaine vide, cette methode retourne 0.
|
|
@return le ProjectView editant le fichier filepath, ou 0 si ce fichier n'est
|
|
pas edite par cet editeur de schemas.
|
|
*/
|
|
ProjectView *QETDiagramEditor::viewForFile(const QString &filepath) const
|
|
{
|
|
if (filepath.isEmpty()) return(nullptr);
|
|
|
|
QString searched_can_file_path = QFileInfo(filepath).canonicalFilePath();
|
|
if (searched_can_file_path.isEmpty()) {
|
|
// QFileInfo returns an empty path for non-existent files
|
|
return(nullptr);
|
|
}
|
|
foreach (ProjectView *project_view, openedProjects()) {
|
|
QString project_can_file_path = QFileInfo(project_view -> project() -> filePath()).canonicalFilePath();
|
|
if (project_can_file_path == searched_can_file_path) {
|
|
return(project_view);
|
|
}
|
|
}
|
|
return(nullptr);
|
|
}
|
|
|
|
/**
|
|
@brief QETDiagramEditor::drawGrid
|
|
@return true if the grid of folio must be displayed
|
|
*/
|
|
bool QETDiagramEditor::drawGrid() const
|
|
{
|
|
return m_draw_grid->isChecked();
|
|
}
|
|
|
|
/**
|
|
@brief QETDiagramEditor::openBackupFiles
|
|
@param backup_files
|
|
*/
|
|
void QETDiagramEditor::openBackupFiles(QList<KAutoSaveFile *> backup_files)
|
|
{
|
|
for (KAutoSaveFile *file : backup_files)
|
|
{
|
|
//Create the project
|
|
DialogWaiting::instance(this);
|
|
|
|
//QETProject takes ownership of file and deletes it, whether or not it opens
|
|
const QString file_name = file->managedFile().fileName();
|
|
QETProject *project = new QETProject(file, this);
|
|
if (project->state() != QETProject::Ok)
|
|
{
|
|
if (project -> state() != QETProject::FileOpenDiscard)
|
|
{
|
|
QET::QetMessageBox::warning(
|
|
this,
|
|
tr("Unable to open project", "message box title"),
|
|
QString(tr(
|
|
"An error occurred while opening file %1.",
|
|
"message box content")).arg(file_name));
|
|
}
|
|
delete project;
|
|
DialogWaiting::dropInstance();
|
|
continue;
|
|
}
|
|
addProject(project);
|
|
DialogWaiting::dropInstance();
|
|
}
|
|
}
|
|
/**
|
|
met a jour le menu "Fenetres"
|
|
*/
|
|
void QETDiagramEditor::slot_updateWindowsMenu()
|
|
{
|
|
// nettoyage du menu
|
|
foreach(QAction *a, windows_menu -> actions()) windows_menu -> removeAction(a);
|
|
|
|
// actions de fermeture
|
|
windows_menu -> addAction(m_close_file);
|
|
//windows_menu -> addAction(closeAllAct);
|
|
|
|
// actions de reorganisation des fenetres
|
|
windows_menu -> addSeparator();
|
|
windows_menu -> addAction(m_tile_window);
|
|
windows_menu -> addAction(m_cascade_window);
|
|
|
|
// actions de deplacement entre les fenetres
|
|
windows_menu -> addSeparator();
|
|
windows_menu -> addAction(m_next_window);
|
|
windows_menu -> addAction(m_previous_window);
|
|
|
|
// liste des fenetres
|
|
QList<ProjectView *> windows = openedProjects();
|
|
|
|
m_tile_window -> setEnabled(!windows.isEmpty() && m_workspace.viewMode() == QMdiArea::SubWindowView);
|
|
m_cascade_window -> setEnabled(!windows.isEmpty() && m_workspace.viewMode() == QMdiArea::SubWindowView);
|
|
m_next_window -> setEnabled(windows.count() > 1);
|
|
m_previous_window -> setEnabled(windows.count() > 1);
|
|
|
|
if (!windows.isEmpty()) windows_menu -> addSeparator();
|
|
QActionGroup *windows_actions = new QActionGroup(this);
|
|
foreach(ProjectView *project_view, windows) {
|
|
QString pv_title = project_view -> windowTitle();
|
|
QAction *action = windows_menu -> addAction(pv_title);
|
|
windows_actions -> addAction(action);
|
|
action -> setStatusTip(QString(tr("Activates the project « %1 »")).arg(pv_title));
|
|
action -> setCheckable(true);
|
|
action -> setChecked(project_view == currentProjectView());
|
|
connect(action, &QAction::triggered, &windowMapper, qOverload<>(&QSignalMapper::map));
|
|
windowMapper.setMapping(action, project_view);
|
|
}
|
|
}
|
|
|
|
/**
|
|
Edite les proprietes du schema diagram
|
|
@param diagram_view schema dont il faut editer les proprietes
|
|
*/
|
|
void QETDiagramEditor::editDiagramProperties(DiagramView *diagram_view)
|
|
{
|
|
if (ProjectView *project_view = findProject(diagram_view)) {
|
|
activateProject(project_view);
|
|
project_view -> editDiagramProperties(diagram_view);
|
|
}
|
|
}
|
|
|
|
/**
|
|
Edite les proprietes du schema diagram
|
|
@param diagram schema dont il faut editer les proprietes
|
|
*/
|
|
void QETDiagramEditor::editDiagramProperties(Diagram *diagram)
|
|
{
|
|
if (ProjectView *project_view = findProject(diagram)) {
|
|
activateProject(project_view);
|
|
project_view -> editDiagramProperties(diagram);
|
|
}
|
|
}
|
|
|
|
/**
|
|
Affiche les projets dans des fenetres.
|
|
*/
|
|
void QETDiagramEditor::setWindowedMode()
|
|
{
|
|
m_workspace.setViewMode(QMdiArea::SubWindowView);
|
|
m_windowed_view_mode -> setChecked(true);
|
|
slot_updateWindowsMenu();
|
|
}
|
|
|
|
/**
|
|
Affiche les projets dans des onglets.
|
|
*/
|
|
void QETDiagramEditor::setTabbedMode()
|
|
{
|
|
m_workspace.setViewMode(QMdiArea::TabbedView);
|
|
m_tabbed_view_mode -> setChecked(true);
|
|
slot_updateWindowsMenu();
|
|
}
|
|
|
|
/**
|
|
@brief QETDiagramEditor::readSettings
|
|
Read the settings
|
|
*/
|
|
void QETDiagramEditor::readSettings()
|
|
{
|
|
QSettings settings;
|
|
|
|
// dimensions et position de la fenetre
|
|
QVariant geometry = settings.value("diagrameditor/geometry");
|
|
if (geometry.isValid()) restoreGeometry(geometry.toByteArray());
|
|
|
|
// gestion des projets (onglets ou fenetres)
|
|
bool tabbed = settings.value("diagrameditor/viewmode", "tabbed") == "tabbed";
|
|
if (tabbed) {
|
|
setTabbedMode();
|
|
} else {
|
|
setWindowedMode();
|
|
}
|
|
}
|
|
|
|
/**
|
|
@brief QETDiagramEditor::readSettingsState
|
|
Restore the window state (docks, toolbars).
|
|
Must be called AFTER show() in Qt6 for restoreState() to work correctly.
|
|
*/
|
|
void QETDiagramEditor::readSettingsState()
|
|
{
|
|
QSettings settings;
|
|
|
|
// etat de la fenetre (barres d'outils, docks...)
|
|
QVariant state = settings.value("diagrameditor/state");
|
|
if (state.isValid()) {
|
|
if (!restoreState(state.toByteArray())) {
|
|
settings.remove("diagrameditor/state");
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
@brief QETDiagramEditor::writeSettings
|
|
Write the settings
|
|
*/
|
|
void QETDiagramEditor::writeSettings()
|
|
{
|
|
QSettings settings;
|
|
settings.setValue("diagrameditor/geometry", saveGeometry());
|
|
settings.setValue("diagrameditor/state", saveState());
|
|
}
|
|
|
|
/**
|
|
Active le projet passe en parametre
|
|
@param project Projet a activer
|
|
*/
|
|
void QETDiagramEditor::activateProject(QETProject *project)
|
|
{
|
|
activateProject(findProject(project));
|
|
}
|
|
|
|
/**
|
|
Active le projet passe en parametre
|
|
@param project_view Projet a activer
|
|
*/
|
|
void QETDiagramEditor::activateProject(ProjectView *project_view)
|
|
{
|
|
if (!project_view) return;
|
|
activateWidget(project_view);
|
|
}
|
|
|
|
/**
|
|
@brief QETDiagramEditor::projectWasClosed
|
|
Manage the close of a project.
|
|
@param project_view
|
|
*/
|
|
void QETDiagramEditor::projectWasClosed(ProjectView *project_view)
|
|
{
|
|
QETProject *project = project_view -> project();
|
|
if (project)
|
|
{
|
|
pa -> elementsPanel().projectWasClosed(project);
|
|
m_element_collection_widget->removeProject(project);
|
|
undo_group.removeStack(project -> undoStack());
|
|
QETApp::unregisterProject(project);
|
|
}
|
|
//When project is closed, a lot of signals are emitted, notably if there is an item selected in a diagram.
|
|
//In some special case, since signal/slot connection can be direct or queued, some signals are handled after QObject is deleted, and crash qet
|
|
//notably in the function Diagram::elements when it calls items() (I don't know exactly why).
|
|
//set nullptr to "m_selection_properties_editor->setDiagram()" fixes this crash
|
|
m_selection_properties_editor->setDiagram(nullptr);
|
|
project_view -> deleteLater();
|
|
project -> deleteLater();
|
|
}
|
|
|
|
/**
|
|
Edite les proprietes du projet project_view.
|
|
@param project_view Vue sur le projet dont il faut editer les proprietes
|
|
*/
|
|
void QETDiagramEditor::editProjectProperties(ProjectView *project_view)
|
|
{
|
|
if (!project_view) return;
|
|
activateProject(project_view);
|
|
project_view -> editProjectProperties();
|
|
}
|
|
|
|
/**
|
|
Edite les proprietes du projet project.
|
|
@param project Projet dont il faut editer les proprietes
|
|
*/
|
|
void QETDiagramEditor::editProjectProperties(QETProject *project)
|
|
{
|
|
editProjectProperties(findProject(project));
|
|
}
|
|
|
|
/**
|
|
@brief QETDiagramEditor::addDiagramToProject
|
|
Add a diagram to project
|
|
@param project
|
|
*/
|
|
void QETDiagramEditor::addDiagramToProject(QETProject *project)
|
|
{
|
|
if (!project) {
|
|
return;
|
|
}
|
|
|
|
if (ProjectView *project_view = findProject(project))
|
|
{
|
|
activateProject(project);
|
|
project_view->project()->addNewDiagram();
|
|
}
|
|
}
|
|
|
|
/**
|
|
@brief QETDiagramEditor::addDiagramToProjectAt
|
|
Add a diagram to project, inserted at a specific position.
|
|
@param project
|
|
@param pos
|
|
*/
|
|
void QETDiagramEditor::addDiagramToProjectAt(QETProject *project, int pos)
|
|
{
|
|
if (!project) {
|
|
return;
|
|
}
|
|
|
|
if (ProjectView *project_view = findProject(project))
|
|
{
|
|
activateProject(project);
|
|
project_view->project()->addNewDiagram(pos);
|
|
}
|
|
}
|
|
/**
|
|
* @brief QETDiagramEditor::removeDiagram
|
|
* Wrapper für einzelne Diagramme, um Abwärtskompatibilität zu erhalten.
|
|
*/
|
|
void QETDiagramEditor::removeDiagram(Diagram *diagram)
|
|
{
|
|
if (!diagram) return;
|
|
QList<Diagram *> list;
|
|
list << diagram;
|
|
removeDiagrams(list);
|
|
}
|
|
|
|
/**
|
|
* @brief QETDiagramEditor::removeDiagrams
|
|
* Deletes a list of folios with a single query.
|
|
*/
|
|
void QETDiagramEditor::removeDiagrams(const QList<Diagram *> &diagrams)
|
|
{
|
|
if (diagrams.isEmpty()) return;
|
|
|
|
if (diagrams.count() == 1) {
|
|
QMessageBox::StandardButton reply;
|
|
reply = QMessageBox::question(this, tr("Delete this sheet"),
|
|
tr("Are you sure you want to delete this sheet?"),
|
|
QMessageBox::Yes | QMessageBox::No);
|
|
if (reply == QMessageBox::No) return;
|
|
} else {
|
|
QMessageBox::StandardButton reply;
|
|
reply = QMessageBox::question(this, tr("Delete the sheets"),
|
|
tr("Are you sure you want to delete the selected %1 sheets ?").arg(diagrams.count()),
|
|
QMessageBox::Yes | QMessageBox::No);
|
|
if (reply == QMessageBox::No) return;
|
|
}
|
|
|
|
ProjectView *project_view = nullptr;
|
|
QETProject *project = diagrams.first()->project();
|
|
if (project) {
|
|
project_view = findProject(project);
|
|
}
|
|
|
|
if (project_view) project_view->setUpdatesEnabled(false);
|
|
if (pa) pa->setUpdatesEnabled(false);
|
|
|
|
if (project) {
|
|
project->undoStack()->beginMacro(diagrams.count() == 1
|
|
? tr("Delete this sheet")
|
|
: tr("Delete %1 sheets").arg(diagrams.count()));
|
|
}
|
|
|
|
foreach (Diagram *diagram, diagrams) {
|
|
removeDiagramSilent(diagram);
|
|
}
|
|
|
|
if (project) project->undoStack()->endMacro();
|
|
|
|
if (pa) pa->setUpdatesEnabled(true);
|
|
if (project_view) project_view->setUpdatesEnabled(true);
|
|
|
|
emit syncElementsPanel();
|
|
}
|
|
|
|
/**
|
|
Supprime un schema de son projet
|
|
@param diagram Schema a supprimer
|
|
*/
|
|
void QETDiagramEditor::removeDiagramSilent(Diagram *diagram)
|
|
{
|
|
if (!diagram) return;
|
|
|
|
if (QETProject *diagram_project = diagram -> project()) {
|
|
if (ProjectView *project_view = findProject(diagram_project)) {
|
|
|
|
// supprime le schema
|
|
project_view -> removeDiagram(diagram, true);
|
|
}
|
|
}
|
|
}
|
|
void QETDiagramEditor::moveDiagramUp(const QList<Diagram *> &diagrams) {
|
|
if (diagrams.isEmpty()) return;
|
|
QList<Diagram *> safeDiagrams = diagrams;
|
|
if (QETProject *diagram_project = safeDiagrams.first()->project()) {
|
|
if (!diagram_project->isReadOnly()) {
|
|
if (ProjectView *project_view = findProject(diagram_project)) {
|
|
// Forward loop for moving up
|
|
diagram_project->undoStack()->beginMacro(tr("Move the sheets"));
|
|
for (int i = 0; i < safeDiagrams.size(); ++i) {
|
|
project_view->moveDiagramUp(safeDiagrams.at(i));
|
|
QCoreApplication::processEvents(QEventLoop::ExcludeUserInputEvents);
|
|
}
|
|
diagram_project->undoStack()->endMacro();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void QETDiagramEditor::moveDiagramDown(const QList<Diagram *> &diagrams) {
|
|
if (diagrams.isEmpty()) return;
|
|
QList<Diagram *> safeDiagrams = diagrams;
|
|
if (QETProject *diagram_project = safeDiagrams.first()->project()) {
|
|
if (!diagram_project->isReadOnly()) {
|
|
if (ProjectView *project_view = findProject(diagram_project)) {
|
|
// Backward loop for moving down
|
|
diagram_project->undoStack()->beginMacro(tr("Move the sheets"));
|
|
for (int i = safeDiagrams.size() - 1; i >= 0; --i) {
|
|
project_view->moveDiagramDown(safeDiagrams.at(i));
|
|
QCoreApplication::processEvents(QEventLoop::ExcludeUserInputEvents);
|
|
}
|
|
diagram_project->undoStack()->endMacro();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void QETDiagramEditor::moveDiagramUpTop(const QList<Diagram *> &diagrams) {
|
|
if (diagrams.isEmpty()) return;
|
|
QList<Diagram *> safeDiagrams = diagrams;
|
|
if (QETProject *diagram_project = safeDiagrams.first()->project()) {
|
|
if (!diagram_project->isReadOnly()) {
|
|
if (ProjectView *project_view = findProject(diagram_project)) {
|
|
// Backward loop to preserve relative order of the selected items when moving to top
|
|
diagram_project->undoStack()->beginMacro(tr("Move the sheets"));
|
|
for (int i = safeDiagrams.size() - 1; i >= 0; --i) {
|
|
project_view->moveDiagramUpTop(safeDiagrams.at(i));
|
|
QCoreApplication::processEvents(QEventLoop::ExcludeUserInputEvents);
|
|
}
|
|
diagram_project->undoStack()->endMacro();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void QETDiagramEditor::moveDiagramUpx10(const QList<Diagram *> &diagrams) {
|
|
if (diagrams.isEmpty()) return;
|
|
QList<Diagram *> safeDiagrams = diagrams;
|
|
if (QETProject *diagram_project = safeDiagrams.first()->project()) {
|
|
if (!diagram_project->isReadOnly()) {
|
|
if (ProjectView *project_view = findProject(diagram_project)) {
|
|
// Forward loop for moving up
|
|
diagram_project->undoStack()->beginMacro(tr("Move the sheets"));
|
|
for (int i = 0; i < safeDiagrams.size(); ++i) {
|
|
project_view->moveDiagramUpx10(safeDiagrams.at(i));
|
|
QCoreApplication::processEvents(QEventLoop::ExcludeUserInputEvents);
|
|
}
|
|
diagram_project->undoStack()->endMacro();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void QETDiagramEditor::moveDiagramDownx10(const QList<Diagram *> &diagrams) {
|
|
if (diagrams.isEmpty()) return;
|
|
QList<Diagram *> safeDiagrams = diagrams;
|
|
if (QETProject *diagram_project = safeDiagrams.first()->project()) {
|
|
if (!diagram_project->isReadOnly()) {
|
|
if (ProjectView *project_view = findProject(diagram_project)) {
|
|
// Backward loop for moving down
|
|
diagram_project->undoStack()->beginMacro(tr("Move the sheets"));
|
|
for (int i = safeDiagrams.size() - 1; i >= 0; --i) {
|
|
project_view->moveDiagramDownx10(safeDiagrams.at(i));
|
|
QCoreApplication::processEvents(QEventLoop::ExcludeUserInputEvents);
|
|
}
|
|
diagram_project->undoStack()->endMacro();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void QETDiagramEditor::moveDiagramUpx100(const QList<Diagram *> &diagrams) {
|
|
if (diagrams.isEmpty()) return;
|
|
QList<Diagram *> safeDiagrams = diagrams;
|
|
if (QETProject *diagram_project = safeDiagrams.first()->project()) {
|
|
if (!diagram_project->isReadOnly()) {
|
|
if (ProjectView *project_view = findProject(diagram_project)) {
|
|
// Forward loop for moving up
|
|
diagram_project->undoStack()->beginMacro(tr("Move the sheets"));
|
|
for (int i = 0; i < safeDiagrams.size(); ++i) {
|
|
project_view->moveDiagramUpx100(safeDiagrams.at(i));
|
|
QCoreApplication::processEvents(QEventLoop::ExcludeUserInputEvents);
|
|
}
|
|
diagram_project->undoStack()->endMacro();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void QETDiagramEditor::moveDiagramDownx100(const QList<Diagram *> &diagrams) {
|
|
if (diagrams.isEmpty()) return;
|
|
QList<Diagram *> safeDiagrams = diagrams;
|
|
if (QETProject *diagram_project = safeDiagrams.first()->project()) {
|
|
if (!diagram_project->isReadOnly()) {
|
|
if (ProjectView *project_view = findProject(diagram_project)) {
|
|
// Backward loop for moving down
|
|
diagram_project->undoStack()->beginMacro(tr("Move the sheets"));
|
|
for (int i = safeDiagrams.size() - 1; i >= 0; --i) {
|
|
project_view->moveDiagramDownx100(safeDiagrams.at(i));
|
|
QCoreApplication::processEvents(QEventLoop::ExcludeUserInputEvents);
|
|
}
|
|
diagram_project->undoStack()->endMacro();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void QETDiagramEditor::reloadOldElementPanel()
|
|
{
|
|
pa->reloadAndFilter();
|
|
}
|
|
|
|
/**
|
|
Supprime le schema courant du projet courant
|
|
*/
|
|
void QETDiagramEditor::removeDiagramFromProject()
|
|
{
|
|
if (ProjectView *current_project = currentProjectView()) {
|
|
if (DiagramView *current_diagram = current_project -> currentDiagram()) {
|
|
current_project -> removeDiagram(current_diagram);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
@brief QETDiagramEditor::diagramWasAdded
|
|
Manage the adding of diagram view in a project
|
|
@param dv : added diagram view
|
|
*/
|
|
void QETDiagramEditor::diagramWasAdded(DiagramView *dv)
|
|
{
|
|
connect(dv->diagram(),
|
|
&QGraphicsScene::selectionChanged,
|
|
this,
|
|
&QETDiagramEditor::selectionChanged,
|
|
Qt::DirectConnection);
|
|
//Grouping leaves the selection as it is, so without this Group
|
|
//and Ungroup would keep the state from before (#1144)
|
|
connect(dv->diagram(),
|
|
&Diagram::itemGroupChanged,
|
|
this,
|
|
&QETDiagramEditor::slot_updateComplexActions);
|
|
connect(dv, &DiagramView::modeChanged, this, &QETDiagramEditor::slot_updateModeActions);
|
|
connect(dv, &DiagramView::elementPlacementStarted, this, &QETDiagramEditor::rememberPlacedElement);
|
|
}
|
|
|
|
/**
|
|
@brief QETDiagramEditor::findElementInPanel
|
|
Find the item for location in the element panel
|
|
@param location
|
|
*/
|
|
void QETDiagramEditor::findElementInPanel(const ElementsLocation &location)
|
|
{
|
|
m_element_collection_widget->setCurrentLocation(location);
|
|
}
|
|
|
|
/**
|
|
Show the error message contained in \a result.
|
|
*/
|
|
void QETDiagramEditor::showError(const QETResult &result)
|
|
{
|
|
if (result.isOk()) return;
|
|
showError(result.errorMessage());
|
|
}
|
|
|
|
/**
|
|
Show the \a error message.
|
|
*/
|
|
void QETDiagramEditor::showError(const QString &error)
|
|
{
|
|
if (error.isEmpty()) return;
|
|
QET::QetMessageBox::critical(this, tr("Error", "message box title"), error);
|
|
}
|
|
|
|
/**
|
|
@brief QETDiagramEditor::subWindowActivated
|
|
Slot used to update menu and undo stack when subwindows of MDIarea was activated
|
|
@param subWindows
|
|
*/
|
|
void QETDiagramEditor::subWindowActivated(QMdiSubWindow *subWindows)
|
|
{
|
|
Q_UNUSED(subWindows)
|
|
|
|
slot_updateActions();
|
|
slot_updateWindowsMenu();
|
|
emit syncElementsPanel();
|
|
updateUsageTrackersActiveState();
|
|
updateWindowModifiedState();
|
|
}
|
|
|
|
/**
|
|
@brief QETDiagramEditor::updateUsageTrackersActiveState
|
|
Mark the currently active project's usage tracker (time spent on this
|
|
project) as active, and every other opened project's tracker as
|
|
inactive. Called whenever the current MDI subwindow changes.
|
|
|
|
Known limitation: this only accounts for tab switches within this
|
|
QETDiagramEditor window. If the same project were ever shown as the
|
|
active tab in two different windows at once, its tracked time could be
|
|
double-counted -- today QETApp only ever gives a project one ProjectView,
|
|
so this doesn't happen in practice.
|
|
*/
|
|
void QETDiagramEditor::updateUsageTrackersActiveState()
|
|
{
|
|
QETProject *active_project = currentProject();
|
|
const QList<ProjectView *> project_views = openedProjects();
|
|
for (ProjectView *project_view : project_views) {
|
|
if (QETProject *project = project_view->project()) {
|
|
project->projectPropertiesHandler().usageTracker().setActive(project == active_project);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
@brief QETDiagramEditor::updateWindowModifiedState
|
|
Reflect the currently active project's unsaved-changes state in the
|
|
main window's title and native "document modified" indicator (e.g.
|
|
the dot in the close button on macOS). Called whenever the active
|
|
project changes, or whenever the active project's own modified state
|
|
changes.
|
|
|
|
The window title's "[*]" placeholder is Qt's own convention: combined
|
|
with setWindowModified(), it lets each platform render the modified
|
|
indicator its own way (or not at all, on platforms without one)
|
|
without QET having to draw anything itself.
|
|
*/
|
|
void QETDiagramEditor::updateWindowModifiedState()
|
|
{
|
|
if (QETProject *project = currentProject()) {
|
|
setWindowTitle(QString("%1[*] - %2").arg(
|
|
project->pathNameTitle(),
|
|
tr("QElectroTech", "window title")));
|
|
setWindowModified(project->projectWasModified());
|
|
} else {
|
|
setWindowTitle(tr("QElectroTech", "window title"));
|
|
setWindowModified(false);
|
|
}
|
|
}
|
|
|
|
/**
|
|
@brief QETDiagramEditor::selectionChanged
|
|
This slot is called when a diagram selection was changed.
|
|
*/
|
|
void QETDiagramEditor::selectionChanged()
|
|
{
|
|
slot_updateComplexActions();
|
|
|
|
DiagramView *dv = currentDiagramView();
|
|
if (dv && dv->diagram())
|
|
m_selection_properties_editor->setDiagram(dv->diagram());
|
|
}
|
|
|
|
|
|
/**
|
|
@brief QETDiagramEditor::insertElementFromCollection
|
|
Place @a location on the current folio using the interactive placement
|
|
mode -- the same mode a drag and drop ends in, entered without the drag.
|
|
@param location
|
|
*/
|
|
void QETDiagramEditor::insertElementFromCollection(const ElementsLocation &location)
|
|
{
|
|
DiagramView *dv = currentDiagramView();
|
|
if (dv && dv->startElementPlacement(location, dv->defaultPlacementPos())) {
|
|
return;
|
|
}
|
|
|
|
//Nowhere to place it: no folio open, or a read-only project. Do what
|
|
//a double click did before it placed, rather than nothing.
|
|
m_element_collection_widget->editLocation(location);
|
|
}
|
|
|
|
/**
|
|
@brief QETDiagramEditor::rememberPlacedElement
|
|
Remember @a location for "insert last element". Connected to every view,
|
|
so an element placed by drag and drop counts as well as one placed from
|
|
the collection without a drag. Macros never reach here: the view does not
|
|
report them, since re-entering their placement mode from a shortcut has
|
|
not been thought through.
|
|
@param location
|
|
*/
|
|
void QETDiagramEditor::rememberPlacedElement(const ElementsLocation &location)
|
|
{
|
|
m_last_inserted_element = location;
|
|
m_insert_last_element->setEnabled(true);
|
|
setLastCommand(m_insert_last_element);
|
|
}
|
|
|
|
/**
|
|
@brief QETDiagramEditor::setLastCommand
|
|
Remember @a action as the command Enter repeats, and name it in the
|
|
Édition menu entry so it is clear what will happen.
|
|
*/
|
|
void QETDiagramEditor::setLastCommand(QAction *action)
|
|
{
|
|
m_last_command = action;
|
|
m_repeat_last_command->setText(
|
|
action == m_insert_last_element
|
|
? tr("Repeat: insert \"%1\"").arg(m_last_inserted_element.name())
|
|
: tr("Repeat: %1").arg(action->text().remove(QLatin1Char('&'))));
|
|
slot_updateActions();
|
|
}
|
|
|
|
/**
|
|
@brief QETDiagramEditor::repeatLastCommand
|
|
Run the last drawing or placing command again -- a line, a text field,
|
|
the last element -- as Enter does in SolidWorks.
|
|
@return true if a command was run
|
|
*/
|
|
bool QETDiagramEditor::repeatLastCommand()
|
|
{
|
|
if (!m_last_command || !m_repeat_last_command->isEnabled()) {
|
|
return false;
|
|
}
|
|
//A tool that is still on would be switched off by triggering it
|
|
if (m_last_command->isCheckable() && m_last_command->isChecked()) {
|
|
return false;
|
|
}
|
|
m_last_command->trigger();
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
@brief QETDiagramEditor::insertLastElement
|
|
Re-enter placement mode with the element placed most recently, so a run of
|
|
the same symbol can be dropped without returning to the collection.
|
|
*/
|
|
void QETDiagramEditor::insertLastElement()
|
|
{
|
|
if (!m_last_inserted_element.isElement()
|
|
|| !m_last_inserted_element.exist()) {
|
|
return;
|
|
}
|
|
insertElementFromCollection(m_last_inserted_element);
|
|
}
|
|
|
|
/**
|
|
@brief QETDiagramEditor::elementPicker
|
|
@return the element picker, built on first use: most sessions never open
|
|
it, and it holds a list view and a model of its own.
|
|
*/
|
|
ElementPickerPopup *QETDiagramEditor::elementPicker()
|
|
{
|
|
if (!m_element_picker)
|
|
{
|
|
m_element_picker = new ElementPickerPopup(m_element_collection_widget,
|
|
this);
|
|
connect(m_element_picker, &ElementPickerPopup::elementChosen,
|
|
this, &QETDiagramEditor::insertElementFromCollection);
|
|
}
|
|
return m_element_picker;
|
|
}
|
|
|
|
/**
|
|
@brief QETDiagramEditor::showElementPicker
|
|
Open the element picker where the mouse is.
|
|
*/
|
|
void QETDiagramEditor::showElementPicker()
|
|
{
|
|
if (!currentDiagramView()) {
|
|
return;
|
|
}
|
|
|
|
elementPicker()->popUpAt(QCursor::pos());
|
|
}
|
|
|
|
/**
|
|
@brief QETDiagramEditor::showShortcutBar
|
|
Open the element picker where the mouse is, with a row of commands above
|
|
it chosen by what is selected: the folio's commands with nothing
|
|
selected, conductor commands when only conductors are, and selection
|
|
commands otherwise. The commands are ShortcutManager ids, listed in
|
|
ShortcutBarSettings and editable in the configuration dialog.
|
|
*/
|
|
void QETDiagramEditor::showShortcutBar()
|
|
{
|
|
DiagramView *dv = currentDiagramView();
|
|
if (!dv) {
|
|
return;
|
|
}
|
|
|
|
const ShortcutBarSettings::Context context =
|
|
ShortcutBarSettings::contextFor(dv->diagram()->selectedItems());
|
|
|
|
elementPicker()->popUpShortcutBar(QCursor::pos(), context);
|
|
}
|
|
|
|
/**
|
|
@brief QETDiagramEditor::generateTerminalBlock
|
|
*/
|
|
void QETDiagramEditor::generateTerminalBlock()
|
|
{
|
|
#ifdef TODO_LIST
|
|
# pragma message("@TODO Merge 'qet_tb_generator' code in to Qet")
|
|
# pragma message("https://github.com/qelectrotech/qet_tb_generator")
|
|
#endif
|
|
|
|
bool success = false;
|
|
QList<QString> exeList;
|
|
QProcess *process = new QProcess(qApp);
|
|
|
|
#if defined(Q_OS_WIN32) || defined(Q_OS_WIN64)
|
|
exeList << (QETApp::dataDir() + "/binary/qet_tb_generator.exe")
|
|
<< (QDir::currentPath() + "/qet_tb_generator.exe")
|
|
<< QStandardPaths::findExecutable("qet_tb_generator.exe")
|
|
<< "qet_tb_generator.exe"
|
|
<< "qet_tb_generator"; // from original code: missing ".exe" ???
|
|
#elif defined(Q_OS_MACOS)
|
|
exeList << (QETApp::dataDir() + "/binary/qet_tb_generator")
|
|
<< (QDir::currentPath() + "/qet_tb_generator")
|
|
<< QStandardPaths::findExecutable("qet_tb_generator")
|
|
<< (QDir::homePath() + "/.qet/qet_tb_generator.app")
|
|
<< "/Library/Frameworks/Python.framework/Versions/3.11/bin/qet_tb_generator";
|
|
#else
|
|
exeList << (QETApp::dataDir() + "/binary/qet_tb_generator")
|
|
<< (QDir::currentPath() + "/qet_tb_generator")
|
|
<< (QDir::homePath() + "/.qet/qet_tb_generator")
|
|
<< QStandardPaths::findExecutable("qet_tb_generator")
|
|
<< "qet_tb_generator";
|
|
#endif
|
|
|
|
// If launched under control:
|
|
//connect(process, SIGNAL(errorOcurred(int error)), this, SLOT(slot_generateTerminalBlock_error()));
|
|
//process->start("qet_tb_generator");
|
|
|
|
qInfo() << " project to use for qet_tb_generator: "
|
|
<< (QETDiagramEditor::currentProjectView()->project()->filePath());
|
|
|
|
if (openedProjects().count()) {
|
|
foreach(QString exe, exeList) {
|
|
if ((success == false) && exe.length() && QFile::exists(exe)) {
|
|
success = process->startDetached(exe, {(QETDiagramEditor::currentProjectView()->project()->filePath())});
|
|
}
|
|
if (success == true) {
|
|
qInfo() << " qet_tb_generator found here:" << exe;
|
|
break;
|
|
} else {
|
|
qInfo() << " qet_tb_generator not found :" << exe;
|
|
}
|
|
}
|
|
} else {
|
|
qInfo() << "No project loaded - no need to start \"qet_tb_generator\"";
|
|
}
|
|
process->close();
|
|
|
|
#if defined(Q_OS_WIN32) || defined(Q_OS_WIN64)
|
|
QString message=QObject::tr(
|
|
"To install the plugin qet_tb_generator"
|
|
"<br>Visit :"
|
|
"<br>"
|
|
"<a href='https://pypi.python.org/pypi/qet-tb-generator'>qet-tb-generator</a>"
|
|
"<br>Requires python 3.5 or above."
|
|
"<br><B><U> First install on Windows</B></U>"
|
|
"<br>1. Install, if required, python 3.5 or above"
|
|
"<br> Visit :"
|
|
"<br>"
|
|
"<a href='https://www.python.org/downloads/'>python.org</a>"
|
|
"<br>2. pip install qet_tb_generator"
|
|
"<br><B><U> Update on Windows</B></U>"
|
|
"<br>python -m pip install --upgrade qet_tb_generator"
|
|
"<br>"
|
|
">>user could launch in a terminal this script in this directory"
|
|
"<br>"
|
|
" C:\\users\\XXXX\\AppData\\Local\\Programs\\Python\\Python36-32\\Scripts "
|
|
"<br>");
|
|
#elif defined(Q_OS_MACOS)
|
|
QString message=QObject::tr(
|
|
"To install the plugin qet_tb_generator"
|
|
"<br>Visit :"
|
|
"<br>"
|
|
"<a href='https://pypi.python.org/pypi/qet-tb-generator'>qet-tb-generator</a>"
|
|
"<br><B><U> First install on macOSX</B></U>"
|
|
"<br>1. Install, if required, python 3.11 bundle only, "
|
|
"<a href='https://www.python.org/ftp/python/3.11.2/python-3.11.2-macos11.pkg'>python-3.11.2-macos11.pkg</a>"
|
|
"<br>2 Run Profile.command script"
|
|
"<br>"
|
|
"because program use hardcoded PATH for localise qet-tb-generator plugin "
|
|
"<br> Visit :"
|
|
"<br>"
|
|
"<a href='https://qelectrotech.org/forum/viewtopic.php?pid=5674#p5674'>howto</a>"
|
|
"<br>2. pip3 install qet_tb_generator"
|
|
"<br><B><U> Update on macOSX</B></U>"
|
|
"<br> pip3 install --upgrade qet_tb_generator"
|
|
"<br>");
|
|
#else
|
|
QString message=QObject::tr(
|
|
"To install the plugin qet_tb_generator"
|
|
"<br>Visit :"
|
|
"<br>"
|
|
"<a href='https://pypi.python.org/pypi/qet-tb-generator'>qet-tb-generator</a>"
|
|
"<br>"
|
|
"<br>Requires python 3.5 or above."
|
|
"<br>"
|
|
"<br><B><U> First install on Linux</B>""</U>"
|
|
"<br>1. check you have pip3 installed: pip3 --version"
|
|
"<br>If not install with: sudo apt-get install python3-pip"
|
|
"<br>2. Install the program: sudo pip3 install qet_tb_generator"
|
|
"<br>3. Run the program: qet_tb_generator"
|
|
"<br>"
|
|
"<br><B>""<U> Update on Linux</B>""</U>"
|
|
"<br>sudo pip3 install --upgrade qet_tb_generator"
|
|
"<br>");
|
|
#endif
|
|
if ( !success ) {
|
|
QMessageBox::warning(nullptr,
|
|
QObject::tr("Error launching qet_tb_generator plugin"),
|
|
message);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief QETDiagramEditor::slot_terminalNumbering
|
|
* Opens the dialog for automatic terminal numbering and applies the generated undo command.
|
|
*/
|
|
void QETDiagramEditor::slot_terminalNumbering() {
|
|
QETProject *project = currentProject();
|
|
if (!project) return;
|
|
|
|
TerminalNumberingDialog dialog(this, project);
|
|
if (dialog.exec() == QDialog::Accepted) {
|
|
// Fetch the generated undo command from the dialog logic
|
|
QUndoCommand *macro = dialog.getUndoCommand(project);
|
|
|
|
// If changes were made, push them to the global undo stack
|
|
if (macro) {
|
|
undo_group.activeStack()->push(macro);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
@brief QETDiagramEditor::slot_reloadElementDrawings
|
|
Redraw every placed element of the current project from its current
|
|
definition (bugtracker #802): a symbol edited and saved after being
|
|
placed keeps showing its old drawing otherwise, until the whole project
|
|
is closed and reopened.
|
|
|
|
Purely visual and not undoable, the way pressing a "refresh" button
|
|
would be: nothing is pushed on the undo stack and the project is not
|
|
marked as modified. Elements whose size, hotspot or terminals changed
|
|
are skipped and listed: they must be removed and re-inserted, which
|
|
deletes the conductors already connected to them.
|
|
*/
|
|
void QETDiagramEditor::slot_reloadElementDrawings() {
|
|
QETProject *project = currentProject();
|
|
if (!project) return;
|
|
|
|
QList<Element *> elements;
|
|
QSet<QString> dropped_locations;
|
|
for (Diagram *diagram : project->diagrams())
|
|
{
|
|
DiagramContent content(diagram, false);
|
|
for (Element *elmt : content.m_elements)
|
|
{
|
|
elements << elmt;
|
|
const QString key = elmt->location().toString();
|
|
if (!dropped_locations.contains(key))
|
|
{
|
|
ElementPictureFactory::instance()->dropCache(elmt->location());
|
|
dropped_locations.insert(key);
|
|
}
|
|
}
|
|
}
|
|
|
|
int reloaded = 0;
|
|
int unavailable = 0;
|
|
QStringList geometry_changed;
|
|
for (Element *elmt : elements)
|
|
{
|
|
switch (elmt->reloadPicture())
|
|
{
|
|
case Element::ReloadPictureResult::Reloaded:
|
|
++reloaded;
|
|
break;
|
|
case Element::ReloadPictureResult::Unavailable:
|
|
++unavailable;
|
|
break;
|
|
case Element::ReloadPictureResult::GeometryChanged:
|
|
{
|
|
const Diagram *diagram = elmt->diagram();
|
|
const QString folio = diagram
|
|
? tr("sheet %1").arg(project->folioIndex(diagram) + 1)
|
|
: QString();
|
|
geometry_changed << QStringLiteral("%1 (%2)").arg(elmt->name(), folio);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
QString message = tr("%n elements redrawn.", "", reloaded);
|
|
|
|
if (unavailable) {
|
|
message += QStringLiteral("\n\n")
|
|
% tr("%n elements for which no definition could be found or which "
|
|
"were illegible: their current design has been retained.", "", unavailable);
|
|
}
|
|
|
|
if (geometry_changed.isEmpty())
|
|
{
|
|
QET::QetMessageBox::information(
|
|
this, tr("Reload element drawings"), message);
|
|
return;
|
|
}
|
|
|
|
message += QStringLiteral("\n\n")
|
|
% tr("%n elements not redrawn: their size, grip point or terminals have "
|
|
"changed (a terminal was added, removed or moved).",
|
|
"", geometry_changed.size())
|
|
% QStringLiteral("\n\n")
|
|
% tr("To update them, they must be deleted then reinserted. Warning: "
|
|
"this operation removes the conductors already connected to these "
|
|
"elements, which will need to be redrawn.");
|
|
|
|
//The full list goes in the expandable, scrollable details area
|
|
//so the dialog stays readable on large projects.
|
|
QMessageBox box(QMessageBox::Warning,
|
|
tr("Reload element drawings"),
|
|
message,
|
|
QMessageBox::Ok,
|
|
this);
|
|
box.setDetailedText(geometry_changed.join(QLatin1Char('\n')));
|
|
box.exec();
|
|
}
|
|
|
|
#ifdef QET_HAS_SCRIPTING
|
|
/**
|
|
@brief QETDiagramEditor::slot_runScript
|
|
Run a JavaScript macro against the current project (bugtracker #162).
|
|
See QetScriptApi for what a script can do -- read-mostly: folio/element/
|
|
conductor counts and the same export operations the --export-* CLI
|
|
flags provide. Export/save calls inside the script act on this
|
|
project's file on disk, so unsaved edits in the open editor are not
|
|
visible to the script; save first if that matters.
|
|
*/
|
|
void QETDiagramEditor::slot_runScript() {
|
|
QETProject *project = currentProject();
|
|
if (!project) return;
|
|
|
|
if (!ensureScriptingEnabled(tr("Run a script"))) return;
|
|
|
|
const QString script_path = QFileDialog::getOpenFileName(
|
|
this,
|
|
tr("Run a script"),
|
|
QString(),
|
|
tr("JavaScript scripts (*.js);;All files (*)")
|
|
);
|
|
if (script_path.isEmpty()) return;
|
|
|
|
QetScripting::runOnProject(script_path, project, currentDiagramView());
|
|
}
|
|
|
|
/**
|
|
@brief QETDiagramEditor::ensureScriptingEnabled
|
|
@return true if scripts may run, asking to switch them on if they are
|
|
off. Shared by "Run a script..." and the stored script buttons, so
|
|
there is one prompt to keep right.
|
|
*/
|
|
bool QETDiagramEditor::ensureScriptingEnabled(const QString &title)
|
|
{
|
|
// Scripting is off until somebody says otherwise, so the first use has
|
|
// to ask. Asking here rather than greying the action out keeps the
|
|
// feature discoverable: a disabled menu entry tells a user that
|
|
// something exists and nothing about how to have it.
|
|
if (!QetSettings::scriptingEnabled()) {
|
|
const QMessageBox::StandardButton answer = QET::QetMessageBox::question(
|
|
this,
|
|
title,
|
|
tr("Scripts are disabled.\n"
|
|
"\n"
|
|
"A script runs with your permissions: it can read and "
|
|
"modify the open project and write files. Only run "
|
|
"scripts whose origin you know.\n"
|
|
"\n"
|
|
"Enable scripts? This setting can be changed in "
|
|
"Configure QElectroTech > General > Projects."),
|
|
QMessageBox::Yes | QMessageBox::Cancel,
|
|
QMessageBox::Cancel);
|
|
if (answer != QMessageBox::Yes) {
|
|
return false;
|
|
}
|
|
QetSettings::setScriptingEnabled(true);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
@brief QETDiagramEditor::runStoredScript
|
|
Run the stored script at @a path on the current project, as one undo
|
|
step named after the script. The file is read again on every run, so
|
|
an edit to it takes effect on the next click.
|
|
*/
|
|
void QETDiagramEditor::runStoredScript(const QString &path, const QString &name)
|
|
{
|
|
QETProject *project = currentProject();
|
|
if (!project) return;
|
|
if (!ensureScriptingEnabled(name)) return;
|
|
QetScripting::runOnProject(path, project, currentDiagramView(), name);
|
|
}
|
|
|
|
/**
|
|
@brief QETDiagramEditor::rebuildScriptActions
|
|
One action per stored script, in the Projet > Scripts menu and on the
|
|
Scripts toolbar, registered with ShortcutManager under
|
|
diagrameditor.script.<file name>: that one registration is what lists
|
|
it in the shortcut settings, the shortcut bar (S) and command search.
|
|
Rebuilt from scratch whenever the scripts folder changes.
|
|
*/
|
|
void QETDiagramEditor::rebuildScriptActions()
|
|
{
|
|
for (QAction *action : std::as_const(m_script_actions)) {
|
|
ShortcutManager::instance().unregisterAction(
|
|
action, action->property("qet_script_action_id").toString());
|
|
delete action;
|
|
}
|
|
m_script_actions.clear();
|
|
m_scripts_menu->clear();
|
|
m_scripts_tool_bar->clear();
|
|
|
|
const ScriptLibrary &library = ScriptLibrary::instance();
|
|
for (const ScriptLibrary::Script &script : library.scripts()) {
|
|
const ScriptHeader &h = script.header;
|
|
auto *action = new QAction(ScriptLibrary::icon(script), h.name, this);
|
|
action->setStatusTip(h.tooltip);
|
|
action->setToolTip(h.tooltip.isEmpty() ? h.name : h.name + QLatin1Char('\n') + h.tooltip);
|
|
action->setProperty("qet_script_action_id", ScriptLibrary::actionId(h.id));
|
|
action->setProperty("qet_script_context", h.context);
|
|
connect(action, &QAction::triggered, this, [this, path = script.path, name = h.name]() {
|
|
runStoredScript(path, name);
|
|
});
|
|
ShortcutManager::instance().registerAction(action, ScriptLibrary::actionId(h.id),
|
|
tr("Scripts"),
|
|
QKeySequence::fromString(h.shortcut));
|
|
m_scripts_menu->addAction(action);
|
|
m_scripts_tool_bar->addAction(action);
|
|
m_script_actions << action;
|
|
}
|
|
|
|
//A script with a header that cannot be used gets no button: say
|
|
//which file and why, where its author will look for the button.
|
|
const QStringList errors = library.errors();
|
|
if (!errors.isEmpty()) {
|
|
m_scripts_menu->addSeparator();
|
|
for (const QString &error : errors) {
|
|
QAction *ignored = m_scripts_menu->addAction(QET::Icons::DialogInformation,
|
|
tr("Ignored: %1").arg(error));
|
|
ignored->setEnabled(false);
|
|
}
|
|
}
|
|
|
|
m_scripts_menu->addSeparator();
|
|
m_scripts_menu->addAction(m_record_macro);
|
|
m_scripts_menu->addAction(m_manage_scripts);
|
|
m_scripts_menu->addAction(m_run_script);
|
|
m_scripts_menu->addAction(m_open_scripts_folder);
|
|
|
|
//Show the toolbar when the first script arrives, hide it when the
|
|
//last one goes; in between it is the user's to show or hide.
|
|
const bool has_scripts = !m_script_actions.isEmpty();
|
|
if (has_scripts != m_had_scripts) m_scripts_tool_bar->setVisible(has_scripts);
|
|
m_had_scripts = has_scripts;
|
|
|
|
rebuildToolBars();
|
|
updateScriptActions();
|
|
}
|
|
|
|
/**
|
|
@brief QETDiagramEditor::setUpMacroRecorder
|
|
While recording: the action checked, and "● Enregistrement : N étapes"
|
|
with Arrêter on the status bar. At the end, where it was saved and the
|
|
request to paste into the assistant.
|
|
*/
|
|
void QETDiagramEditor::setUpMacroRecorder()
|
|
{
|
|
auto *box = new QWidget(this);
|
|
auto *layout = new QHBoxLayout(box);
|
|
layout->setContentsMargins(0, 0, 0, 0);
|
|
auto *label = new QLabel(box);
|
|
label->setStyleSheet(QStringLiteral("color: #d03030; font-weight: bold"));
|
|
auto *stop = new QToolButton(box);
|
|
stop->setText(tr("Stop"));
|
|
stop->setToolTip(tr("Stop recording the macro"));
|
|
layout->addWidget(label);
|
|
layout->addWidget(stop);
|
|
statusBar()->addPermanentWidget(box);
|
|
box->hide();
|
|
connect(stop, &QToolButton::clicked, this, []() { MacroRecorder::instance().stop(); });
|
|
|
|
connect(&MacroRecorder::instance(), &MacroRecorder::stateChanged, box,
|
|
[this, box, label](bool recording, int steps) {
|
|
box->setVisible(recording);
|
|
label->setText(tr("● Recording: %n steps", nullptr, steps));
|
|
const QSignalBlocker blocker(m_record_macro);
|
|
m_record_macro->setChecked(recording);
|
|
updateScriptActions();
|
|
});
|
|
connect(&MacroRecorder::instance(), &MacroRecorder::finished, this,
|
|
[this](const QJsonObject &recording, QETDiagramEditor *editor) {
|
|
if (editor == this) macroRecorded(recording);
|
|
});
|
|
}
|
|
|
|
void QETDiagramEditor::macroRecorded(const QJsonObject &recording)
|
|
{
|
|
const int steps = recording.value(QStringLiteral("steps")).toArray().size();
|
|
const QString dir = recording.value(QStringLiteral("folder")).toString();
|
|
QMessageBox box(QMessageBox::Information, tr("Macro recorded"),
|
|
tr("“%1”: %n steps.\n"
|
|
"\n"
|
|
"To make a script of it, ask your AI assistant: the button below "
|
|
"copies the request, just paste it into its window.", nullptr, steps)
|
|
.arg(recording.value(QStringLiteral("name")).toString()),
|
|
QMessageBox::NoButton, this);
|
|
box.setInformativeText(QDir::toNativeSeparators(dir)
|
|
+ (recording.value(QStringLiteral("complete")).toBool()
|
|
? QString() : QStringLiteral("\n") + recording.value(QStringLiteral("note")).toString()));
|
|
QPushButton *copy = box.addButton(tr("&Copy the request for the assistant"), QMessageBox::AcceptRole);
|
|
QPushButton *open = box.addButton(tr("&Open the folder"), QMessageBox::ActionRole);
|
|
box.addButton(tr("&Close"), QMessageBox::RejectRole);
|
|
box.setDefaultButton(copy);
|
|
box.exec();
|
|
if (box.clickedButton() == copy) {
|
|
QGuiApplication::clipboard()->setText(MacroRecorder::assistantRequest(recording));
|
|
statusBar()->showMessage(tr("Request copied: paste it into the assistant's window"), 8000);
|
|
} else if (box.clickedButton() == open) {
|
|
QDesktopServices::openUrl(QUrl::fromLocalFile(dir));
|
|
}
|
|
}
|
|
|
|
/**
|
|
@brief QETDiagramEditor::setUpLiveIndicator
|
|
"Assistant connecté" and a Stop button on the status bar, shown
|
|
whenever live mode is open, so it is never on without being seen.
|
|
*/
|
|
void QETDiagramEditor::setUpLiveIndicator()
|
|
{
|
|
auto *box = new QWidget(this);
|
|
auto *layout = new QHBoxLayout(box);
|
|
layout->setContentsMargins(0, 0, 0, 0);
|
|
auto *label = new QLabel(box);
|
|
auto *stop = new QToolButton(box);
|
|
stop->setText(tr("Stop"));
|
|
stop->setToolTip(tr("Close the assistant's connection for the rest of the session"));
|
|
layout->addWidget(label);
|
|
layout->addWidget(stop);
|
|
statusBar()->addPermanentWidget(box);
|
|
connect(stop, &QToolButton::clicked, this, []() { LiveServer::instance().stop(); });
|
|
|
|
auto update = [box, label](LiveServer::State state) {
|
|
box->setVisible(state != LiveServer::Off);
|
|
label->setText(state == LiveServer::Connected
|
|
? tr("Live mode: assistant connected")
|
|
: tr("Live mode: waiting for an assistant"));
|
|
label->setStyleSheet(state == LiveServer::Connected
|
|
? QStringLiteral("font-weight: bold") : QString());
|
|
};
|
|
connect(&LiveServer::instance(), &LiveServer::stateChanged, box, update);
|
|
update(LiveServer::instance().state());
|
|
|
|
//The Assistant dock: every action, and whether to be asked first
|
|
auto *dock = new QDockWidget(tr("Assistant"), this);
|
|
dock->setObjectName(QStringLiteral("assistant_dock"));
|
|
auto *content = new QWidget(dock);
|
|
auto *dock_layout = new QVBoxLayout(content);
|
|
auto *ask = new QCheckBox(tr("Ask before running a script the assistant wrote"), content);
|
|
ask->setChecked(LiveServer::instance().askFirst());
|
|
ask->setToolTip(tr("Remembered after a restart"));
|
|
auto *log = new QListWidget(content);
|
|
log->setWordWrap(true);
|
|
dock_layout->addWidget(ask);
|
|
dock_layout->addWidget(log);
|
|
dock->setWidget(content);
|
|
addDockWidget(Qt::RightDockWidgetArea, dock);
|
|
dock->hide();
|
|
connect(ask, &QCheckBox::toggled, &LiveServer::instance(), &LiveServer::setAskFirst);
|
|
connect(&LiveServer::instance(), &LiveServer::askFirstChanged, ask, &QCheckBox::setChecked);
|
|
connect(&LiveServer::instance(), &LiveServer::stateChanged, dock,
|
|
[dock](LiveServer::State state) {
|
|
if (state == LiveServer::Waiting) dock->show();
|
|
else if (state == LiveServer::Off) dock->hide();
|
|
});
|
|
connect(&LiveServer::instance(), &LiveServer::handled, log,
|
|
[log](const QJsonObject &request, const QJsonObject &answer) {
|
|
const QString cmd = request.value(QStringLiteral("cmd")).toString();
|
|
if (cmd == QLatin1String("status")) return;
|
|
const bool ok = answer.value(QStringLiteral("ok")).toBool();
|
|
QString what = answer.value(QStringLiteral("undo")).toString();
|
|
for (const char *key : {"name", "script", "action"})
|
|
if (what.isEmpty()) what = request.value(QLatin1String(key)).toString();
|
|
if (what.isEmpty()) what = cmd;
|
|
auto *item = new QListWidgetItem(QStringLiteral("%1 %2 %3")
|
|
.arg(QTime::currentTime().toString(QStringLiteral("HH:mm:ss")),
|
|
ok ? QStringLiteral("✓") : QStringLiteral("✗"), what));
|
|
QStringList details;
|
|
if (!ok) details << answer.value(QStringLiteral("error")).toString();
|
|
const QJsonArray lines = answer.value(QStringLiteral("log")).toArray();
|
|
for (const QJsonValue &l : lines) details << l.toString();
|
|
const QString source = request.value(QStringLiteral("source")).toString();
|
|
if (!source.isEmpty()) details << QString() << source;
|
|
item->setToolTip(details.join(QLatin1Char('\n')));
|
|
item->setData(Qt::UserRole, details.join(QLatin1Char('\n')));
|
|
log->insertItem(0, item);
|
|
});
|
|
connect(log, &QListWidget::itemDoubleClicked, this, [this](QListWidgetItem *item) {
|
|
QET::QetMessageBox::information(this, tr("Assistant"),
|
|
item->data(Qt::UserRole).toString());
|
|
});
|
|
//What the assistant just did, where the user is already looking
|
|
connect(&LiveServer::instance(), &LiveServer::handled, box,
|
|
[label](const QJsonObject &request, const QJsonObject &answer) {
|
|
const QString cmd = request.value(QStringLiteral("cmd")).toString();
|
|
if (cmd == QLatin1String("status")) return;
|
|
QString what = answer.value(QStringLiteral("undo")).toString();
|
|
if (what.isEmpty())
|
|
what = request.value(QStringLiteral("name")).toString();
|
|
if (what.isEmpty())
|
|
what = request.value(QStringLiteral("script")).toString();
|
|
if (what.isEmpty())
|
|
what = request.value(QStringLiteral("action")).toString();
|
|
if (what.isEmpty())
|
|
what = cmd;
|
|
label->setText(tr("Live mode: %1 %2 at %3")
|
|
.arg(answer.value(QStringLiteral("ok")).toBool() ? QStringLiteral("✓")
|
|
: QStringLiteral("✗"),
|
|
what, QTime::currentTime().toString(QStringLiteral("HH:mm:ss"))));
|
|
label->setToolTip(answer.value(QStringLiteral("error")).toString());
|
|
});
|
|
}
|
|
|
|
/**
|
|
@brief QETDiagramEditor::updateScriptActions
|
|
Enable each script for what its header's @context asks for: always
|
|
(canvas), with something selected (selection), or with a conductor
|
|
selected (conductor). None without an open project.
|
|
*/
|
|
void QETDiagramEditor::updateScriptActions()
|
|
{
|
|
DiagramView *dv = currentDiagramView();
|
|
Diagram *diagram = dv ? dv->diagram() : nullptr;
|
|
const bool selection = diagram && !diagram->selectedItems().isEmpty();
|
|
const bool conductor = diagram && !diagram->selectedConductors().isEmpty();
|
|
m_record_macro->setEnabled(currentProject() || MacroRecorder::instance().isRecording());
|
|
for (QAction *action : std::as_const(m_script_actions)) {
|
|
const QString context = action->property("qet_script_context").toString();
|
|
bool enabled = currentProject() != nullptr;
|
|
if (context == QLatin1String("selection")) enabled = enabled && selection;
|
|
else if (context == QLatin1String("conductor")) enabled = enabled && conductor;
|
|
action->setEnabled(enabled);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
/**
|
|
@brief QETDiagramEditor::updateShownKinds
|
|
Tick the kinds shown in View > Show, and say in the status bar how many
|
|
are hidden, so that a hidden kind is never forgotten.
|
|
*/
|
|
void QETDiagramEditor::updateShownKinds()
|
|
{
|
|
for (QAction *action : m_shown_kinds_menu->actions())
|
|
action->setChecked(ShownKinds::isShown(ShownKinds::Kind(action->data().toInt())));
|
|
|
|
const int hidden = ShownKinds::hiddenCount();
|
|
m_hidden_kinds_label->setText(tr("%n kinds of items hidden", "", hidden));
|
|
m_hidden_kinds_label->setVisible(hidden > 0);
|
|
}
|
|
|
|
/**
|
|
@brief QETDiagramEditor::updateTextGridButton
|
|
Show the current text grid on its toolbar button and check it in its menu.
|
|
*/
|
|
void QETDiagramEditor::updateTextGridButton()
|
|
{
|
|
const qreal divisor = QSettings().value(TextGrid::settings_key, 1).toReal();
|
|
for (QAction *action : m_text_grid_menu->actions())
|
|
{
|
|
if (qFuzzyCompare(action->data().toReal() + 1, divisor + 1))
|
|
{
|
|
action->setChecked(true);
|
|
m_text_grid_button->setText(tr("Texts %1").arg(action->text()));
|
|
return;
|
|
}
|
|
}
|
|
//A divisor the menu does not offer, set by hand in the config file
|
|
m_text_grid_button->setText(tr("Texts %1").arg(TextGrid::ratioLabel(divisor)));
|
|
}
|