/*
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 .
*/
#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
#include
#include
#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
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#ifdef BUILD_WITHOUT_KF
# include "ui/nokde/kautosavefile.h"
#else
# include
#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 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(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(&QETDiagramEditor::activateProject));
connect(pa, &ElementsPanelWidget::requestForProjectClosing, this, qOverload(&QETDiagramEditor::closeProject));
connect(pa, &ElementsPanelWidget::requestForProjectPropertiesEdition, this, qOverload(&QETDiagramEditor::editProjectProperties));
connect(pa, &ElementsPanelWidget::requestForNewDiagram, this, &QETDiagramEditor::addDiagramToProject);
connect(pa, &ElementsPanelWidget::requestForNewDiagramAt, this, &QETDiagramEditor::addDiagramToProjectAt);
connect(pa, &ElementsPanelWidget::requestForDiagramPropertiesEdition, this, qOverload(&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> 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"));
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_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_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());
});
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("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 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 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 QETDiagramEditor::openedProjects() const
{
QList result;
QList window_list(m_workspace.subWindowList());
foreach(QMdiSubWindow *window, window_list) {
if (ProjectView *project_view = qobject_cast(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(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 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("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 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 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 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 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 texts = DiagramContent(diagram_).selectedTexts();
QList 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 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 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 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 selectedInformationElements(Diagram *diagram)
{
QList 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 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, QPair> 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(&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(&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 QETDiagramEditor::editedFiles() const
{
QList 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 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 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 list;
list << diagram;
removeDiagrams(list);
}
/**
* @brief QETDiagramEditor::removeDiagrams
* Deletes a list of folios with a single query.
*/
void QETDiagramEditor::removeDiagrams(const QList &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 &diagrams) {
if (diagrams.isEmpty()) return;
QList 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 &diagrams) {
if (diagrams.isEmpty()) return;
QList 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 &diagrams) {
if (diagrams.isEmpty()) return;
QList 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 &diagrams) {
if (diagrams.isEmpty()) return;
QList 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 &diagrams) {
if (diagrams.isEmpty()) return;
QList 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 &diagrams) {
if (diagrams.isEmpty()) return;
QList 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 &diagrams) {
if (diagrams.isEmpty()) return;
QList 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 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 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"
"
Visit :"
"
"
"qet-tb-generator"
"
Requires python 3.5 or above."
"
First install on Windows"
"
1. Install, if required, python 3.5 or above"
"
Visit :"
"
"
"python.org"
"
2. pip install qet_tb_generator"
"
Update on Windows"
"
python -m pip install --upgrade qet_tb_generator"
"
"
">>user could launch in a terminal this script in this directory"
"
"
" C:\\users\\XXXX\\AppData\\Local\\Programs\\Python\\Python36-32\\Scripts "
"
");
#elif defined(Q_OS_MACOS)
QString message=QObject::tr(
"To install the plugin qet_tb_generator"
"
Visit :"
"
"
"qet-tb-generator"
"
First install on macOSX"
"
1. Install, if required, python 3.11 bundle only, "
"python-3.11.2-macos11.pkg"
"
2 Run Profile.command script"
"
"
"because program use hardcoded PATH for localise qet-tb-generator plugin "
"
Visit :"
"
"
"howto"
"
2. pip3 install qet_tb_generator"
"
Update on macOSX"
"
pip3 install --upgrade qet_tb_generator"
"
");
#else
QString message=QObject::tr(
"To install the plugin qet_tb_generator"
"
Visit :"
"
"
"qet-tb-generator"
"
"
"
Requires python 3.5 or above."
"
"
"
First install on Linux"""
"
1. check you have pip3 installed: pip3 --version"
"
If not install with: sudo apt-get install python3-pip"
"
2. Install the program: sudo pip3 install qet_tb_generator"
"
3. Run the program: qet_tb_generator"
"
"
"
"" Update on Linux"""
"
sudo pip3 install --upgrade qet_tb_generator"
"
");
#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 elements;
QSet 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.: 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)));
}