Non-modal notifications for macro editors too

This commit is contained in:
Matthias Koefferlein 2023-09-02 00:36:17 +02:00
parent c4e57d2bce
commit 3d0bb8516b
4 changed files with 282 additions and 51 deletions

View File

@ -2373,6 +2373,15 @@ BEGIN_PROTECTED
END_PROTECTED
}
void
MacroEditorDialog::close_requested ()
{
MacroEditorPage *page = dynamic_cast<MacroEditorPage *> (sender ());
if (! m_in_exec && page) {
tab_close_requested (tabWidget->indexOf (page));
}
}
void
MacroEditorDialog::tab_close_requested (int index)
{
@ -2605,66 +2614,65 @@ BEGIN_PROTECTED
return;
}
std::set<std::string> modified_files;
for (std::map <lym::Macro *, MacroEditorPage *>::const_iterator m = m_tab_widgets.begin (); m != m_tab_widgets.end (); ++m) {
if (m->first->is_modified ()) {
modified_files.insert (m->first->path ());
std::map<std::string, MacroEditorPage *> path_to_page;
for (int i = 0; i < tabWidget->count (); ++i) {
MacroEditorPage *page = dynamic_cast<MacroEditorPage *> (tabWidget->widget (i));
if (page) {
path_to_page.insert (std::make_pair (page->path (), page));
}
}
std::vector<QString> conflicts;
for (std::vector<QString>::const_iterator f = m_changed_files.begin (); f != m_changed_files.end (); ++f) {
if (modified_files.find (tl::to_string (*f)) != modified_files.end ()) {
conflicts.push_back (*f);
std::string fn = tl::to_string (*f);
auto w = path_to_page.find (fn);
if (w == path_to_page.end ()) {
continue;
}
if (w->second->macro () && w->second->macro ()->is_modified ()) {
lay::MacroEditorNotification n ("reload", tl::to_string (tr ("Macro has changed on disk, but was modified")), tl::Variant (fn));
n.add_action ("reload", tl::to_string (tr ("Reload and discard changes")));
w->second->add_notification (n);
} else {
lay::MacroEditorNotification n ("reload", tl::to_string (tr ("Macro has changed on disk")), tl::Variant (fn));
n.add_action ("reload", tl::to_string (tr ("Reload")));
w->second->add_notification (n);
}
}
for (std::vector<QString>::const_iterator f = m_removed_files.begin (); f != m_removed_files.end (); ++f) {
if (modified_files.find (tl::to_string (*f)) != modified_files.end ()) {
conflicts.push_back (*f);
std::string fn = tl::to_string (*f);
auto w = path_to_page.find (fn);
if (w == path_to_page.end ()) {
continue;
}
if (w->second->macro () && w->second->macro ()->is_modified ()) {
lay::MacroEditorNotification n ("close", tl::to_string (tr ("Macro has been removed on disk, but was modified")), tl::Variant (fn));
n.add_action ("close", tl::to_string (tr ("Close tab and discard changes")));
w->second->add_notification (n);
} else {
lay::MacroEditorNotification n ("close", tl::to_string (tr ("Macro has been removed on disk")), tl::Variant (fn));
n.add_action ("close", tl::to_string (tr ("Close tab")));
w->second->add_notification (n);
}
}
QString msg;
if (m_changed_files.size () + m_removed_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 (modified)\n").arg (*f);
}
for (std::vector<QString>::const_iterator f = m_removed_files.begin (); f != m_removed_files.end (); ++f) {
msg += QString::fromUtf8 (" %1 (removed)\n").arg (*f);
}
if (!conflicts.empty ()) {
msg += tr ("\nThis file has been modified in the editor as well.\nRefresh this file and discard changes?");
} else {
msg += tr ("\nRefresh 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 (modified)\n").arg (*f);
}
for (std::vector<QString>::const_iterator f = m_removed_files.begin (); f != m_removed_files.end (); ++f) {
msg += QString::fromUtf8 (" %1 (removed)\n").arg (*f);
}
if (!conflicts.empty ()) {
msg += tr("\nSome of these files are modified on disk and in the editor:\n\n");
for (std::vector<QString>::const_iterator f = conflicts.begin (); f != conflicts.end (); ++f) {
msg += QString::fromUtf8 (" %1 (conflict)\n").arg (*f);
}
msg += tr ("\nRefresh these and the other files and discard all changes?");
} else {
msg += tr ("\nRefresh those files?");
}
}
refresh_file_watcher ();
m_changed_files.clear ();
m_removed_files.clear ();
if (QMessageBox::question (this, tr ("Refresh Files"), msg, QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) {
reload_macros ();
}
END_PROTECTED
}
@ -3523,6 +3531,7 @@ MacroEditorDialog::create_page (lym::Macro *macro)
editor->set_font (m_font_family, m_font_size);
editor->exec_model ()->set_run_mode (m_in_exec);
editor->connect_macro (macro);
connect (editor.get (), SIGNAL (close_requested ()), this, SLOT (close_requested ()));
connect (editor.get (), SIGNAL (help_requested (const QString &)), this, SLOT (help_requested (const QString &)));
connect (editor.get (), SIGNAL (search_requested (const QString &, bool)), this, SLOT (search_requested (const QString &, bool)));
connect (editor.get (), SIGNAL (edit_trace (bool)), this, SLOT (add_edit_trace (bool)));

View File

@ -212,6 +212,7 @@ private slots:
void search_editing ();
void search_finished ();
void tab_close_requested (int);
void close_requested ();
void close_all ();
void close_all_but_this ();
void close_all_left ();

View File

@ -46,6 +46,8 @@
#include <QTimer>
#include <QListWidget>
#include <QApplication>
#include <QToolButton>
#include <QPushButton>
namespace lay
{
@ -81,6 +83,59 @@ void MacroEditorTextWidget::paintEvent (QPaintEvent *event)
return TextEditWidget::paintEvent (event);
}
// ----------------------------------------------------------------------------------------------
// MacroEditorNotificationWidget implementation
MacroEditorNotificationWidget::MacroEditorNotificationWidget (MacroEditorPage *parent, const MacroEditorNotification *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
MacroEditorNotificationWidget::action_triggered ()
{
auto a = m_action_buttons.find (sender ());
if (a != m_action_buttons.end ()) {
mp_parent->notification_action (*mp_notification, a->second);
}
}
void
MacroEditorNotificationWidget::close_triggered ()
{
mp_parent->remove_notification (*mp_notification);
}
// ----------------------------------------------------------------------------------------------
// MacroEditorHighlighters implementation
@ -491,15 +546,17 @@ void MacroEditorSidePanel::paintEvent (QPaintEvent *)
MacroEditorPage::MacroEditorPage (QWidget * /*parent*/, MacroEditorHighlighters *highlighters)
: mp_macro (0), mp_highlighters (highlighters), mp_highlighter (0), m_error_line (-1), m_ntab (8), m_nindent (2), m_ignore_cursor_changed_event (false)
{
QVBoxLayout *layout = new QVBoxLayout (this);
mp_layout = new QVBoxLayout (this);
mp_layout->setContentsMargins (0, 0, 0, 0);
mp_readonly_label = new QLabel (this);
mp_readonly_label->setText (QObject::tr ("Macro is read-only and cannot be edited"));
mp_readonly_label->hide ();
layout->addWidget (mp_readonly_label);
mp_layout->addWidget (mp_readonly_label);
QHBoxLayout *hlayout = new QHBoxLayout ();
layout->addLayout (hlayout);
hlayout->setContentsMargins (4, 4, 4, 4);
mp_layout->addLayout (hlayout);
mp_exec_model = new MacroEditorExecutionModel (this);
mp_text = new MacroEditorTextWidget (this);
@ -1021,6 +1078,8 @@ void MacroEditorPage::connect_macro (lym::Macro *macro)
if (mp_macro) {
m_path = mp_macro->path ();
connect (mp_macro, SIGNAL (changed ()), this, SLOT (update ()));
lym::Macro::Interpreter lang = macro->interpreter ();
@ -1924,5 +1983,54 @@ MacroEditorPage::eventFilter (QObject *watched, QEvent *event)
return false;
}
void
MacroEditorPage::add_notification (const MacroEditorNotification &notification)
{
if (m_notification_widgets.find (&notification) == m_notification_widgets.end ()) {
m_notifications.push_back (notification);
QWidget *w = new MacroEditorNotificationWidget (this, &m_notifications.back ());
m_notification_widgets.insert (std::make_pair (&m_notifications.back (), w));
mp_layout->insertWidget (0, w);
}
}
void
MacroEditorPage::remove_notification (const MacroEditorNotification &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
MacroEditorPage::notification_action (const MacroEditorNotification &notification, const std::string &action)
{
if (action == "close") {
remove_notification (notification);
emit close_requested ();
} else if (action == "reload") {
remove_notification (notification);
mp_macro->load ();
mp_macro->reset_modified ();
}
}
}

View File

@ -27,6 +27,7 @@
#include "lymMacro.h"
#include "layGenericSyntaxHighlighter.h"
#include "tlVariant.h"
#include <QDialog>
#include <QPixmap>
@ -47,10 +48,13 @@ class QSyntaxHighlighter;
class QTimer;
class QWindow;
class QListWidget;
class QVBoxLayout;
namespace lay
{
class MacroEditorPage;
/**
* @brief A collection of highlighters
*/
@ -209,7 +213,92 @@ private:
bool m_debugging_on;
};
class MacroEditorPage
/**
* @brief Descriptor for a notification inside the macro editor
*
* 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 MacroEditorNotification
{
public:
MacroEditorNotification (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 MacroEditorNotification &other) const
{
if (m_name != other.name ()) {
return m_name < other.name ();
}
return m_parameter < other.parameter ();
}
bool operator==(const MacroEditorNotification &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 MacroEditorNotificationWidget
: public QFrame
{
Q_OBJECT
public:
MacroEditorNotificationWidget (MacroEditorPage *parent, const MacroEditorNotification *notification);
private slots:
void action_triggered ();
void close_triggered ();
private:
MacroEditorPage *mp_parent;
const MacroEditorNotification *mp_notification;
std::map<QObject *, std::string> m_action_buttons;
};
class MacroEditorPage
: public QWidget
{
Q_OBJECT
@ -224,6 +313,11 @@ public:
return mp_macro;
}
const std::string path () const
{
return m_path;
}
bool is_modified () const
{
return m_is_modified;
@ -269,10 +363,14 @@ public:
void set_editor_focus ();
void add_notification (const MacroEditorNotification &notificaton);
void remove_notification (const MacroEditorNotification &notificaton);
signals:
void help_requested (const QString &s);
void search_requested (const QString &s, bool backward);
void edit_trace (bool);
void close_requested ();
public slots:
void commit ();
@ -288,10 +386,22 @@ protected slots:
void hide_completer ();
private:
friend class MacroEditorNotificationWidget;
struct CompareNotificationPointers
{
bool operator() (const MacroEditorNotification *a, const MacroEditorNotification *b) const
{
return *a < *b;
}
};
lym::Macro *mp_macro;
std::string m_path;
MacroEditorExecutionModel *mp_exec_model;
MacroEditorTextWidget *mp_text;
MacroEditorSidePanel *mp_side_panel;
QVBoxLayout *mp_layout;
QLabel *mp_readonly_label;
bool m_is_modified;
MacroEditorHighlighters *mp_highlighters;
@ -305,6 +415,8 @@ private:
QTimer *mp_completer_timer;
QWidget *mp_completer_popup;
QListWidget *mp_completer_list;
std::list<MacroEditorNotification> m_notifications;
std::map<const MacroEditorNotification *, QWidget *, CompareNotificationPointers> m_notification_widgets;
void update_extra_selections ();
bool return_pressed ();
@ -316,6 +428,7 @@ private:
QTextCursor get_completer_cursor (int &pos0, int &pos);
bool select_match_here ();
void replace_in_selection (const QString &replace, bool first);
void notification_action (const MacroEditorNotification &notification, const std::string &action);
bool eventFilter (QObject *watched, QEvent *event);
};