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

This commit is contained in:
Matthias Koefferlein 2023-05-13 18:44:20 +02:00
parent 7169910607
commit d40ed70fb9
3 changed files with 255 additions and 56 deletions

View File

@ -590,12 +590,7 @@ MainWindow::file_changed_timer ()
}
// Prevent recursive signals
m_file_changed_timer.blockSignals (true);
std::set<QString> reloaded_files;
BEGIN_PROTECTED
std::set<QString> 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<QString>::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<QString>::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<QString> changed_files;
changed_files.swap (m_changed_files);
if (QMessageBox::question (this, tr ("Reload Files"), msg, QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) {
std::map<QString, std::vector<std::pair<lay::LayoutViewWidget *, int> > > views_per_file;
m_file_changed_timer.blockSignals (false);
std::map<QString, std::pair<lay::LayoutViewWidget *, int> > views_per_file;
for (std::vector<lay::LayoutViewWidget *>::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<QString>::const_iterator f = changed_files.begin (); f != changed_files.end (); ++f) {
std::map<QString, std::pair<lay::LayoutViewWidget *, int> >::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<QString> changed_files;
changed_files.swap (m_changed_files);
for (std::vector<QString>::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 ();
}

View File

@ -39,6 +39,8 @@
#include <QHBoxLayout>
#include <QMessageBox>
#include <QApplication>
#include <QPushButton>
#include <QToolButton>
#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 &notificaton)
{
if (m_notification_widgets.find (&notificaton) == 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 &notification)
{
auto nw = m_notification_widgets.find (&notification);
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 &notification, 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
{

View File

@ -57,6 +57,7 @@
#include <QImage>
#include <QPointer>
#include <QVBoxLayout>
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 &parameter = 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<std::pair<std::string, std::string> > &actions () const
{
return m_actions;
}
const std::string &name () const
{
return m_name;
}
const std::string &title () const
{
return m_title;
}
const tl::Variant &parameter () 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<std::pair<std::string, std::string> > 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<QObject *, std::string> m_action_buttons;
};
/**
* @brief The layout view widget
*
@ -710,6 +796,16 @@ public:
*/
~LayoutViewWidget ();
/**
* @brief Adds a notification
*/
void add_notification (const LayoutViewNotification &notificaton);
/**
* @brief Removes a notification
*/
void remove_notification (const LayoutViewNotification &notificaton);
/**
* @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 &notification, 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<lay::LayoutViewNotification> m_notifications;
std::map<const LayoutViewNotification *, QWidget *, CompareNotificationPointers> m_notification_widgets;
};
/**