From d40ed70fb9435a591c9dc8e9f65323e4ecab2de4 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sat, 13 May 2023 18:44:20 +0200 Subject: [PATCH] Implemented a solution for issue #1348 (Reload Files popup prevents closing KLayout): now the reload notification is shown as a notification bar at the top of the layout view --- src/lay/lay/layMainWindow.cc | 77 ++++----------- src/layview/layview/layLayoutView_qt.cc | 123 ++++++++++++++++++++++++ src/layview/layview/layLayoutView_qt.h | 111 +++++++++++++++++++++ 3 files changed, 255 insertions(+), 56 deletions(-) diff --git a/src/lay/lay/layMainWindow.cc b/src/lay/lay/layMainWindow.cc index 277a702eb..390263bb6 100644 --- a/src/lay/lay/layMainWindow.cc +++ b/src/lay/lay/layMainWindow.cc @@ -590,12 +590,7 @@ MainWindow::file_changed_timer () } - // Prevent recursive signals - m_file_changed_timer.blockSignals (true); - - std::set reloaded_files; - -BEGIN_PROTECTED + std::set notified_files; // Make the names unique std::sort (m_changed_files.begin (), m_changed_files.end ()); @@ -605,68 +600,38 @@ BEGIN_PROTECTED return; } - QString msg; - - if (m_changed_files.size () == 1) { - msg = QObject::tr ("The following file has been changed on disk:\n\n"); - for (std::vector::const_iterator f = m_changed_files.begin (); f != m_changed_files.end (); ++f) { - msg += QString::fromUtf8 (" %1\n").arg (*f); - } - msg += tr ("\nReload this file?"); - } else { - msg = QObject::tr ("The following files have been changed on disk:\n\n"); - for (std::vector::const_iterator f = m_changed_files.begin (); f != m_changed_files.end (); ++f) { - msg += QString::fromUtf8 (" %1\n").arg (*f); - } - msg += tr ("\nReload these files?"); - } + // adds a notification to the involved views that the file has changed std::vector changed_files; changed_files.swap (m_changed_files); - if (QMessageBox::question (this, tr ("Reload Files"), msg, QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { + std::map > > views_per_file; - m_file_changed_timer.blockSignals (false); - - std::map > views_per_file; - - for (std::vector::iterator v = mp_views.begin (); v != mp_views.end (); ++v) { - for (int cv = 0; cv < int ((*v)->view ()->cellviews ()); ++cv) { - views_per_file [tl::to_qstring ((*v)->view ()->cellview (cv)->filename ())] = std::make_pair (*v, cv); - } + for (auto v = mp_views.begin (); v != mp_views.end (); ++v) { + for (int cv = 0; cv < int ((*v)->view ()->cellviews ()); ++cv) { + views_per_file [tl::to_qstring ((*v)->view ()->cellview (cv)->filename ())].push_back (std::make_pair (*v, cv)); } - - for (std::vector::const_iterator f = changed_files.begin (); f != changed_files.end (); ++f) { - std::map >::const_iterator v = views_per_file.find (*f); - if (v != views_per_file.end ()) { - v->second.first->view ()->reload_layout (v->second.second); - reloaded_files.insert (*f); - } - } - } -END_PROTECTED + for (auto f = changed_files.begin (); f != changed_files.end (); ++f) { - m_file_changed_timer.blockSignals (false); + auto v = views_per_file.find (*f); + if (v != views_per_file.end ()) { - // While the message box was open, new request might have collected - remove - // the ones we just reloaded and restart the timer - if (! m_changed_files.empty ()) { + for (auto w = v->second.begin (); w != v->second.end (); ++w) { + + std::string title; + if (w->first->view ()->cellviews () > 1) { + title = tl::sprintf (tl::to_string (tr ("Layout file @%d (%s) has changed on disk")), w->second + 1, tl::filename (tl::to_string (*f))); + } else { + title = tl::sprintf (tl::to_string (tr ("Layout file (%s) has changed on disk")), tl::filename (tl::to_string (*f))); + } + + lay::LayoutViewNotification n ("reload", title, tl::Variant (tl::to_string (*f))); + n.add_action ("reload", tl::to_string (tr ("Reload"))); + w->first->add_notification (n); - std::vector changed_files; - changed_files.swap (m_changed_files); - for (std::vector::const_iterator f = changed_files.begin (); f != changed_files.end (); ++f) { - if (reloaded_files.find (*f) == reloaded_files.end ()) { - m_changed_files.push_back (*f); } - } - - if (! m_changed_files.empty ()) { - - // Wait a little to let more to allow for more reload requests to collect - m_file_changed_timer.setInterval (300); - m_file_changed_timer.start (); } diff --git a/src/layview/layview/layLayoutView_qt.cc b/src/layview/layview/layLayoutView_qt.cc index 9b3f04286..8660fe15d 100644 --- a/src/layview/layview/layLayoutView_qt.cc +++ b/src/layview/layview/layLayoutView_qt.cc @@ -39,6 +39,8 @@ #include #include #include +#include +#include #include "tlInternational.h" #include "tlExpression.h" @@ -76,6 +78,7 @@ #include "layEditorOptionsPages.h" #include "layUtils.h" #include "layPropertiesDialog.h" +#include "layQtTools.h" #include "dbClipboard.h" #include "dbLayout.h" #include "dbLayoutUtils.h" @@ -94,12 +97,70 @@ namespace lay { +// ------------------------------------------------------------- +// LayoutViewNotificationWidget implementation + +LayoutViewNotificationWidget::LayoutViewNotificationWidget (LayoutViewWidget *parent, const LayoutViewNotification *notification) + : QFrame (parent), mp_parent (parent), mp_notification (notification) +{ + setBackgroundRole (QPalette::ToolTipBase); + setAutoFillBackground (true); + + QHBoxLayout *layout = new QHBoxLayout (this); + layout->setContentsMargins (4, 4, 4, 4); + + QLabel *title_label = new QLabel (this); + layout->addWidget (title_label, 1); + title_label->setText (tl::to_qstring (notification->title ())); + title_label->setForegroundRole (QPalette::ToolTipText); + title_label->setWordWrap (true); + activate_help_links (title_label); + + for (auto a = notification->actions ().begin (); a != notification->actions ().end (); ++a) { + + QPushButton *pb = new QPushButton (this); + layout->addWidget (pb); + + pb->setText (tl::to_qstring (a->second)); + m_action_buttons.insert (std::make_pair (pb, a->first)); + connect (pb, SIGNAL (clicked ()), this, SLOT (action_triggered ())); + + } + + QToolButton *close_button = new QToolButton (); + close_button->setIcon (QIcon (":clear_edit_16px.png")); + close_button->setAutoRaise (true); + layout->addWidget (close_button); + + connect (close_button, SIGNAL (clicked ()), this, SLOT (close_triggered ())); +} + +void +LayoutViewNotificationWidget::action_triggered () +{ + auto a = m_action_buttons.find (sender ()); + if (a != m_action_buttons.end ()) { + mp_parent->notification_action (*mp_notification, a->second); + } +} + +void +LayoutViewNotificationWidget::close_triggered () +{ + mp_parent->remove_notification (*mp_notification); +} + // ------------------------------------------------------------- // LayoutViewWidget implementation LayoutViewWidget::LayoutViewWidget (db::Manager *mgr, bool editable, lay::Plugin *plugin_parent, QWidget *parent, unsigned int options) : QFrame (parent), mp_view (0) { + mp_layout = new QVBoxLayout (this); + mp_layout->setContentsMargins (0, 0, 0, 0); + mp_layout->setSpacing (0); + mp_layout->addStretch (1); + // NOTE: construction the LayoutView may trigger events (script code executed etc.) which must // not meet an invalid mp_view pointer (e.g. in eventFilter). Hence, mp_view is 0 first, and set only // after the LayoutView is successfully constructed. @@ -110,6 +171,11 @@ LayoutViewWidget::LayoutViewWidget (db::Manager *mgr, bool editable, lay::Plugin LayoutViewWidget::LayoutViewWidget (lay::LayoutView *source, db::Manager *mgr, bool editable, lay::Plugin *plugin_parent, QWidget *parent, unsigned int options) : QFrame (parent), mp_view (0) { + mp_layout = new QVBoxLayout (this); + mp_layout->setContentsMargins (0, 0, 0, 0); + mp_layout->setSpacing (0); + mp_layout->addStretch (1); + // NOTE: construction the LayoutView may trigger events (script code executed etc.) which must // not meet an invalid mp_view pointer (e.g. in eventFilter). Hence, mp_view is 0 first, and set only // after the LayoutView is successfully constructed. @@ -124,6 +190,55 @@ LayoutViewWidget::~LayoutViewWidget () delete view; } +void +LayoutViewWidget::add_notification (const LayoutViewNotification ¬ificaton) +{ + if (m_notification_widgets.find (¬ificaton) == m_notification_widgets.end ()) { + m_notifications.push_back (notificaton); + QWidget *w = new LayoutViewNotificationWidget (this, &m_notifications.back ()); + m_notification_widgets.insert (std::make_pair (&m_notifications.back (), w)); + mp_layout->insertWidget (0, w); + } +} + +void +LayoutViewWidget::remove_notification (const LayoutViewNotification ¬ification) +{ + auto nw = m_notification_widgets.find (¬ification); + if (nw != m_notification_widgets.end ()) { + + nw->second->deleteLater (); + m_notification_widgets.erase (nw); + + for (auto n = m_notifications.begin (); n != m_notifications.end (); ++n) { + if (*n == notification) { + m_notifications.erase (n); + break; + } + } + + } +} + +void +LayoutViewWidget::notification_action (const LayoutViewNotification ¬ification, const std::string &action) +{ + if (action == "reload") { + + std::string fn = notification.parameter ().to_string (); + + for (unsigned int cvi = 0; cvi < mp_view->cellviews (); ++cvi) { + const lay::CellView &cv = mp_view->cellview (cvi); + if (cv->filename () == fn) { + mp_view->reload_layout (cvi); + } + } + + remove_notification (notification); + + } +} + void LayoutViewWidget::view_deleted (lay::LayoutView *view) { @@ -135,6 +250,14 @@ LayoutViewWidget::view_deleted (lay::LayoutView *view) mp_view = new LayoutView (view->manager (), view->is_editable (), view->plugin_parent (), this, view->options ()); } +void +LayoutViewWidget::resizeEvent (QResizeEvent *) +{ + if (mp_view && mp_view->canvas ()) { + mp_view->canvas ()->resize (width (), height ()); + } +} + QSize LayoutViewWidget::sizeHint () const { diff --git a/src/layview/layview/layLayoutView_qt.h b/src/layview/layview/layLayoutView_qt.h index 294f85f70..b956c9348 100644 --- a/src/layview/layview/layLayoutView_qt.h +++ b/src/layview/layview/layLayoutView_qt.h @@ -57,6 +57,7 @@ #include #include +#include class QSpinBox; @@ -683,6 +684,91 @@ private: void activate_editor_option_pages (); }; +/** + * @brief Descriptor for a notification inside the layout view + * + * Notifications are popups added at the top of the view to indicate need for reloading for example. + * Notifications have a name, a title, optional actions (id, title) and a parameter (e.g. file path to reload). + * Actions are mapped to QPushButtons. + */ +class LAYVIEW_PUBLIC LayoutViewNotification +{ +public: + LayoutViewNotification (const std::string &name, const std::string &title, const tl::Variant ¶meter = tl::Variant ()) + : m_name (name), m_title (title), m_parameter (parameter) + { + // .. nothing yet .. + } + + void add_action (const std::string &name, const std::string &title) + { + m_actions.push_back (std::make_pair (name, title)); + } + + const std::vector > &actions () const + { + return m_actions; + } + + const std::string &name () const + { + return m_name; + } + + const std::string &title () const + { + return m_title; + } + + const tl::Variant ¶meter () const + { + return m_parameter; + } + + bool operator<(const LayoutViewNotification &other) const + { + if (m_name != other.name ()) { + return m_name < other.name (); + } + return m_parameter < other.parameter (); + } + + bool operator==(const LayoutViewNotification &other) const + { + if (m_name != other.name ()) { + return false; + } + return m_parameter == other.parameter (); + } + +private: + std::string m_name; + std::string m_title; + tl::Variant m_parameter; + std::vector > m_actions; +}; + +/** + * @brief A widget representing a notification + */ +class LAYVIEW_PUBLIC LayoutViewNotificationWidget + : public QFrame +{ +Q_OBJECT + +public: + LayoutViewNotificationWidget (LayoutViewWidget *parent, const LayoutViewNotification *notification); + +private slots: + void action_triggered (); + void close_triggered (); + +private: + LayoutViewWidget *mp_parent; + const LayoutViewNotification *mp_notification; + std::map m_action_buttons; +}; + /** * @brief The layout view widget * @@ -710,6 +796,16 @@ public: */ ~LayoutViewWidget (); + /** + * @brief Adds a notification + */ + void add_notification (const LayoutViewNotification ¬ificaton); + + /** + * @brief Removes a notification + */ + void remove_notification (const LayoutViewNotification ¬ificaton); + /** * @brief Gets the LayoutView embedded into this widget */ @@ -817,10 +913,25 @@ signals: private: friend class LayoutView; + friend class LayoutViewNotificationWidget; void view_deleted (lay::LayoutView *view); + void notification_action (const LayoutViewNotification ¬ification, const std::string &action); + + void resizeEvent (QResizeEvent *event); + + struct CompareNotificationPointers + { + bool operator() (const LayoutViewNotification *a, const LayoutViewNotification *b) const + { + return *a < *b; + } + }; LayoutView *mp_view; + QVBoxLayout *mp_layout; + std::list m_notifications; + std::map m_notification_widgets; }; /**