mirror of
https://github.com/qelectrotech/qelectrotech-source-mirror.git
synced 2026-08-03 10:34:14 +02:00
5275fb44fe
Implements the first pillar of #574: a "Shortcuts" preferences page letting users rebind, search and reset every keyboard shortcut in the app. What it does - New ShortcutManager singleton: every one of the ~95 setShortcut()/ setShortcuts() call sites across qet.cpp, qetmainwindow.cpp, elementspanelwidget.cpp, autonumberingdockwidget.cpp, richtexteditor.cpp, qetdiagrameditor.cpp, qettemplateeditor.cpp and qetelementeditor.cpp now calls registerAction(target, id, category, default_sequence) instead, which applies the user's saved override (or the default) and remembers the target for later editing. - New ShortcutsConfigPage, added to the existing "Configurer QElectroTech" dialog: a filterable table of every registered shortcut, grouped by category, each with a QKeySequenceEdit and a per-row reset button, plus a "reset all" button. Bindings are only persisted (via ShortcutManager::setSequence()) when the dialog is accepted. - Conflict detection: rows whose currently-edited sequence collides with another row are highlighted with a tooltip naming the conflicting action. - Overrides are stored under a "shortcuts/" QSettings group, one key per id, keyed to match the id (not persisted at all when equal to the hardcoded default), so a future QET version can safely raise a default for anyone who never customized it. Design notes - Targets are handled generically via QObject rather than QAction, since one call site (autonumberingdockwidget's "Configurer" button) is a QPushButton, not a QAction. Both declare an identical "shortcut" QKeySequence Q_PROPERTY, so registerAction() reads/writes it through the property system instead of needing a separate code path. - Several live targets can share one id at once -- QET allows multiple windows of the same kind (diagram editor, element editor...) open simultaneously, each constructing its own QAction with the same id. setSequence() updates every live target for that id in one call, so a rebind takes effect in all open windows immediately, without restart. - A shortcut's description is captured from its target's text() the first time that id is registered, then cached -- so the config page stays correct even after the owning window is closed. One consequence: a shortcut belonging to an on-demand window (element editor, title block editor, rich text editor) only appears in the list once that window has been opened at least once in the current session, since nothing has registered its id yet otherwise. Testing Full CMake build (qmake CONFIG+=no_kf5, Qt 5.15) compiles clean with zero errors and zero new warnings. Verified end-to-end in a real running session (Xvfb + xdotool): - The Shortcuts page appears in Configure QElectroTech with the right icon, lists every always-registered shortcut with correct category/action name/ current binding. - The filter box correctly narrows the list, and correctly returns nothing for an action whose owning window hasn't been constructed yet this session (confirming the on-demand-registration behavior above is working as designed, not silently broken). - Conflict detection correctly flagged a real pre-existing same-key overlap between "Supprimer" (delete selection, Del) and "Supprimer ce folio" (delete diagram from panel, Del) -- both highlighted with explanatory tooltips. - Rebound "Manuel en ligne" to Ctrl+Shift+M, clicked OK: persisted under [shortcuts] in QElectroTech.conf, and the Aide menu's entry showed the new binding immediately, no restart needed. - Reopened the dialog: the rebind was still shown. Clicked its per-row reset button, then OK: the settings key was removed entirely (not stored as "F1"), correctly falling back to the hardcoded default. Retrofitting the Tab/Shift+Tab, select-all (#585) and Ctrl+G jump-to-element (#586) shortcuts through this registry is left for a follow-up once those PRs land, to avoid re-merging still-open branches into this one. Developed with assistance from Claude (Anthropic).
1547 lines
48 KiB
C++
1547 lines
48 KiB
C++
/*
|
|
Copyright 2006-2026 The QElectroTech Team
|
|
This file is part of QElectroTech.
|
|
|
|
QElectroTech is free software: you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation, either version 2 of the License, or
|
|
(at your option) any later version.
|
|
|
|
QElectroTech is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with QElectroTech. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
#include "qetelementeditor.h"
|
|
#include "ui_qetelementeditor.h"
|
|
#include "../elementscene.h"
|
|
#include "../../qeticons.h"
|
|
#include "../../shortcutmanager.h"
|
|
#include "../elementview.h"
|
|
#include "../../qetmessagebox.h"
|
|
#include "../../qetapp.h"
|
|
#include "../../recentfiles.h"
|
|
#include "../graphicspart/customelementpart.h"
|
|
#include "../elementitemeditor.h"
|
|
#include "../styleeditor.h"
|
|
#include "../esevent/eseventaddline.h"
|
|
#include "../esevent/eseventaddrect.h"
|
|
#include "../esevent/eseventaddellipse.h"
|
|
#include "../esevent/eseventaddpolygon.h"
|
|
#include "../esevent/eseventaddarc.h"
|
|
#include "../esevent/eseventaddtext.h"
|
|
#include "../esevent/eseventaddterminal.h"
|
|
#include "../esevent/eseventadddynamictextfield.h"
|
|
#include "../../elementdialog.h"
|
|
#include "../graphicspart/partterminal.h"
|
|
#include "../arceditor.h"
|
|
#include "ellipseeditor.h"
|
|
#include "lineeditor.h"
|
|
#include "polygoneditor.h"
|
|
#include "rectangleeditor.h"
|
|
#include "terminaleditor.h"
|
|
#include "texteditor.h"
|
|
#include "dynamictextfieldeditor.h"
|
|
#include "../../newelementwizard.h"
|
|
#include "../editorcommands.h"
|
|
#include "../../dxf/dxftoelmt.h"
|
|
#include "../../qet_elementscaler/qet_elementscaler.h"
|
|
#include "../UndoCommand/openelmtcommand.h"
|
|
|
|
#include <QSettings>
|
|
#include <QActionGroup>
|
|
|
|
/**
|
|
* @brief QETElementEditor::QETElementEditor
|
|
* @param parent
|
|
*/
|
|
QETElementEditor::QETElementEditor(QWidget *parent) :
|
|
QMainWindow(parent),
|
|
ui(new Ui::QETElementEditor)
|
|
{
|
|
ui->setupUi(this);
|
|
initGui();
|
|
setupActions();
|
|
setupConnection();
|
|
|
|
auto menu = createPopupMenu();
|
|
menu->setTearOffEnabled(true);
|
|
menu->setTitle(tr("Afficher", "menu entry"));
|
|
menu->setIcon(QET::Icons::ConfigureToolbars);
|
|
ui->m_display_menu->addMenu(menu);
|
|
//ui->m_display_menu->insertMenu(ui->m_zoom_in_action, menu);
|
|
|
|
setWindowState(Qt::WindowMaximized);
|
|
readSettings();
|
|
show();
|
|
}
|
|
|
|
/**
|
|
* @brief QETElementEditor::~QETElementEditor
|
|
*/
|
|
QETElementEditor::~QETElementEditor()
|
|
{
|
|
delete ui;
|
|
qDeleteAll(m_editors.begin(), m_editors.end());
|
|
}
|
|
|
|
/**
|
|
* @brief QETElementEditor::contextMenu
|
|
* Display a context menu, with all available action.
|
|
* @param p : the pos of the menu, in screen coordinate
|
|
* @param actions : a list of actions who can be prepended to the context menu.
|
|
*/
|
|
void QETElementEditor::contextMenu(QPoint p, QList<QAction *> actions)
|
|
{
|
|
QMenu menu(this);
|
|
menu.addActions(std::move(actions));
|
|
menu.addSeparator();
|
|
menu.addAction(m_undo_action);
|
|
menu.addAction(m_redo_action);
|
|
menu.addAction(ui->m_select_all_act);
|
|
menu.addAction(ui->m_deselect_all_action);
|
|
menu.addAction(ui->m_revert_selection_action);
|
|
menu.addSeparator();
|
|
menu.addAction(ui->m_delete_action);
|
|
menu.addAction(ui->m_cut_action);
|
|
menu.addAction(ui->m_copy_action);
|
|
menu.addSeparator();
|
|
menu.addAction((ui->m_mirror_action));
|
|
menu.addAction((ui->m_flip_action));
|
|
menu.addAction((ui->m_rotate_action));
|
|
menu.addSeparator();
|
|
menu.addAction(ui->m_paste_action);
|
|
menu.addAction(ui->m_paste_in_area_action);
|
|
menu.addMenu(ui->m_paste_from_menu);
|
|
menu.addSeparator();
|
|
menu.addActions(m_depth_action_group -> actions());
|
|
|
|
//Remove from the context menu the actions which are disabled.
|
|
const QList<QAction *>menu_actions = menu.actions();
|
|
for(QAction *action : menu_actions) {
|
|
if(!action -> isEnabled()) {
|
|
menu.removeAction(action);
|
|
}
|
|
}
|
|
menu.exec(p);
|
|
}
|
|
|
|
/**
|
|
* @brief QETElementEditor::setNames
|
|
* Set the names list of the edited element
|
|
* @param name_list
|
|
*/
|
|
void QETElementEditor::setNames(const NamesList &name_list)
|
|
{
|
|
auto data = m_elmt_scene->elementData();
|
|
data.m_names_list = name_list;
|
|
m_elmt_scene->setElementData(data);
|
|
}
|
|
|
|
/**
|
|
* @brief QETElementEditor::setLocation
|
|
* Set the location to edit
|
|
* @param location
|
|
*/
|
|
void QETElementEditor::setLocation(const ElementsLocation &location)
|
|
{
|
|
m_location = location;
|
|
m_opened_from_file = false;
|
|
setReadOnly(!location.isWritable());
|
|
updateTitle();
|
|
|
|
}
|
|
|
|
/**
|
|
* @brief QETElementEditor::location
|
|
* @return The location of the edited element
|
|
*/
|
|
ElementsLocation QETElementEditor::location() const {
|
|
return m_location;
|
|
}
|
|
|
|
/**
|
|
* @brief QETElementEditor::setFileName
|
|
* Set the file name of the edited element
|
|
* @param file_name
|
|
*/
|
|
void QETElementEditor::setFileName(const QString &file_name)
|
|
{
|
|
m_file_name = file_name;
|
|
m_opened_from_file = true;
|
|
bool must_be_read_only = !QFileInfo(file_name).isWritable();
|
|
if (isReadOnly() != must_be_read_only) {
|
|
setReadOnly(must_be_read_only);
|
|
}
|
|
updateTitle();
|
|
}
|
|
|
|
/**
|
|
* @brief QETElementEditor::fileName
|
|
* @return the file name of this element
|
|
*/
|
|
QString QETElementEditor::fileName() const {
|
|
return m_file_name;
|
|
}
|
|
|
|
void QETElementEditor::setReadOnly(bool ro)
|
|
{
|
|
m_read_only = ro;
|
|
m_view->setInteractive(!ro);
|
|
updateAction();
|
|
}
|
|
|
|
bool QETElementEditor::isReadOnly() const {
|
|
return m_read_only;
|
|
}
|
|
|
|
/**
|
|
* @brief QETElementEditor::fromFile
|
|
* Open an element from @filepath
|
|
* @param filepath
|
|
*/
|
|
void QETElementEditor::fromFile(const QString &filepath)
|
|
{
|
|
bool state_ = true;
|
|
QString error_message;
|
|
|
|
|
|
QFileInfo infos_file(filepath);
|
|
if (!infos_file.exists() || !infos_file.isFile())
|
|
{
|
|
state_ = false;
|
|
error_message = QString(tr("Le fichier %1 n'existe pas.", "message box content")).arg(filepath);
|
|
}
|
|
|
|
QFile file(filepath);
|
|
if (state_ && !file.open(QIODevice::ReadOnly)) {
|
|
state_ = false;
|
|
error_message = QString(tr("Impossible d'ouvrir le fichier %1.", "message box content")).arg(filepath);
|
|
}
|
|
|
|
QDomDocument document_xml;
|
|
if (state_) {
|
|
if (!document_xml.setContent(&file)) {
|
|
state_ = false;
|
|
error_message = tr("Ce fichier n'est pas un document XML valide", "message box content");
|
|
}
|
|
file.close();
|
|
}
|
|
|
|
if (!state_) {
|
|
QET::QetMessageBox::critical(this, tr("Erreur", "toolbar title"), error_message);
|
|
return;
|
|
}
|
|
|
|
m_elmt_scene -> fromXml(document_xml);
|
|
fillPartsList();
|
|
|
|
if (!infos_file.isWritable())
|
|
{
|
|
QET::QetMessageBox::warning(
|
|
this,
|
|
tr("Édition en lecture seule", "message box title"),
|
|
tr("Vous n'avez pas les privilèges nécessaires pour modifier cet élement. Il sera donc ouvert en lecture seule.", "message box content")
|
|
);
|
|
setReadOnly(true);
|
|
}
|
|
else {
|
|
setReadOnly(false);
|
|
}
|
|
|
|
// memorise le fichier
|
|
setFileName(filepath);
|
|
QETApp::elementsRecentFiles() -> fileWasOpened(filepath);
|
|
updateAction();
|
|
}
|
|
|
|
/**
|
|
* @brief QETElementEditor::toFile
|
|
* Save to a file the current edited element
|
|
* @param filepath : path of the file
|
|
* @return true if success
|
|
*/
|
|
bool QETElementEditor::toFile(const QString &filepath)
|
|
{
|
|
m_elmt_scene -> clearEventInterface();
|
|
m_elmt_scene -> clearSelection();
|
|
UncheckAddPrimitive();
|
|
|
|
QDomDocument element_xml = m_elmt_scene -> toXml();
|
|
bool writing = QET::writeXmlFile(element_xml, filepath);
|
|
if (!writing) {
|
|
QET::QetMessageBox::warning(
|
|
this,
|
|
tr("Erreur", "message box title"),
|
|
tr("Impossible d'écrire dans ce fichier", "message box content")
|
|
);
|
|
}
|
|
return(writing);
|
|
}
|
|
|
|
/**
|
|
* @brief QETElementEditor::fromLocation
|
|
* Location of an element to edit.
|
|
*/
|
|
void QETElementEditor::fromLocation(const ElementsLocation &location)
|
|
{
|
|
if (!location.isElement()) {
|
|
QET::QetMessageBox::critical(this,
|
|
tr("Élément inexistant.", "message box title"),
|
|
tr("Le chemin virtuel choisi ne correspond pas à un élément.", "message box content"));
|
|
return;
|
|
}
|
|
if (!location.exist()) {
|
|
QET::QetMessageBox::critical(this,
|
|
tr("Élément inexistant.", "message box title"),
|
|
tr("L'élément n'existe pas.", "message box content"));
|
|
return;
|
|
}
|
|
|
|
//The file must be an xml document
|
|
QDomDocument document_xml;
|
|
QDomNode node = document_xml.importNode(location.xml(), true);
|
|
document_xml.appendChild(node);
|
|
|
|
//Load the element
|
|
m_elmt_scene -> fromXml(document_xml);
|
|
fillPartsList();
|
|
|
|
//location is read only
|
|
if (!location.isWritable()) {
|
|
QET::QetMessageBox::warning(this,
|
|
tr("Édition en lecture seule", "message box title"),
|
|
tr("Vous n'avez pas les privilèges nécessaires pour modifier cet élement. Il sera donc ouvert en lecture seule.", "message box content"));
|
|
setReadOnly(true);
|
|
}
|
|
else {
|
|
setReadOnly(false);
|
|
}
|
|
|
|
setLocation(location);
|
|
updateAction();
|
|
}
|
|
|
|
/**
|
|
* @brief QETElementEditor::toLocation
|
|
* Save the edited element into @location
|
|
* @param location
|
|
* @return true if successfully saved
|
|
*/
|
|
bool QETElementEditor::toLocation(const ElementsLocation &location)
|
|
{
|
|
m_elmt_scene -> clearEventInterface();
|
|
m_elmt_scene -> clearSelection();
|
|
UncheckAddPrimitive();
|
|
|
|
if (!location.setXml(m_elmt_scene -> toXml())) {
|
|
QET::QetMessageBox::critical(this,
|
|
tr("Erreur", "message box title"),
|
|
tr("Impossible d'enregistrer l'élément", "message box content"));
|
|
return(false);
|
|
}
|
|
return(true);
|
|
}
|
|
|
|
/**
|
|
* @brief QETElementEditor::isEditing
|
|
* @param location
|
|
* @return true if this editor is currently
|
|
* editing element of location
|
|
*/
|
|
bool QETElementEditor::isEditing(const ElementsLocation &location)
|
|
{
|
|
if (m_opened_from_file)
|
|
{
|
|
auto r = QET::compareCanonicalFilePaths(
|
|
m_file_name,
|
|
QETApp::realPath(location.toString()));
|
|
return r;
|
|
}
|
|
else {
|
|
return(location == m_location);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief QETElementEditor::isEditing
|
|
* @param filepath
|
|
* @return true if this editor is currently
|
|
* editing element at filepath.
|
|
*/
|
|
bool QETElementEditor::isEditing(const QString &filepath)
|
|
{
|
|
QString current_filepath;
|
|
if (m_opened_from_file) {
|
|
current_filepath = m_file_name;
|
|
} else {
|
|
current_filepath = QETApp::realPath(m_location.toString());
|
|
}
|
|
|
|
auto r = QET::compareCanonicalFilePaths(
|
|
current_filepath,
|
|
filepath);
|
|
return r;
|
|
}
|
|
|
|
/**
|
|
* @brief QETElementEditor::elementScene
|
|
* @return
|
|
*/
|
|
ElementScene *QETElementEditor::elementScene() const {
|
|
return m_elmt_scene;
|
|
}
|
|
|
|
/**
|
|
* @brief QETElementEditor::elementView
|
|
* @return
|
|
*/
|
|
ElementView *QETElementEditor::elementView() const {
|
|
return m_view;
|
|
}
|
|
|
|
/**
|
|
* @brief QETElementEditor::pasteOffset
|
|
* @return the vertical and horizontal paste offset
|
|
*/
|
|
QPointF QETElementEditor::pasteOffset()
|
|
{
|
|
QPointF paste_offset(5.0, 0.0);
|
|
return(paste_offset);
|
|
}
|
|
|
|
/**
|
|
* @brief QETElementEditor::getOpenElementFileName
|
|
* Ask user to open a file who must be an element.
|
|
* @param parent
|
|
* @param dir
|
|
* @return
|
|
*/
|
|
QString QETElementEditor::getOpenElementFileName(QWidget *parent, const QString &dir)
|
|
{
|
|
QString user_filename = QFileDialog::getOpenFileName(
|
|
parent,
|
|
tr("Ouvrir un fichier", "dialog title"),
|
|
dir.isEmpty() ? QETApp::customElementsDir() : dir,
|
|
tr(
|
|
"Éléments QElectroTech (*.elmt);;"
|
|
"Fichiers XML (*.xml);;"
|
|
"Tous les fichiers (*)",
|
|
"filetypes allowed when opening an element file"
|
|
)
|
|
);
|
|
return(user_filename);
|
|
}
|
|
|
|
/**
|
|
* @brief QETElementEditor::updateTitle
|
|
* Update the title of the windows
|
|
*/
|
|
void QETElementEditor::updateTitle()
|
|
{
|
|
QString title = m_min_title;
|
|
title += " - " + m_elmt_scene->elementData().m_names_list.name() + " ";
|
|
if (!m_file_name.isEmpty() || !m_location.isNull()) {
|
|
if (!m_elmt_scene -> undoStack().isClean()) {
|
|
title += tr("[Modifié]", "window title tag");
|
|
}
|
|
}
|
|
if (isReadOnly()) {
|
|
title += tr(" [lecture seule]", "window title tag");
|
|
}
|
|
setWindowTitle(title);
|
|
}
|
|
|
|
/**
|
|
* @brief QETElementEditor::fillPartsList
|
|
*/
|
|
void QETElementEditor::fillPartsList()
|
|
{
|
|
m_parts_list -> blockSignals(true);
|
|
m_parts_list -> clear();
|
|
QList<QGraphicsItem *> qgis = m_elmt_scene -> zItems();
|
|
|
|
QSettings settings;
|
|
int maxParts = settings.value("elementeditor/max-parts-element-editor-list", 200).toInt();
|
|
if (qgis.count() <= maxParts)
|
|
{
|
|
for (int j = qgis.count() - 1 ; j >= 0 ; -- j)
|
|
{
|
|
QGraphicsItem *qgi = qgis[j];
|
|
if (CustomElementPart *cep = dynamic_cast<CustomElementPart *>(qgi))
|
|
{
|
|
QString part_desc = cep -> name();
|
|
if (PartTerminal *terminal = dynamic_cast<PartTerminal *>(qgi)) {
|
|
const auto t_name { terminal->terminalName() } ;
|
|
if (!t_name.isEmpty()) {
|
|
part_desc += QLatin1String(" : ") + t_name;
|
|
}
|
|
}
|
|
QListWidgetItem *qlwi = new QListWidgetItem(part_desc);
|
|
// Qt declares the QGraphicsItem* metatype itself, so this
|
|
// works on Qt 5 and Qt 6 alike. Without the stored pointer
|
|
// the parts list loses its item association and selecting
|
|
// a part no longer selects it on the canvas.
|
|
QVariant v = QVariant::fromValue(qgi);
|
|
qlwi -> setData(42, v);
|
|
m_parts_list -> addItem(qlwi);
|
|
qlwi -> setSelected(qgi -> isSelected());
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
m_parts_list -> addItem(new QListWidgetItem(tr("Trop de primitives, liste non générée: %1").arg(qgis.count())));
|
|
}
|
|
m_parts_list -> blockSignals(false);
|
|
}
|
|
|
|
/**
|
|
* @brief QETElementEditor::UncheckAddPrimitive
|
|
*/
|
|
void QETElementEditor::UncheckAddPrimitive() {
|
|
for (auto action : m_add_part_action_grp->actions()) {
|
|
action->setChecked(false);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief QETElementEditor::updateCurrentPartEditor
|
|
*/
|
|
void QETElementEditor::updateCurrentPartEditor()
|
|
{
|
|
if (!m_tools_dock_stack -> currentIndex()) {
|
|
return;
|
|
}
|
|
|
|
if (ElementItemEditor *current_editor = dynamic_cast<ElementItemEditor *>(m_tools_dock_stack -> widget(1))) {
|
|
current_editor -> updateForm();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief QETElementEditor::updateInformations
|
|
* Update the information and editor dock.
|
|
*/
|
|
void QETElementEditor::updateInformations()
|
|
{
|
|
QList<QGraphicsItem *> selected_qgis = m_elmt_scene -> selectedItems();
|
|
if (selected_qgis.isEmpty())
|
|
{
|
|
clearToolsDock();
|
|
m_default_informations -> setText(tr("%n partie(s) sélectionnée(s).",
|
|
"",
|
|
selected_qgis.size()));
|
|
m_default_informations -> setAlignment(Qt::AlignHCenter | Qt::AlignTop);
|
|
m_tools_dock_stack -> setCurrentIndex(0);
|
|
return;
|
|
}
|
|
|
|
QList<CustomElementPart *> cep_list;
|
|
|
|
CustomElementPart* part = dynamic_cast<CustomElementPart *>(selected_qgis.first());
|
|
QString selection_xml_name = part -> xmlName();
|
|
bool same_xml_name = true;
|
|
bool style_editable = true;
|
|
for (QGraphicsItem *qgi: selected_qgis)
|
|
{
|
|
if (CustomElementPart *cep = dynamic_cast<CustomElementPart *>(qgi)) {
|
|
cep_list << cep;
|
|
if (cep -> xmlName() != selection_xml_name) {
|
|
same_xml_name = false;
|
|
}
|
|
}
|
|
else {
|
|
style_editable = false;
|
|
same_xml_name = false;
|
|
}
|
|
}
|
|
if (style_editable) {
|
|
style_editable = StyleEditor::isStyleEditable(cep_list);
|
|
}
|
|
|
|
if (same_xml_name)
|
|
{
|
|
if ( selection_xml_name == "text"
|
|
|| selection_xml_name == "dynamic_text"
|
|
|| selection_xml_name == "line"
|
|
|| selection_xml_name == "rect"
|
|
|| selection_xml_name == "ellipse"
|
|
|| selection_xml_name == "arc")
|
|
{
|
|
clearToolsDock();
|
|
//We add the editor widget
|
|
ElementItemEditor *editor = static_cast<ElementItemEditor*>(m_editors[selection_xml_name]);
|
|
|
|
#if TODO_LIST
|
|
#pragma message("@TODO Check if it takes longer than setting the parts again to the editor.")
|
|
#endif
|
|
// TODO: Check if it takes longer than setting the parts again to the editor.
|
|
bool equal = true;
|
|
QList<CustomElementPart*> parts = editor -> currentParts();
|
|
if (parts.length() == cep_list.length()) {
|
|
for (auto cep: cep_list) {
|
|
bool part_found = false;
|
|
for (auto part: parts) {
|
|
if (part == cep) {
|
|
part_found = true;
|
|
break;
|
|
}
|
|
}
|
|
if (!part_found) {
|
|
equal = false;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
equal = false;
|
|
}
|
|
|
|
if (editor) {
|
|
bool success = true;
|
|
if (equal == false) {
|
|
success = editor -> setParts(cep_list);
|
|
}
|
|
if (success) {
|
|
m_tools_dock_stack -> insertWidget(1, editor);
|
|
m_tools_dock_stack -> setCurrentIndex(1);
|
|
}
|
|
else {
|
|
qDebug() << "Editor refused part.";
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
else if (cep_list.length() == 1 &&
|
|
(selection_xml_name == "polygon" || selection_xml_name == "terminal"))
|
|
{
|
|
#if TODO_LIST
|
|
#pragma message("@TODO maybe allowing multipart edit when number of points is the same?")
|
|
#endif
|
|
// multi edit for polygons makes no sense
|
|
// TODO: maybe allowing multipart edit when number of points is the same?
|
|
//We add the editor widget
|
|
clearToolsDock();
|
|
ElementItemEditor *editor = static_cast<ElementItemEditor*>(m_editors[selection_xml_name]);
|
|
CustomElementPart* part = editor -> currentPart();
|
|
bool equal = part == cep_list.first();
|
|
|
|
if (editor) {
|
|
bool success = true;
|
|
if (equal == false) {
|
|
success = editor -> setPart(cep_list.first());
|
|
}
|
|
if (success) {
|
|
m_tools_dock_stack -> insertWidget(1, editor);
|
|
m_tools_dock_stack -> setCurrentIndex(1);
|
|
}
|
|
else {
|
|
qDebug() << "Editor refused part.";
|
|
}
|
|
}
|
|
return;
|
|
|
|
}
|
|
else {
|
|
qDebug() << "Multiedit not supported for: " << cep_list.first() -> xmlName();
|
|
}
|
|
|
|
}
|
|
|
|
//There's several parts selecteds and all can be edited by style editor.
|
|
if (style_editable) {
|
|
clearToolsDock();
|
|
ElementItemEditor *selection_editor = m_editors["style"];
|
|
if (selection_editor) {
|
|
if (selection_editor -> setParts(cep_list)) {
|
|
m_tools_dock_stack -> insertWidget(1, selection_editor);
|
|
m_tools_dock_stack -> setCurrentIndex(1);
|
|
}
|
|
else {
|
|
qDebug() << "Editor refused part.";
|
|
}
|
|
}
|
|
}
|
|
//Else we only display the number of selected items
|
|
else {
|
|
clearToolsDock();
|
|
m_default_informations -> setText(tr("%n partie(s) sélectionnée(s).",
|
|
"",
|
|
selected_qgis.size()));
|
|
m_default_informations -> setAlignment(Qt::AlignHCenter | Qt::AlignTop);
|
|
m_tools_dock_stack -> setCurrentIndex(0);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief QETElementEditor::updatePartsList
|
|
*/
|
|
void QETElementEditor::updatePartsList()
|
|
{
|
|
int items_count = m_elmt_scene -> items().count();
|
|
QSettings settings;
|
|
const int maxParts = settings.value("elementeditor/max-parts-element-editor-list", 200).toInt();
|
|
if (m_parts_list -> count() != items_count) {
|
|
fillPartsList();
|
|
}
|
|
else if (items_count <= maxParts) {
|
|
m_parts_list -> blockSignals(true);
|
|
int i = 0;
|
|
QList<QGraphicsItem *> items = m_elmt_scene -> zItems();
|
|
for (int j = items.count() - 1 ; j >= 0 ; -- j) {
|
|
QGraphicsItem *qgi = items[j];
|
|
QListWidgetItem *qlwi = m_parts_list -> item(i);
|
|
if (qlwi) qlwi -> setSelected(qgi -> isSelected());
|
|
++ i;
|
|
}
|
|
m_parts_list -> blockSignals(false);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief QETElementEditor::updateSelectionFromPartsList
|
|
*/
|
|
void QETElementEditor::updateSelectionFromPartsList()
|
|
{
|
|
m_elmt_scene -> blockSignals(true);
|
|
m_parts_list -> blockSignals(true);
|
|
for (int i = 0 ; i < m_parts_list -> count() ; ++ i) {
|
|
QListWidgetItem *qlwi = m_parts_list -> item(i);
|
|
QGraphicsItem *qgi = qlwi -> data(42).value<QGraphicsItem *>();
|
|
if (qgi) {
|
|
qgi -> setSelected(qlwi -> isSelected());
|
|
}
|
|
}
|
|
m_parts_list -> blockSignals(false);
|
|
m_elmt_scene -> blockSignals(false);
|
|
updateInformations();
|
|
updateAction();
|
|
}
|
|
|
|
/**
|
|
* @brief QETElementEditor::checkElement
|
|
* Do several check about element
|
|
* @return true if there is no error
|
|
*/
|
|
bool QETElementEditor::checkElement()
|
|
{
|
|
//List of warning and error
|
|
typedef QPair<QString, QString> QETWarning;
|
|
QList<QETWarning> warnings;
|
|
QList<QETWarning> errors;
|
|
|
|
// Warning #1: Element does not have (enough) terminals
|
|
// (except for report and conductor definition, because they must have one terminal and this checking is done below)
|
|
// (another exception: "thumbnails" aka "front-views" may/should not have terminals)
|
|
if (!m_elmt_scene -> containsTerminals() &&
|
|
!(m_elmt_scene->elementData().m_type & ElementData::AllReport) &&
|
|
m_elmt_scene->elementData().m_type != ElementData::ConductorDefinition &&
|
|
m_elmt_scene->elementData().m_type != ElementData::Thumbnail) {
|
|
warnings << qMakePair(
|
|
tr("Absence de borne", "warning title"),
|
|
tr(
|
|
"<br>En l'absence de borne, l'élément ne pourra être"
|
|
" relié à d'autres éléments par l'intermédiaire de conducteurs.",
|
|
"warning description"
|
|
)
|
|
);
|
|
}
|
|
|
|
// Check folio report element
|
|
if (m_elmt_scene->elementData().m_type & ElementData::AllReport)
|
|
{
|
|
int terminal =0;
|
|
|
|
for(auto qgi : m_elmt_scene -> items()) {
|
|
if (qgraphicsitem_cast<PartTerminal *>(qgi)) {
|
|
terminal ++;
|
|
}
|
|
}
|
|
|
|
//Error folio report must have only one terminal
|
|
if (terminal != 1) {
|
|
errors << qMakePair (tr("Absence de borne"),
|
|
tr("<br><b>Erreur</b> :"
|
|
"<br>Les reports de folio doivent posséder une seul borne."
|
|
"<br><b>Solution</b> :"
|
|
"<br>Verifier que l'élément ne possède qu'une seul borne"));
|
|
}
|
|
}
|
|
|
|
// Check conductor definition element
|
|
if (m_elmt_scene->elementData().m_type == ElementData::ConductorDefinition)
|
|
{
|
|
int terminal =0;
|
|
|
|
for(auto qgi : m_elmt_scene -> items()) {
|
|
if (qgraphicsitem_cast<PartTerminal *>(qgi)) {
|
|
terminal ++;
|
|
}
|
|
}
|
|
|
|
// Error: Conductor definition must have exactly one terminal
|
|
if (terminal != 1) {
|
|
errors << qMakePair (tr("Nombre de bornes incorrect"),
|
|
tr("<br><b>Erreur</b> :"
|
|
"<br>Les définitions de conducteur ne peuvent posséder qu'une seule borne."
|
|
"<br><b>Solution</b> :"
|
|
"<br>Vérifier que l'élément ne possède qu'une seule borne"));
|
|
}
|
|
}
|
|
|
|
if (!errors.count() && !warnings.count()) {
|
|
return(true);
|
|
}
|
|
|
|
// Display warnings
|
|
QString dialog_message = tr("La vérification de cet élément a généré", "message box content");
|
|
|
|
if (errors.size()) {
|
|
dialog_message += QString(tr(" %n erreur(s)", "errors", errors.size()));
|
|
}
|
|
|
|
if (warnings.size()) {
|
|
if (errors.size()) {
|
|
dialog_message += QString (tr(" et"));
|
|
}
|
|
dialog_message += QString (tr(" %n avertissement(s)", "warnings", warnings.size()));
|
|
}
|
|
dialog_message += " :";
|
|
|
|
dialog_message += "<ol>";
|
|
QList<QETWarning> total = warnings << errors;
|
|
for(QETWarning warning : total)
|
|
{
|
|
dialog_message += "<li>";
|
|
dialog_message += QString(
|
|
tr("<b>%1</b> : %2", "warning title: warning description")
|
|
).arg(warning.first).arg(warning.second);
|
|
dialog_message += "</li>";
|
|
}
|
|
dialog_message += "</ol>";
|
|
|
|
if (errors.size()) {
|
|
QMessageBox::critical(this, tr("Erreurs"), dialog_message);
|
|
}
|
|
else {
|
|
QMessageBox::warning(this, tr("Avertissements"), dialog_message);
|
|
}
|
|
|
|
//if error == 0 that means they are only warning, we return true.
|
|
if (errors.count() == 0) {
|
|
return(true);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* @brief QETElementEditor::event
|
|
* @param event
|
|
* @return
|
|
*/
|
|
bool QETElementEditor::event(QEvent *event)
|
|
{
|
|
if (m_first_activation && event->type() == QEvent::WindowActivate) {
|
|
m_first_activation = false;
|
|
QTimer::singleShot(250, m_view, SLOT(zoomFit()));
|
|
}
|
|
|
|
return QMainWindow::event(event);
|
|
}
|
|
|
|
/**
|
|
* @brief QETElementEditor::openElement
|
|
* OPen an element from @filepath
|
|
* @param filepath
|
|
*/
|
|
void QETElementEditor::openElement(const QString &filepath)
|
|
{
|
|
if (filepath.isEmpty()) {
|
|
return;
|
|
}
|
|
// we have to test the file existence here because QETApp::openElementFiles()
|
|
// will discard non-existent files through QFileInfo::canonicalFilePath()
|
|
if (!QFile::exists(filepath)) {
|
|
QET::QetMessageBox::critical(
|
|
this,
|
|
tr("Impossible d'ouvrir le fichier", "message box title"),
|
|
QString(
|
|
tr("Il semblerait que le fichier %1 que vous essayez d'ouvrir"
|
|
" n'existe pas ou plus.")
|
|
).arg(filepath)
|
|
);
|
|
}
|
|
QETApp::instance() -> openElementFiles(QStringList() << filepath);
|
|
}
|
|
|
|
/**
|
|
* @brief QETElementEditor::closeEvent
|
|
* @param qce
|
|
*/
|
|
void QETElementEditor::closeEvent(QCloseEvent *qce)
|
|
{
|
|
if (canClose()) {
|
|
writeSettings();
|
|
setAttribute(Qt::WA_DeleteOnClose);
|
|
m_elmt_scene -> reset();
|
|
qce -> accept();
|
|
}
|
|
else {
|
|
qce -> ignore();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief QETElementEditor::canClose
|
|
* @return
|
|
*/
|
|
bool QETElementEditor::canClose()
|
|
{
|
|
if (m_elmt_scene -> undoStack().isClean()) {
|
|
return(true);
|
|
}
|
|
//First ask user to save
|
|
QMessageBox::StandardButton answer = QET::QetMessageBox::question(
|
|
this,
|
|
tr("Enregistrer l'élément en cours ?", "dialog title"),
|
|
QString(
|
|
tr(
|
|
"Voulez-vous enregistrer l'élément %1 ?",
|
|
"dialog content - %1 is an element name"
|
|
)
|
|
).arg(m_elmt_scene->elementData().m_names_list.name()),
|
|
QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel,
|
|
QMessageBox::Cancel
|
|
);
|
|
|
|
bool result;
|
|
switch(answer) {
|
|
case QMessageBox::Cancel: {
|
|
result = false;
|
|
break;
|
|
}
|
|
case QMessageBox::Yes: {
|
|
result = on_m_save_action_triggered();
|
|
break;
|
|
}
|
|
default: {
|
|
result = true;
|
|
}
|
|
}
|
|
return(result);
|
|
}
|
|
|
|
/**
|
|
* @brief QETElementEditor::readSettings
|
|
* Read some setting about this widget in the QSetting
|
|
* of qelectrotech
|
|
*/
|
|
void QETElementEditor::readSettings()
|
|
{
|
|
QSettings settings;
|
|
|
|
QVariant geometry = settings.value("elementeditor/geometry");
|
|
if (geometry.isValid()) {
|
|
restoreGeometry(geometry.toByteArray());
|
|
}
|
|
|
|
QVariant state = settings.value("elementeditor/state");
|
|
if (state.isValid()) {
|
|
restoreState(state.toByteArray());
|
|
}
|
|
|
|
auto data = m_elmt_scene->elementData();
|
|
data.m_drawing_information = settings.value("elementeditor/default-informations", "").toString();
|
|
m_elmt_scene->setElementData(data);
|
|
}
|
|
|
|
/**
|
|
* @brief QETElementEditor::writeSettings
|
|
* Write some setting of this widget in the
|
|
* QSetting of qelectrotech
|
|
*/
|
|
void QETElementEditor::writeSettings() const
|
|
{
|
|
QSettings settings;
|
|
settings.setValue("elementeditor/geometry", saveGeometry());
|
|
settings.setValue("elementeditor/state", saveState());
|
|
}
|
|
|
|
/**
|
|
* @brief QETElementEditor::setupActions
|
|
*/
|
|
void QETElementEditor::setupActions()
|
|
{
|
|
m_undo_action = m_elmt_scene -> undoStack().createUndoAction(this, tr("Annuler"));
|
|
m_redo_action = m_elmt_scene -> undoStack().createRedoAction(this, tr("Refaire"));
|
|
m_undo_action -> setIcon(QET::Icons::EditUndo);
|
|
m_redo_action -> setIcon(QET::Icons::EditRedo);
|
|
ShortcutManager::instance().registerAction(m_undo_action, "elementeditor.undo", tr("Éditeur d'élément"), QKeySequence::Undo);
|
|
ShortcutManager::instance().registerAction(m_redo_action, "elementeditor.redo", tr("Éditeur d'élément"), QKeySequence::Redo);
|
|
ui->m_undo_toolbar->addAction(m_undo_action);
|
|
ui->m_undo_toolbar->addAction(m_redo_action);
|
|
|
|
ShortcutManager::instance().registerAction(ui->m_new_action, "elementeditor.new", tr("Éditeur d'élément"), QKeySequence::New);
|
|
ShortcutManager::instance().registerAction(ui->m_open_action, "elementeditor.open", tr("Éditeur d'élément"), QKeySequence::Open);
|
|
ShortcutManager::instance().registerAction(ui->m_open_from_file_action, "elementeditor.open_from_file", tr("Éditeur d'élément"), Qt::CTRL | Qt::SHIFT | Qt::Key_O);
|
|
ShortcutManager::instance().registerAction(ui->m_save_action, "elementeditor.save", tr("Éditeur d'élément"), QKeySequence::Save);
|
|
ShortcutManager::instance().registerAction(ui->m_save_as_file_action, "elementeditor.save_as_file", tr("Éditeur d'élément"), Qt::CTRL | Qt::SHIFT | Qt::Key_S);
|
|
ShortcutManager::instance().registerAction(ui->m_select_all_act, "elementeditor.select_all", tr("Éditeur d'élément"), QKeySequence::SelectAll);
|
|
ShortcutManager::instance().registerAction(ui->m_deselect_all_action, "elementeditor.deselect_all", tr("Éditeur d'élément"), Qt::CTRL | Qt::SHIFT | Qt::Key_A);
|
|
ShortcutManager::instance().registerAction(ui->m_revert_selection_action, "elementeditor.revert_selection", tr("Éditeur d'élément"), Qt::CTRL | Qt::Key_I);
|
|
ShortcutManager::instance().registerAction(ui->m_cut_action, "elementeditor.cut", tr("Éditeur d'élément"), QKeySequence::Cut);
|
|
ShortcutManager::instance().registerAction(ui->m_copy_action, "elementeditor.copy", tr("Éditeur d'élément"), QKeySequence::Copy);
|
|
ShortcutManager::instance().registerAction(ui->m_paste_action, "elementeditor.paste", tr("Éditeur d'élément"), QKeySequence::Paste);
|
|
ShortcutManager::instance().registerAction(ui->m_paste_in_area_action, "elementeditor.paste_in_area", tr("Éditeur d'élément"), Qt::CTRL | Qt::SHIFT | Qt::Key_V);
|
|
ShortcutManager::instance().registerAction(ui->m_edit_names_action, "elementeditor.edit_names", tr("Éditeur d'élément"), Qt::CTRL | Qt::Key_E);
|
|
ShortcutManager::instance().registerAction(ui->m_edit_author_action, "elementeditor.edit_author", tr("Éditeur d'élément"), Qt::CTRL | Qt::Key_Y);
|
|
|
|
#ifdef Q_OS_MAC
|
|
ShortcutManager::instance().registerAction(ui->m_delete_action, "elementeditor.delete", tr("Éditeur d'élément"), Qt::Key_Backspace);
|
|
ShortcutManager::instance().registerAction(ui->m_quit_action, "elementeditor.quit", tr("Éditeur d'élément"), Qt::CTRL | Qt::Key_W);
|
|
#else
|
|
ShortcutManager::instance().registerAction(ui->m_delete_action, "elementeditor.delete", tr("Éditeur d'élément"), Qt::Key_Delete);
|
|
ShortcutManager::instance().registerAction(ui->m_quit_action, "elementeditor.quit", tr("Éditeur d'élément"), Qt::CTRL | Qt::Key_Q);
|
|
#endif
|
|
|
|
//Depth action
|
|
m_depth_action_group = QET::depthActionGroup(this);
|
|
connect(m_depth_action_group, &QActionGroup::triggered, [this](QAction *action) {
|
|
this -> elementScene() -> undoStack().push(
|
|
new ChangeZValueCommand(this -> elementScene(), action -> data().value<QET::DepthOption>()));
|
|
emit(this -> elementScene() -> partsZValueChanged());
|
|
});
|
|
auto depth_toolbar = addToolBar(tr("Profondeur", "toolbar title"));
|
|
depth_toolbar -> setObjectName("depth_toolbar");
|
|
depth_toolbar -> addActions(m_depth_action_group -> actions());
|
|
addToolBar(Qt::TopToolBarArea, depth_toolbar);
|
|
|
|
//Rotate action
|
|
ShortcutManager::instance().registerAction(ui->m_rotate_action, "elementeditor.rotate", tr("Éditeur d'élément"), Qt::Key_Space);
|
|
connect(ui->m_rotate_action, &QAction::triggered, [this]() {this -> elementScene() -> undoStack().push(new RotateElementsCommand(this->elementScene()));});
|
|
|
|
//Rotate Fine action = rotate with smaller inkrement
|
|
ShortcutManager::instance().registerAction(ui->m_rotateFine_action, "elementeditor.rotate_fine", tr("Éditeur d'élément"), Qt::CTRL | Qt::Key_Space);
|
|
connect(ui->m_rotateFine_action, &QAction::triggered, [this]() {this -> elementScene() -> undoStack().push(new RotateFineElementsCommand(this->elementScene()));});
|
|
|
|
//Flip action
|
|
ShortcutManager::instance().registerAction(ui->m_flip_action, "elementeditor.flip", tr("Éditeur d'élément"), Qt::Key_F);
|
|
connect(ui->m_flip_action, &QAction::triggered, [this]() {this -> elementScene() -> undoStack().push(new FlipElementsCommand(this->elementScene()));});
|
|
|
|
//Mirror action
|
|
ShortcutManager::instance().registerAction(ui->m_mirror_action, "elementeditor.mirror", tr("Éditeur d'élément"), Qt::Key_M);
|
|
connect(ui->m_mirror_action, &QAction::triggered, [this]() {this -> elementScene() -> undoStack().push(new MirrorElementsCommand(this->elementScene()));});
|
|
|
|
|
|
//Zoom action
|
|
ShortcutManager::instance().registerAction(ui->m_zoom_in_action, "elementeditor.zoom_in", tr("Éditeur d'élément"), QKeySequence::ZoomIn);
|
|
ShortcutManager::instance().registerAction(ui->m_zoom_out_action, "elementeditor.zoom_out", tr("Éditeur d'élément"), QKeySequence::ZoomOut);
|
|
ShortcutManager::instance().registerAction(ui->m_zoom_fit_best_action, "elementeditor.zoom_fit_best", tr("Éditeur d'élément"), Qt::CTRL | Qt::Key_9);
|
|
ShortcutManager::instance().registerAction(ui->m_zoom_original_action, "elementeditor.zoom_original", tr("Éditeur d'élément"), Qt::CTRL | Qt::Key_0);
|
|
|
|
//Add primitive actions
|
|
m_add_part_action_grp = new QActionGroup(this);
|
|
|
|
auto *add_line = new QAction(QET::Icons::PartLine, tr("Ajouter une ligne"), m_add_part_action_grp);
|
|
auto *add_rectangle = new QAction(QET::Icons::PartRectangle, tr("Ajouter un rectangle"), m_add_part_action_grp);
|
|
auto *add_ellipse = new QAction(QET::Icons::PartEllipse, tr("Ajouter une ellipse"), m_add_part_action_grp);
|
|
auto *add_polygon = new QAction(QET::Icons::PartPolygon, tr("Ajouter un polygone"), m_add_part_action_grp);
|
|
auto *add_text = new QAction(QET::Icons::PartText, tr("Ajouter du texte"), m_add_part_action_grp);
|
|
auto *add_arc = new QAction(QET::Icons::PartArc, tr("Ajouter un arc de cercle"), m_add_part_action_grp);
|
|
auto *add_terminal = new QAction(QET::Icons::Terminal, tr("Ajouter une borne"), m_add_part_action_grp);
|
|
auto *add_dynamic_text_field = new QAction(QET::Icons::PartTextField, tr("Ajouter un champ texte dynamique"), m_add_part_action_grp);
|
|
|
|
for (auto action : m_add_part_action_grp->actions()) {
|
|
action -> setCheckable(true);
|
|
}
|
|
|
|
connect(add_line, &QAction::triggered, [this]() {m_elmt_scene->setEventInterface(new ESEventAddLine(m_elmt_scene));});
|
|
connect(add_rectangle, &QAction::triggered, [this]() {m_elmt_scene->setEventInterface(new ESEventAddRect(m_elmt_scene));});
|
|
connect(add_ellipse, &QAction::triggered, [this]() {m_elmt_scene->setEventInterface(new ESEventAddEllipse(m_elmt_scene));});
|
|
connect(add_polygon, &QAction::triggered, [this]() {m_elmt_scene->setEventInterface(new ESEventAddPolygon(m_elmt_scene));});
|
|
connect(add_text, &QAction::triggered, [this]() {m_elmt_scene->setEventInterface(new ESEventAddText(m_elmt_scene));});
|
|
connect(add_arc, &QAction::triggered, [this]() {m_elmt_scene->setEventInterface(new ESEventAddArc(m_elmt_scene));});
|
|
connect(add_terminal, &QAction::triggered, [this]() {m_elmt_scene->setEventInterface(new ESEventAddTerminal(m_elmt_scene));});
|
|
connect(add_dynamic_text_field, &QAction::triggered, [this]() {m_elmt_scene->setEventInterface(new ESEventAddDynamicTextField(m_elmt_scene));});
|
|
|
|
add_polygon -> setStatusTip(tr("Double-click pour terminer la forme, Click droit pour annuler le dernier point"));
|
|
add_text -> setStatusTip(tr("Ajouter un texte d'élément non éditable dans les schémas"));
|
|
add_dynamic_text_field -> setStatusTip(tr("Ajouter un texte d'élément pouvant être édité dans les schémas"));
|
|
|
|
auto parts_toolbar = addToolBar(tr("Parties", "toolbar title"));
|
|
parts_toolbar -> setAllowedAreas(Qt::AllToolBarAreas);
|
|
parts_toolbar -> setObjectName("parts");
|
|
parts_toolbar -> addActions(m_add_part_action_grp -> actions());
|
|
addToolBar(Qt::LeftToolBarArea, parts_toolbar);
|
|
}
|
|
|
|
/**
|
|
* @brief QETElementEditor::updateAction
|
|
* Update actions
|
|
*/
|
|
void QETElementEditor::updateAction()
|
|
{
|
|
//Action disabled if read only
|
|
auto ro_list = m_add_part_action_grp->actions();
|
|
ro_list << ui->m_select_all_act
|
|
<< ui->m_revert_selection_action
|
|
<< ui->m_paste_from_file_action
|
|
<< ui->m_paste_from_element_action;
|
|
for (auto action : std::as_const(ro_list)) {
|
|
action->setDisabled(m_read_only);
|
|
}
|
|
|
|
//Action enabled if a primitive is selected
|
|
auto select_list = m_depth_action_group->actions();
|
|
select_list << ui->m_deselect_all_action
|
|
<< ui->m_cut_action
|
|
<< ui->m_copy_action
|
|
<< ui->m_delete_action
|
|
<< ui->m_rotate_action
|
|
<< ui->m_rotateFine_action
|
|
<< ui->m_flip_action
|
|
<< ui->m_mirror_action;
|
|
auto items_selected = !m_read_only && m_elmt_scene->selectedItems().count();
|
|
for (auto action : std::as_const(select_list)) {
|
|
action->setEnabled(items_selected);
|
|
}
|
|
|
|
//Action about clipboard
|
|
auto clipboard_contain_elmt = !m_read_only && ElementScene::clipboardMayContainElement();
|
|
ui->m_paste_action->setEnabled(clipboard_contain_elmt);
|
|
ui->m_paste_in_area_action->setEnabled(clipboard_contain_elmt);
|
|
|
|
//Action about undo stack status
|
|
ui->m_save_action-> setEnabled(!m_read_only && !m_elmt_scene->undoStack().isClean());
|
|
m_undo_action -> setEnabled(!m_read_only && m_elmt_scene -> undoStack().canUndo());
|
|
m_redo_action -> setEnabled(!m_read_only && m_elmt_scene -> undoStack().canRedo());
|
|
|
|
}
|
|
|
|
/**
|
|
* @brief QETElementEditor::setupConnection
|
|
*/
|
|
void QETElementEditor::setupConnection()
|
|
{
|
|
connect(m_elmt_scene, &ElementScene::partsAdded, this, &QETElementEditor::UncheckAddPrimitive);
|
|
connect(m_elmt_scene, &ElementScene::partsAdded, this, &QETElementEditor::fillPartsList);
|
|
connect(m_elmt_scene, &ElementScene::partsRemoved, this, &QETElementEditor::fillPartsList);
|
|
connect(m_elmt_scene, &ElementScene::partsZValueChanged, this, &QETElementEditor::fillPartsList);
|
|
connect(m_parts_list, &QListWidget::itemSelectionChanged, this, &QETElementEditor::updateSelectionFromPartsList);
|
|
connect(QApplication::clipboard(), &QClipboard::dataChanged, this, &QETElementEditor::updateAction);
|
|
|
|
connect(m_elmt_scene, &ElementScene::selectionChanged, [this]() {
|
|
this->updateInformations();
|
|
this->updateAction();
|
|
this->updatePartsList();
|
|
});
|
|
|
|
connect(&(m_elmt_scene -> undoStack()), &QUndoStack::cleanChanged, [this]() {
|
|
this->updateAction();
|
|
this->updateTitle();
|
|
});
|
|
|
|
connect(&(m_elmt_scene -> undoStack()), &QUndoStack::indexChanged, [this]() {
|
|
this->updateInformations();
|
|
});
|
|
}
|
|
|
|
/**
|
|
* @brief QETElementEditor::initGui
|
|
*/
|
|
void QETElementEditor::initGui()
|
|
{
|
|
m_editors["arc"] = new ArcEditor(this);
|
|
m_editors["ellipse"] = new EllipseEditor(this);
|
|
m_editors["line"] = new LineEditor(this);
|
|
m_editors["polygon"] = new PolygonEditor(this);
|
|
m_editors["rect"] = new RectangleEditor(this);
|
|
m_editors["terminal"] = new TerminalEditor(this);
|
|
m_editors["text"] = new TextEditor(this);
|
|
m_editors["style"] = new StyleEditor(this);
|
|
m_editors["dynamic_text"] = new DynamicTextFieldEditor(this);
|
|
|
|
m_elmt_scene = new ElementScene(this, this);
|
|
m_view = new ElementView(m_elmt_scene, this);
|
|
m_view->setDragMode(QGraphicsView::RubberBandDrag);
|
|
setCentralWidget(m_view);
|
|
|
|
//Part dock editor
|
|
m_default_informations = new QLabel(this);
|
|
m_tools_dock_stack = new QStackedWidget(this);
|
|
m_tools_dock_stack->insertWidget(0, m_default_informations);
|
|
ui->m_tools_dock->setWidget(m_tools_dock_stack);
|
|
|
|
//Undo dock
|
|
auto undo_view = new QUndoView(&(m_elmt_scene->undoStack()), this);
|
|
undo_view->setEmptyLabel(tr("Aucune modification"));
|
|
ui->m_undo_dock->setWidget(undo_view);
|
|
|
|
//parts list dock
|
|
m_parts_list = new QListWidget(this);
|
|
m_parts_list->setSelectionMode(QAbstractItemView::ExtendedSelection);
|
|
tabifyDockWidget(ui->m_undo_dock, ui->m_parts_dock);
|
|
ui->m_parts_dock->setWidget(m_parts_list);
|
|
|
|
updateInformations();
|
|
fillPartsList();
|
|
|
|
// When the element type changes, update the terminal editor master label visibility
|
|
connect(m_elmt_scene, &ElementScene::elementTypeChanged, this, [this]() {
|
|
auto *te = static_cast<TerminalEditor *>(m_editors["terminal"]);
|
|
if (te) te->refreshMasterLabelVisibility();
|
|
});
|
|
|
|
statusBar()->showMessage(tr("Éditeur d'éléments", "status bar message"));
|
|
}
|
|
|
|
/**
|
|
* @brief QETElementEditor::clearToolsDock
|
|
* Remove and hide the widget displayed by
|
|
* the dock used to edit primitives
|
|
* @return Return the removed widget or nullptr if there is no widget to remove.
|
|
*/
|
|
QWidget *QETElementEditor::clearToolsDock()
|
|
{
|
|
if (QWidget *previous_widget = m_tools_dock_stack -> widget(1)) {
|
|
m_tools_dock_stack -> removeWidget(previous_widget);
|
|
previous_widget -> hide();
|
|
return(previous_widget);
|
|
}
|
|
return(nullptr);
|
|
}
|
|
|
|
/**
|
|
* @brief QETElementEditor::copyAndPasteXml
|
|
* Copy the content of @xml_document to the clipboard
|
|
* and call pasteInArea method of elementView.
|
|
* @param xml_document
|
|
*/
|
|
void QETElementEditor::copyAndPasteXml(const QDomDocument &xml_document)
|
|
{
|
|
QClipboard *clipboard = QApplication::clipboard();
|
|
QString clipboard_content = xml_document.toString(4);
|
|
|
|
if (clipboard -> supportsSelection()) {
|
|
clipboard -> setText(clipboard_content, QClipboard::Selection);
|
|
}
|
|
clipboard -> setText(clipboard_content);
|
|
|
|
m_view -> pasteInArea();
|
|
}
|
|
|
|
/**
|
|
* @brief QETElementEditor::on_m_save_action_triggered
|
|
* @return
|
|
*/
|
|
bool QETElementEditor::on_m_save_action_triggered()
|
|
{
|
|
if (checkElement())
|
|
{
|
|
if (m_opened_from_file)
|
|
{
|
|
if (m_file_name.isEmpty()) {
|
|
return(on_m_save_as_action_triggered());
|
|
}
|
|
|
|
//Else wa save to the file at filename_ path
|
|
bool result_save = toFile(m_file_name);
|
|
if (result_save) m_elmt_scene -> undoStack().setClean();
|
|
return(result_save);
|
|
}
|
|
else
|
|
{
|
|
if (m_location.isNull()) {
|
|
return(on_m_save_as_action_triggered());
|
|
}
|
|
|
|
//Else save to the known location
|
|
bool result_save = toLocation(m_location);
|
|
if (result_save) {
|
|
m_elmt_scene -> undoStack().setClean();
|
|
emit saveToLocation(m_location);
|
|
}
|
|
return(result_save);
|
|
}
|
|
}
|
|
|
|
QMessageBox::critical(this, tr("Echec de l'enregistrement"), tr("L'enregistrement à échoué,\nles conditions requises ne sont pas valides"));
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* @brief QETElementEditor::on_m_save_as_action_triggered
|
|
* Ask a location to user and save the current edited element
|
|
* to this location
|
|
* @return true is success
|
|
*/
|
|
bool QETElementEditor::on_m_save_as_action_triggered()
|
|
{
|
|
// Check element before writing
|
|
if (checkElement()) {
|
|
//Ask a location to user
|
|
ElementsLocation location = ElementDialog::getSaveElementLocation(this);
|
|
if (location.isNull()) {
|
|
return(false);
|
|
}
|
|
|
|
bool result_save = toLocation(location);
|
|
if (result_save) {
|
|
setLocation(location);
|
|
m_elmt_scene -> undoStack().setClean();
|
|
emit saveToLocation(location);
|
|
}
|
|
|
|
return(result_save);
|
|
}
|
|
QMessageBox::critical(this, tr("Echec de l'enregistrement"), tr("L'enregistrement à échoué,\nles conditions requises ne sont pas valides"));
|
|
return (false);
|
|
}
|
|
|
|
void QETElementEditor::on_m_select_all_act_triggered() {
|
|
m_elmt_scene->slot_selectAll();
|
|
}
|
|
|
|
void QETElementEditor::on_m_edit_element_properties_action_triggered() { m_elmt_scene->slot_editProperties(); }
|
|
|
|
void QETElementEditor::on_m_new_action_triggered()
|
|
{
|
|
NewElementWizard new_element_wizard(this);
|
|
new_element_wizard.exec();
|
|
}
|
|
|
|
void QETElementEditor::on_m_open_action_triggered()
|
|
{
|
|
ElementsLocation location = ElementDialog::getOpenElementLocation(this);
|
|
if (location.isNull()) {
|
|
return;
|
|
}
|
|
QETApp::instance() -> openElementLocations(QList<ElementsLocation>() << location);
|
|
}
|
|
|
|
void QETElementEditor::on_m_open_from_file_action_triggered()
|
|
{
|
|
QString open_dir = m_file_name.isEmpty() ? QETApp::customElementsDir() : QDir(m_file_name).absolutePath();
|
|
QString user_filename = QETElementEditor::getOpenElementFileName(this, open_dir);
|
|
openElement(user_filename);
|
|
}
|
|
|
|
bool QETElementEditor::on_m_save_as_file_action_triggered()
|
|
{
|
|
// Check element before writing
|
|
if (checkElement()) {
|
|
//Ask a filename to user, for save the element
|
|
QString fn = QFileDialog::getSaveFileName(
|
|
this,
|
|
tr("Enregistrer sous", "dialog title"),
|
|
m_file_name.isEmpty() ? QETApp::customElementsDir() : QDir(m_file_name).absolutePath(),
|
|
tr(
|
|
"Éléments QElectroTech (*.elmt)",
|
|
"filetypes allowed when saving an element file"
|
|
)
|
|
);
|
|
|
|
if (fn.isEmpty()) {
|
|
return(false);
|
|
}
|
|
|
|
//If the name doesn't end by .elmt, we add it
|
|
if (!fn.endsWith(".elmt", Qt::CaseInsensitive)) {
|
|
fn += ".elmt";
|
|
}
|
|
|
|
bool result_save = toFile(fn);
|
|
//If the save success, the filename is keep
|
|
if (result_save) {
|
|
setFileName(fn);
|
|
QETApp::elementsRecentFiles() -> fileWasOpened(fn);
|
|
m_elmt_scene -> undoStack().setClean();
|
|
}
|
|
|
|
return(result_save);
|
|
}
|
|
QMessageBox::critical(this, tr("Echec de l'enregistrement"), tr("L'enregistrement à échoué,\nles conditions requises ne sont pas valides"));
|
|
return false;
|
|
}
|
|
|
|
void QETElementEditor::on_m_reload_action_triggered()
|
|
{
|
|
//If user already edit the element, ask confirmation to reload
|
|
if (!m_elmt_scene -> undoStack().isClean()) {
|
|
QMessageBox::StandardButton answer = QET::QetMessageBox::question(this,
|
|
tr("Recharger l'élément", "dialog title"),
|
|
tr("Vous avez efffectué des modifications sur cet élément. Si vous le rechargez, ces modifications seront perdues. Voulez-vous vraiment recharger l'élément ?", "dialog content"),
|
|
QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel,
|
|
QMessageBox::Cancel);
|
|
if (answer != QMessageBox::Yes){
|
|
return;
|
|
}
|
|
}
|
|
|
|
//Reload the element
|
|
m_elmt_scene -> reset();
|
|
if (m_opened_from_file) {
|
|
fromFile(m_file_name);
|
|
}
|
|
else {
|
|
fromLocation(m_location);
|
|
}
|
|
}
|
|
|
|
void QETElementEditor::on_m_quit_action_triggered() { close(); }
|
|
|
|
void QETElementEditor::on_m_deselect_all_action_triggered() { m_elmt_scene->slot_deselectAll(); }
|
|
|
|
void QETElementEditor::on_m_cut_action_triggered() { m_view->cut(); }
|
|
|
|
void QETElementEditor::on_m_copy_action_triggered() { m_view->copy(); }
|
|
|
|
void QETElementEditor::on_m_paste_action_triggered() { m_view->paste(); }
|
|
|
|
void QETElementEditor::on_m_paste_in_area_action_triggered() { m_view->pasteInArea(); }
|
|
|
|
void QETElementEditor::on_m_paste_from_file_action_triggered()
|
|
{
|
|
QString element_file_path = getOpenElementFileName(this);
|
|
if (element_file_path.isEmpty()) {
|
|
return;
|
|
}
|
|
|
|
QString error_message;
|
|
QDomDocument xml_document;
|
|
QFile element_file(element_file_path);
|
|
// le fichier doit etre lisible
|
|
if (!element_file.open(QIODevice::ReadOnly)) {
|
|
error_message = QString(tr("Impossible d'ouvrir le fichier %1.", "message box content")).arg(element_file_path);
|
|
}
|
|
else {
|
|
// le fichier doit etre un document XML
|
|
if (!xml_document.setContent(&element_file)) {
|
|
error_message = tr("Ce fichier n'est pas un document XML valide", "message box content");
|
|
}
|
|
element_file.close();
|
|
}
|
|
|
|
if (!error_message.isEmpty()) {
|
|
QET::QetMessageBox::critical(this, tr("Erreur", "toolbar title"), error_message);
|
|
}
|
|
copyAndPasteXml(xml_document);
|
|
}
|
|
|
|
void QETElementEditor::on_m_paste_from_element_action_triggered()
|
|
{
|
|
//Ask for a location
|
|
ElementsLocation location = ElementDialog::getOpenElementLocation(this);
|
|
if (location.isNull()) {
|
|
return;
|
|
}
|
|
|
|
if (!location.isElement()) {
|
|
QET::QetMessageBox::critical(this,
|
|
tr("Élément inexistant.", "message box title"),
|
|
tr("Le chemin virtuel choisi ne correspond pas à un élément.", "message box content"));
|
|
return;
|
|
}
|
|
if (!location.exist()) {
|
|
QET::QetMessageBox::critical(this,
|
|
tr("Élément inexistant.", "message box title"),
|
|
tr("L'élément n'existe pas.", "message box content"));
|
|
return;
|
|
}
|
|
|
|
//Create an xml document from the location xml
|
|
QDomDocument document_xml;
|
|
QDomNode node = document_xml.importNode(location.xml(), true);
|
|
document_xml.appendChild(node);
|
|
|
|
copyAndPasteXml(document_xml);
|
|
}
|
|
|
|
void QETElementEditor::on_m_revert_selection_action_triggered() { m_elmt_scene->slot_invertSelection(); }
|
|
|
|
void QETElementEditor::on_m_delete_action_triggered() { m_elmt_scene->slot_delete(); }
|
|
|
|
void QETElementEditor::on_m_edit_names_action_triggered() { m_elmt_scene->slot_editNames(); }
|
|
|
|
void QETElementEditor::on_m_edit_author_action_triggered() { m_elmt_scene->slot_editAuthorInformations(); }
|
|
|
|
void QETElementEditor::on_m_zoom_in_action_triggered() { m_view->zoomIn(); }
|
|
|
|
void QETElementEditor::on_m_zoom_out_action_triggered() { m_view->zoomOut(); }
|
|
|
|
void QETElementEditor::on_m_zoom_fit_best_action_triggered() { m_view->zoomFit(); }
|
|
|
|
void QETElementEditor::on_m_zoom_original_action_triggered() { m_view->zoomReset(); }
|
|
|
|
void QETElementEditor::on_m_about_qet_action_triggered() { QETApp::instance()->aboutQET(); }
|
|
|
|
void QETElementEditor::on_m_online_manual_triggered() {
|
|
QString link = "https://download.qelectrotech.org/qet/manual_0.7/build/index.html";
|
|
QDesktopServices::openUrl(QUrl(link));
|
|
}
|
|
|
|
void QETElementEditor::on_m_youtube_action_triggered() {
|
|
QString link = "https://www.youtube.com/user/scorpio8101/videos";
|
|
QDesktopServices::openUrl(QUrl(link));
|
|
}
|
|
|
|
void QETElementEditor::on_m_donate_action_triggered() {
|
|
QString link = "https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=ZZHC9D7C3MDPC";
|
|
QDesktopServices::openUrl(QUrl(link));
|
|
}
|
|
|
|
void QETElementEditor::on_m_about_qt_action_triggered() { qApp->aboutQt(); }
|
|
|
|
void QETElementEditor::on_m_import_dxf_triggered()
|
|
{
|
|
if (dxf2ElmtIsPresent(true, this))
|
|
{
|
|
QString file_path{QFileDialog::getOpenFileName(this,
|
|
QObject::tr("Importer un fichier dxf"),
|
|
QETApp::documentDir(),
|
|
"DXF (*.dxf)")};
|
|
if (file_path.isEmpty()) {
|
|
return;
|
|
}
|
|
|
|
QMessageBox::information(this, tr("Avertissement"), tr("L'import d'un dxf volumineux peut prendre du temps \n"
|
|
"veuillez patienter durant l'import..."));
|
|
|
|
const QByteArray array_{dxfToElmt(file_path)};
|
|
if (array_.isEmpty()) {
|
|
return;
|
|
}
|
|
QDomDocument xml_;
|
|
xml_.setContent(array_);
|
|
|
|
m_elmt_scene->undoStack().push(new OpenElmtCommand(xml_, m_elmt_scene));
|
|
}
|
|
}
|
|
|
|
void QETElementEditor::on_m_import_scaled_element_triggered()
|
|
{
|
|
if (ElementScalerIsPresent(true, this))
|
|
{
|
|
QString file_path{QFileDialog::getOpenFileName(this,
|
|
tr("Importer un élément à redimensionner"),
|
|
QETApp::documentDir(),
|
|
tr("Éléments QElectroTech (*.elmt)"))};
|
|
if (file_path.isEmpty()) {
|
|
return;
|
|
}
|
|
|
|
const QByteArray array_{ElementScaler(file_path, this)};
|
|
if (array_.isEmpty()) {
|
|
return;
|
|
}
|
|
QDomDocument xml_;
|
|
xml_.setContent(array_);
|
|
|
|
m_elmt_scene->undoStack().push(new OpenElmtCommand(xml_, m_elmt_scene));
|
|
}
|
|
}
|
|
|