/* 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 "ui_terminalstripeditorwindow.h" #include "../UndoCommand/addterminalstripcommand.h" #include "freeterminaleditor.h" #include "../../qetapp.h" #include "../../qetdiagrameditor.h" #include "../../qetproject.h" #include "../realterminal.h" #include "../terminalstrip.h" #include "../terminalstripautoimport.h" #include "terminalstripcreatordialog.h" #include "terminalstripeditor.h" #include "terminalstripeditorwindow.h" #include "terminalstriptreedockwidget.h" #include #include #include #include #include #include QPointer TerminalStripEditorWindow::window_; /** * @brief TerminalStripEditorWindow::instance * @param project : the project to edit * @param parent : parent widget of the window, when it is created * @return the terminal block manager window, created the first time, * showing the terminal strips of @a project. * Opening it reads every terminal of the project (and builds the folios * not built yet) : it can take a while on a big project. A message and a * busy cursor say that it is opening, so that the user does not think * that nothing happens. */ TerminalStripEditorWindow *TerminalStripEditorWindow::instance(QETProject *project, QWidget *parent) { QProgressDialog busy_(tr("Opening the terminal block manager..."), QString(), 0, 0, parent); busy_.setWindowTitle(tr("Terminal block manager")); //Only a title : nothing to minimize, maximize or close busy_.setWindowFlags(Qt::Dialog | Qt::CustomizeWindowHint | Qt::WindowTitleHint); busy_.setWindowModality(Qt::ApplicationModal); busy_.setCancelButton(nullptr); busy_.setMinimumDuration(0); busy_.show(); //Paint the message before the long work starts. One pass of the //event loop is not enough : the window is shown by the window system //(Wayland compositor...) a bit later. Wait for it to be exposed, //a short time at most, then paint it. QElapsedTimer timer_; timer_.start(); while (timer_.elapsed() < 500) { QCoreApplication::processEvents(QEventLoop::ExcludeUserInputEvents, 20); if (busy_.windowHandle() && busy_.windowHandle()->isExposed()) { break; } QThread::msleep(5); } busy_.repaint(); QCoreApplication::processEvents(QEventLoop::ExcludeUserInputEvents); QApplication::setOverrideCursor(Qt::WaitCursor); //The folios not built yet (lazy folios) are built now : show the //progress, and run the event loop between two folios, so that the //window system still gets answers (else the message is said //"not responding") if (project) { project->setFolioBuildObserver([&busy_](int built, int total) { if (busy_.maximum() != total) { busy_.setMaximum(total); } busy_.setLabelText(tr("Reading folio %1 of %2...").arg(built).arg(total)); busy_.setValue(built); QCoreApplication::processEvents(QEventLoop::ExcludeUserInputEvents); }); } static QMutex mutex_; if (!window_) { mutex_.lock(); if (!window_) window_ = new TerminalStripEditorWindow{project, parent}; mutex_.unlock(); } else { window_->setProject(project); } if (project) { project->setFolioBuildObserver({}); } QApplication::restoreOverrideCursor(); return window_; } static const int EMPTY_PAGE = 0; static const int FREE_TERMINAL_PAGE = 1; static const int TERMINAL_STRIP_PAGE = 2; /** * @brief TerminalStripEditorWindow::TerminalStripEditorWindow * @param project * @param parent */ void TerminalStripEditorWindow::edit(TerminalStrip *strip) { if (const auto project_ = strip->project()) { auto editor_ = TerminalStripEditorWindow::instance(project_, QETApp::diagramEditor(project_)); editor_->setCurrentStrip(strip); editor_->show(); } } TerminalStripEditorWindow::TerminalStripEditorWindow(QETProject *project, QWidget *parent) : QMainWindow(parent), ui(new Ui::TerminalStripEditorWindow), m_project(project) { ui->setupUi(this); if (auto diagram_editor = QETApp::diagramEditor(project)) { ui->m_tool_bar->addSeparator(); ui->m_tool_bar->addAction(diagram_editor->undo); ui->m_tool_bar->addAction(diagram_editor->redo); } ui->m_remove_terminal->setDisabled(true); addTreeDockWidget(); m_free_terminal_editor = new FreeTerminalEditor(m_project, this); m_terminal_strip_editor = new TerminalStripEditor{m_project, this}; connect(m_tree_dock, &TerminalStripTreeDockWidget::currentStripChanged, this, &TerminalStripEditorWindow::currentStripChanged); //A move reloads the free terminal table, which would lose its unapplied edits m_tree_dock->setDropCheck([this]() { return !m_free_terminal_editor->hasPendingEdits(); }); ui->m_stacked_widget->insertWidget(EMPTY_PAGE, new QWidget(ui->m_stacked_widget)); ui->m_stacked_widget->insertWidget(FREE_TERMINAL_PAGE, m_free_terminal_editor); ui->m_stacked_widget->insertWidget(TERMINAL_STRIP_PAGE, m_terminal_strip_editor); //The strips are worked out from the labels of the free terminals, //so that the user does not create them one by one : only the //toolbar actions run the import, opening the window changes //nothing } /** * @brief TerminalStripEditorWindow::~TerminalStripEditorWindow */ TerminalStripEditorWindow::~TerminalStripEditorWindow() { delete ui; } /** * @brief TerminalStripEditorWindow::setProject * @param project */ void TerminalStripEditorWindow::setProject(QETProject *project) { m_project = project; m_tree_dock->setProject(project); m_free_terminal_editor->setProject(project); m_terminal_strip_editor->setProject(project); } /** * @brief TerminalStripEditorWindow::importFreeTerminals * Run one of the two import actions of the window, after the user has * confirmed it : @a move_placed false assigns the free terminals only, * true re-groups every terminal and also moves placed ones. Both run as * one undoable step and say what has been done. */ void TerminalStripEditorWindow::importFreeTerminals(bool move_placed) { if (!m_project) { return; } //A reload of the free terminal table would lose its unapplied edits if (m_free_terminal_editor && m_free_terminal_editor->hasPendingEdits()) { QMessageBox::information( this, tr("Unapplied edits"), tr("Apply or discard the pending edits of the free terminals " "table first.")); return; } const auto title_ = move_placed ? tr("Re-group all terminals") : tr("Assign free terminals"); const auto text_ = move_placed ? tr("Free terminals will be added to the terminal strips named " "after their labels, and every terminal whose label prefix " "or plant and location no longer match its strip will be " "moved to the right strip.\n\n" "Terminals you placed by hand in a strip whose name does " "not match their label will be moved too. Existing strips " "are never re-sorted.\n\n" "This action can be undone with a single Undo.") : tr("Every free terminal will be added to the terminal strip " "named after its label (\"X1:2\" goes to the strip \"X1\"), " "creating the strips when needed.\n\n" "Terminals that already belong to a strip are not touched " "and no strip is re-sorted.\n\n" "This action can be undone with a single Undo."); if (QMessageBox::question(this, title_, text_, QMessageBox::Yes | QMessageBox::No, QMessageBox::No) != QMessageBox::Yes) { return; } const auto result_ = TerminalStripAutoImport::importFreeTerminals( m_project, move_placed); if (result_.terminals == 0 && result_.strips == 0) { return; } m_tree_dock->reload(); if (move_placed) { statusBar()->showMessage( tr("%1 terminal(s) assigned or moved to a terminal strip.", "", result_.terminals).arg(result_.terminals), 10000); } else { statusBar()->showMessage( tr("%1 free terminal(s) added to the terminal strips.", "", result_.terminals).arg(result_.terminals), 10000); } } void TerminalStripEditorWindow::on_m_import_free_terminals_triggered() { importFreeTerminals(false); } void TerminalStripEditorWindow::on_m_regroup_terminals_triggered() { importFreeTerminals(true); } void TerminalStripEditorWindow::setCurrentStrip(TerminalStrip *strip) { m_tree_dock->setSelectedStrip(strip); } /** * @brief TerminalStripEditorWindow::addTreeDockWidget */ void TerminalStripEditorWindow::addTreeDockWidget() { m_tree_dock = new TerminalStripTreeDockWidget(m_project, this); m_tree_dock->setAllowedAreas(Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea); addDockWidget(Qt::LeftDockWidgetArea, m_tree_dock); } /** * @brief TerminalStripEditorWindow::currentStripChanged * @param strip */ void TerminalStripEditorWindow::currentStripChanged(TerminalStrip *strip) { Q_UNUSED(strip) updateUi(); } void TerminalStripEditorWindow::updateUi() { ui->m_remove_terminal->setEnabled(m_tree_dock->currentIsStrip()); ui->m_stacked_widget->setCurrentIndex(EMPTY_PAGE); if (auto real_terminal = m_tree_dock->currentRealTerminal()) { if (!real_terminal->parentStrip()) { ui->m_stacked_widget->setCurrentIndex(FREE_TERMINAL_PAGE); m_free_terminal_editor->reload(); } } else if (auto strip_ = m_tree_dock->currentStrip()) { ui->m_stacked_widget->setCurrentIndex(TERMINAL_STRIP_PAGE); m_terminal_strip_editor->setCurrentStrip(strip_); } } /** * @brief TerminalStripEditorWindow::on_m_add_terminal_strip_triggered * Action when user click on add terminal strip button */ void TerminalStripEditorWindow::on_m_add_terminal_strip_triggered() { QScopedPointer dialog(new TerminalStripCreatorDialog(m_project, this)); dialog->setLocation(m_tree_dock->currentLocation()); dialog->setInstallation(m_tree_dock->currentInstallation()); if (dialog->exec() == QDialog::Accepted) { auto ts = dialog->generatedTerminalStrip(); m_project->undoStack()->push(new AddTerminalStripCommand(ts, m_project)); m_tree_dock->reload(); m_tree_dock->setSelectedStrip(ts); } } /** * @brief TerminalStripEditorWindow::on_m_remove_terminal_triggered */ void TerminalStripEditorWindow::on_m_remove_terminal_triggered() { if (m_tree_dock->currentIsStrip()) { if (auto strip_ = m_tree_dock->currentStrip()) { m_project->undoStack()->push(new RemoveTerminalStripCommand(strip_, m_project)); m_tree_dock->reload(); } } } /** * @brief TerminalStripEditorWindow::on_m_reload_triggered */ void TerminalStripEditorWindow::on_m_reload_triggered() { m_tree_dock->reload(); m_terminal_strip_editor->reload(); m_free_terminal_editor->reload(); } /** * @brief TerminalStripEditorWindow::on_m_button_box_clicked * Action when user click on the apply/reset button * @param button */ void TerminalStripEditorWindow::on_m_button_box_clicked(QAbstractButton *button) { auto role_{ui->m_button_box->buttonRole(button)}; if (role_ == QDialogButtonBox::ApplyRole) { switch (ui->m_stacked_widget->currentIndex()) { case FREE_TERMINAL_PAGE: m_free_terminal_editor->apply(); break; case TERMINAL_STRIP_PAGE: m_terminal_strip_editor->apply(); break; default: break; } } else if (role_ == QDialogButtonBox::ResetRole) { m_terminal_strip_editor->reload(); m_free_terminal_editor->reload(); } } void TerminalStripEditorWindow::on_m_stacked_widget_currentChanged(int arg1) { ui->m_button_box->setHidden(arg1 == EMPTY_PAGE); }