Fixed issue-1131 (do not show non-existing files in MRU lists) (#1133)

* Fixed issue-1131 (do not show files in MRU lists which do no longer exist)

The solution consists of an extension of the Action system allowing to
dynamically hide or disable items. This currently works for menu items only.

This feature is used to dynamically *disable* (as of now, not hiding)
items from the four MRU lists corresponding to non-existing files.

In addition, a "clear list" menu has been added to the MRU lists.

* Small enhancement: file names can be URIs
This commit is contained in:
Matthias Köfferlein 2022-08-01 18:49:42 +02:00 committed by GitHub
parent e2f9015c26
commit 801ef78990
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 296 additions and 96 deletions

View File

@ -51,6 +51,8 @@
#include "tlStream.h"
#include "tlExceptions.h"
#include "tlExpression.h"
#include "tlFileUtils.h"
#include "tlUri.h"
#include "dbMemStatistics.h"
#include "dbManager.h"
#include "dbStream.h"
@ -2902,29 +2904,28 @@ MainWindow::add_mru (const std::string &fn_rel)
add_mru (fn_rel, m_initial_technology);
}
const size_t max_mru = 16;
const size_t max_mru = 16; // TODO: make configurable?
void
MainWindow::add_mru (const std::string &fn_rel, const std::string &tech)
{
std::vector <std::pair<std::string, std::string> > new_mru (m_mru);
std::vector <std::pair<std::string, std::string> > new_mru;
std::string fn (tl::InputStream::absolute_path (fn_rel));
for (std::vector<std::pair<std::string, std::string> >::iterator mru = new_mru.begin (); mru != new_mru.end (); ++mru) {
if (mru->first == fn) {
new_mru.erase (mru);
break;
for (auto mru = m_mru.begin (); mru != m_mru.end (); ++mru) {
if (mru->first != fn /* delete non-existing files: && tl::is_readable (mru->first) */) {
new_mru.push_back (*mru);
}
}
new_mru.push_back (std::make_pair (fn, tech));
if (new_mru.size () > max_mru) {
new_mru.erase (new_mru.begin ());
new_mru.erase (new_mru.begin (), new_mru.end () - max_mru);
}
std::string config_str;
for (std::vector<std::pair<std::string, std::string> >::const_iterator mru = new_mru.begin (); mru != new_mru.end (); ++mru) {
for (auto mru = new_mru.begin (); mru != new_mru.end (); ++mru) {
if (! config_str.empty ()) {
config_str += " ";
}
@ -2952,20 +2953,19 @@ MainWindow::add_to_other_mru (const std::string &fn_rel, const std::string &cfg)
tl_assert (false);
}
std::vector <std::string> new_mru = *mru_ptr;
std::vector <std::string> new_mru;
std::string fn (tl::InputStream::absolute_path (fn_rel));
for (std::vector<std::string>::iterator mru = new_mru.begin (); mru != new_mru.end (); ++mru) {
if (*mru == fn) {
new_mru.erase (mru);
break;
for (auto mru = mru_ptr->begin (); mru != mru_ptr->end (); ++mru) {
if (*mru != fn /* delete non-existing files: && tl::is_readable (*mru) */) {
new_mru.push_back (*mru);
}
}
new_mru.push_back (fn);
if (new_mru.size () > max_mru) {
new_mru.erase (new_mru.begin ());
new_mru.erase (new_mru.begin (), new_mru.end () - max_mru);
}
std::string config_str;
@ -2986,72 +2986,45 @@ class OpenRecentAction
: public lay::Action
{
public:
OpenRecentAction (lay::MainWindow *mw, size_t n)
: lay::Action (), mp_mw (mw), m_n (n)
OpenRecentAction (lay::MainWindow *mw, size_t n, void (lay::MainWindow::*open_meth) (size_t), bool (lay::MainWindow::*avail_meth) (size_t))
: lay::Action (), mp_mw (mw), m_n (n), m_open_meth (open_meth), m_avail_meth (avail_meth)
{ }
void triggered ()
{
mp_mw->open_recent (m_n);
(mp_mw->*m_open_meth) (m_n);
}
bool wants_enabled () const
{
return (mp_mw->*m_avail_meth) (m_n);
}
private:
lay::MainWindow *mp_mw;
size_t m_n;
void (lay::MainWindow::*m_open_meth) (size_t);
bool (lay::MainWindow::*m_avail_meth) (size_t);
};
class OpenRecentSessionAction
class ClearRecentAction
: public lay::Action
{
public:
OpenRecentSessionAction (lay::MainWindow *mw, size_t n)
: lay::Action (), mp_mw (mw), m_n (n)
{ }
ClearRecentAction (lay::MainWindow *mw, const std::string &cfg)
: lay::Action (), mp_mw (mw), m_cfg (cfg)
{
set_title (tl::to_string (tr ("Clear List")));
}
void triggered ()
{
mp_mw->open_recent_session (m_n);
mp_mw->configure (m_cfg, std::string ());
}
private:
lay::MainWindow *mp_mw;
size_t m_n;
};
class OpenRecentLayerPropertiesAction
: public lay::Action
{
public:
OpenRecentLayerPropertiesAction (lay::MainWindow *mw, size_t n)
: lay::Action (), mp_mw (mw), m_n (n)
{ }
void triggered ()
{
mp_mw->open_recent_layer_properties (m_n);
}
private:
lay::MainWindow *mp_mw;
size_t m_n;
};
class OpenRecentBookmarksAction
: public lay::Action
{
public:
OpenRecentBookmarksAction (lay::MainWindow *mw, size_t n)
: lay::Action (), mp_mw (mw), m_n (n)
{ }
void triggered ()
{
mp_mw->open_recent_bookmarks (m_n);
}
private:
lay::MainWindow *mp_mw;
size_t m_n;
std::string m_cfg;
};
}
@ -3074,11 +3047,14 @@ MainWindow::do_update_mru_menus ()
for (std::vector<std::pair<std::string, std::string> >::iterator mru = m_mru.end (); mru != m_mru.begin (); ) {
--mru;
size_t i = std::distance (m_mru.begin (), mru);
Action *action = new OpenRecentAction (this, i);
Action *action = new OpenRecentAction (this, i, &lay::MainWindow::open_recent, &lay::MainWindow::is_available_recent);
action->set_title (mru->first);
menu ()->insert_item (mru_menu + ".end", tl::sprintf ("open_recent_%d", i + 1), action);
}
menu ()->insert_separator (mru_menu + ".end", "clear_sep");
menu ()->insert_item (mru_menu + ".end", "clear_recent", new ClearRecentAction (this, cfg_mru));
} else {
open_recent_action->set_enabled (false);
}
@ -3100,11 +3076,14 @@ MainWindow::do_update_mru_menus ()
for (std::vector<std::string>::iterator mru = m_mru_sessions.end (); mru != m_mru_sessions.begin (); ) {
--mru;
size_t i = std::distance (m_mru_sessions.begin (), mru);
Action *action = new OpenRecentSessionAction (this, i);
Action *action = new OpenRecentAction (this, i, &lay::MainWindow::open_recent_session, &lay::MainWindow::is_available_recent_session);
action->set_title (*mru);
menu ()->insert_item (mru_menu + ".end", tl::sprintf ("open_recent_%d", i + 1), action);
}
menu ()->insert_separator (mru_menu + ".end", "clear_sep");
menu ()->insert_item (mru_menu + ".end", "clear_recent", new ClearRecentAction (this, cfg_mru_sessions));
} else {
open_recent_action->set_enabled (false);
}
@ -3126,11 +3105,14 @@ MainWindow::do_update_mru_menus ()
for (std::vector<std::string>::iterator mru = m_mru_layer_properties.end (); mru != m_mru_layer_properties.begin (); ) {
--mru;
size_t i = std::distance (m_mru_layer_properties.begin (), mru);
Action *action = new OpenRecentLayerPropertiesAction (this, i);
Action *action = new OpenRecentAction (this, i, &lay::MainWindow::open_recent_layer_properties, &lay::MainWindow::is_available_recent_layer_properties);
action->set_title (*mru);
menu ()->insert_item (mru_menu + ".end", tl::sprintf ("open_recent_%d", i + 1), action);
}
menu ()->insert_separator (mru_menu + ".end", "clear_sep");
menu ()->insert_item (mru_menu + ".end", "clear_recent", new ClearRecentAction (this, cfg_mru_layer_properties));
} else {
open_recent_action->set_enabled (false);
}
@ -3152,11 +3134,14 @@ MainWindow::do_update_mru_menus ()
for (std::vector<std::string>::iterator mru = m_mru_bookmarks.end (); mru != m_mru_bookmarks.begin (); ) {
--mru;
size_t i = std::distance (m_mru_bookmarks.begin (), mru);
Action *action = new OpenRecentBookmarksAction (this, i);
Action *action = new OpenRecentAction (this, i, &lay::MainWindow::open_recent_bookmarks, &lay::MainWindow::is_available_recent_bookmarks);
action->set_title (*mru);
menu ()->insert_item (mru_menu + ".end", tl::sprintf ("open_recent_%d", i + 1), action);
}
menu ()->insert_separator (mru_menu + ".end", "clear_sep");
menu ()->insert_item (mru_menu + ".end", "clear_recent", new ClearRecentAction (this, cfg_mru_bookmarks));
} else {
open_recent_action->set_enabled (false);
}
@ -3218,6 +3203,25 @@ MainWindow::open_recent (size_t n)
END_PROTECTED
}
static bool
is_file_available (const std::string &fn)
{
tl::URI uri (fn);
if (uri.scheme ().empty ()) {
return tl::is_readable (fn);
} else if (uri.scheme () == "file") {
return tl::is_readable (uri.path ());
} else {
return true;
}
}
bool
MainWindow::is_available_recent (size_t n)
{
return (n < m_mru.size () && is_file_available (m_mru [n].first));
}
void
MainWindow::open_recent_session (size_t n)
{
@ -3232,6 +3236,12 @@ MainWindow::open_recent_session (size_t n)
END_PROTECTED
}
bool
MainWindow::is_available_recent_session (size_t n)
{
return (n < m_mru_sessions.size () && is_file_available (m_mru_sessions [n]));
}
void
MainWindow::open_recent_layer_properties (size_t n)
{
@ -3246,6 +3256,12 @@ MainWindow::open_recent_layer_properties (size_t n)
END_PROTECTED
}
bool
MainWindow::is_available_recent_layer_properties (size_t n)
{
return (n < m_mru_layer_properties.size () && is_file_available (m_mru_layer_properties [n]));
}
void
MainWindow::open_recent_bookmarks (size_t n)
{
@ -3264,6 +3280,12 @@ MainWindow::open_recent_bookmarks (size_t n)
END_PROTECTED
}
bool
MainWindow::is_available_recent_bookmarks (size_t n)
{
return (n < m_mru_bookmarks.size () && is_file_available (m_mru_bookmarks [n]));
}
void
MainWindow::open (int mode)
{

View File

@ -637,6 +637,10 @@ public slots:
void open_recent_session (size_t n);
void open_recent_layer_properties (size_t n);
void open_recent_bookmarks (size_t n);
bool is_available_recent(size_t n);
bool is_available_recent_session (size_t n);
bool is_available_recent_layer_properties (size_t n);
bool is_available_recent_bookmarks (size_t n);
void view_selected (int index);
void view_title_changed (lay::LayoutView *view);

View File

@ -39,7 +39,27 @@ public:
on_triggered_event ();
}
virtual bool wants_visible () const
{
if (wants_visible_cb.can_issue ()) {
return wants_visible_cb.issue<lay::Action, bool> (&lay::Action::wants_visible);
} else {
return true;
}
}
virtual bool wants_enabled () const
{
if (wants_enabled_cb.can_issue ()) {
return wants_enabled_cb.issue<lay::Action, bool> (&lay::Action::wants_enabled);
} else {
return true;
}
}
gsi::Callback triggered_cb;
gsi::Callback wants_visible_cb;
gsi::Callback wants_enabled_cb;
tl::Event on_triggered_event;
};
@ -291,10 +311,16 @@ Class<lay::Action> decl_ActionBase ("lay", "ActionBase",
) +
method ("is_effective_visible?", &lay::Action::is_effective_visible,
"@brief Gets a value indicating whether the item is really visible\n"
"This is the combined visibility from \\is_visible? and \\is_hidden?."
"This is the combined visibility from \\is_visible? and \\is_hidden? and dynamic visibility (\\wants_visible)."
"\n"
"This attribute has been introduced in version 0.25.\n"
) +
method ("is_effective_enabled?", &lay::Action::is_effective_enabled,
"@brief Gets a value indicating whether the item is really enabled\n"
"This is the combined value from \\is_enabled? and dynamic value (\\wants_enabled)."
"\n"
"This attribute has been introduced in version 0.28.\n"
) +
method ("separator=", &lay::Action::set_separator, gsi::arg ("separator"),
"@brief Makes an item a separator or not\n"
"\n"
@ -368,6 +394,24 @@ Class<ActionStub> decl_Action (decl_ActionBase, "lay", "Action",
gsi::callback ("triggered", &ActionStub::triggered, &ActionStub::triggered_cb,
"@brief This method is called if the menu item is selected"
) +
gsi::callback ("wants_visible", &ActionStub::wants_visible, &ActionStub::wants_visible_cb,
"@brief Returns a value whether the action wants to become visible\n"
"This is a dynamic query for visibility which the system uses to dynamically show or hide "
"menu items, for example in the MRU lists. This visibility information is evaluated in addition "
"to \\is_visible? and \\is_hidden? and contributes to the effective visibility status from "
"\\is_effective_visible?.\n"
"\n"
"This feature has been introduced in version 0.28.\n"
) +
gsi::callback ("wants_enabled", &ActionStub::wants_enabled, &ActionStub::wants_enabled_cb,
"@brief Returns a value whether the action wants to become enabled\n"
"This is a dynamic query for enabled state which the system uses to dynamically show or hide "
"menu items. This information is evaluated in addition "
"to \\is_enabled? and contributes to the effective enabled status from "
"\\is_effective_enabled?.\n"
"\n"
"This feature has been introduced in version 0.28.\n"
) +
gsi::event ("on_triggered", &ActionStub::on_triggered_event,
"@brief This event is called if the menu item is selected\n"
"\n"

View File

@ -387,7 +387,7 @@ Action::Action () :
#if defined(HAVE_QT)
// catch the destroyed signal to tell if the QAction object is deleted.
if (mp_action) {
connect (mp_action, SIGNAL (destroyed (QObject *)), this, SLOT (destroyed (QObject *)));
connect (mp_action, SIGNAL (destroyed (QObject *)), this, SLOT (was_destroyed (QObject *)));
connect (mp_action, SIGNAL (triggered ()), this, SLOT (qaction_triggered ()));
}
#endif
@ -413,7 +413,7 @@ Action::Action (QAction *action, bool owned)
sp_actionHandles->insert (this);
// catch the destroyed signal to tell if the QAction object is deleted.
connect (mp_action, SIGNAL (destroyed (QObject *)), this, SLOT (destroyed (QObject *)));
connect (mp_action, SIGNAL (destroyed (QObject *)), this, SLOT (was_destroyed (QObject *)));
connect (mp_action, SIGNAL (triggered ()), this, SLOT (qaction_triggered ()));
}
@ -436,7 +436,8 @@ Action::Action (QMenu *menu, bool owned)
sp_actionHandles->insert (this);
// catch the destroyed signal to tell if the QAction object is deleted.
connect (mp_menu, SIGNAL (destroyed (QObject *)), this, SLOT (destroyed (QObject *)));
connect (mp_menu, SIGNAL (destroyed (QObject *)), this, SLOT (was_destroyed (QObject *)));
connect (mp_menu, SIGNAL (aboutToShow ()), this, SLOT (menu_about_to_show ()));
connect (mp_action, SIGNAL (triggered ()), this, SLOT (qaction_triggered ()));
}
#endif
@ -466,7 +467,7 @@ Action::Action (const std::string &title) :
#if defined(HAVE_QT)
// catch the destroyed signal to tell if the QAction object is deleted.
if (mp_action) {
connect (mp_action, SIGNAL (destroyed (QObject *)), this, SLOT (destroyed (QObject *)));
connect (mp_action, SIGNAL (destroyed (QObject *)), this, SLOT (was_destroyed (QObject *)));
connect (mp_action, SIGNAL (triggered ()), this, SLOT (qaction_triggered ()));
}
#endif
@ -536,6 +537,30 @@ Action::configure_from_title (const std::string &s)
}
}
#if defined(HAVE_QT)
void
Action::menu_about_to_show ()
{
BEGIN_PROTECTED
if (! mp_dispatcher || ! mp_dispatcher->menu ()) {
return;
}
AbstractMenuItem *item = mp_dispatcher->menu ()->find_item_for_action (this);
if (! item ) {
return;
}
for (auto i = item->children.begin (); i != item->children.end (); ++i) {
if (i->action ()) {
i->action ()->sync_qaction ();
}
}
END_PROTECTED
}
#endif
#if defined(HAVE_QT)
void
Action::qaction_triggered ()
@ -578,7 +603,7 @@ Action::menu () const
}
void
Action::destroyed (QObject *obj)
Action::was_destroyed (QObject *obj)
{
if (obj == mp_action) {
mp_action = 0;
@ -591,17 +616,24 @@ Action::destroyed (QObject *obj)
}
#endif
void
Action::sync_qaction ()
{
#if defined(HAVE_QT)
if (mp_action) {
mp_action->setVisible (is_effective_visible ());
mp_action->setShortcut (get_key_sequence ());
mp_action->setEnabled (is_effective_enabled ());
}
#endif
}
void
Action::set_visible (bool v)
{
if (m_visible != v) {
m_visible = v;
#if defined(HAVE_QT)
if (mp_action) {
mp_action->setVisible (is_effective_visible ());
mp_action->setShortcut (get_key_sequence ());
}
#endif
sync_qaction ();
}
}
@ -610,12 +642,7 @@ Action::set_hidden (bool h)
{
if (m_hidden != h) {
m_hidden = h;
#if defined(HAVE_QT)
if (mp_action) {
mp_action->setVisible (is_effective_visible ());
mp_action->setShortcut (get_key_sequence ());
}
#endif
sync_qaction ();
}
}
@ -634,7 +661,7 @@ Action::is_hidden () const
bool
Action::is_effective_visible () const
{
return m_visible && !m_hidden;
return m_visible && !m_hidden && wants_visible ();
}
void
@ -792,11 +819,7 @@ Action::is_checked () const
bool
Action::is_enabled () const
{
#if defined(HAVE_QT)
return qaction () ? qaction ()->isEnabled () : m_enabled;
#else
return m_enabled;
#endif
}
bool
@ -808,12 +831,16 @@ Action::is_separator () const
void
Action::set_enabled (bool b)
{
#if defined(HAVE_QT)
if (qaction ()) {
qaction ()->setEnabled (b);
if (m_enabled != b) {
m_enabled = b;
sync_qaction ();
}
#endif
m_enabled = b;
}
bool
Action::is_effective_enabled () const
{
return m_enabled && wants_enabled ();
}
void
@ -1508,6 +1535,33 @@ AbstractMenu::delete_items (Action *action)
}
}
const AbstractMenuItem *
AbstractMenu::find_item_for_action (const Action *action, const AbstractMenuItem *from) const
{
return (const_cast<AbstractMenu *> (this))->find_item_for_action (action, const_cast<AbstractMenuItem *> (from));
}
AbstractMenuItem *
AbstractMenu::find_item_for_action (const Action *action, AbstractMenuItem *from)
{
if (! from) {
from = const_cast<AbstractMenuItem *> (&root ());
}
if (from->action () == action) {
return from;
}
for (auto i = from->children.begin (); i != from->children.end (); ++i) {
AbstractMenuItem *item = find_item_for_action (action, i.operator-> ());
if (item) {
return item;
}
}
return 0;
}
const AbstractMenuItem *
AbstractMenu::find_item_exact (const std::string &path) const
{

View File

@ -318,6 +318,34 @@ public:
return false;
}
/**
* @brief Gets a value indicating the action is visible (dynamic calls)
* In addition to static visibility (visible/hidden), an Action object can request to
* become invisible dynamically based on conditions. This will work for menu-items
* for which the system will query the status before the menu is shown.
*/
virtual bool wants_visible () const
{
return true;
}
/**
* @brief Gets a value indicating the action is enabled (dynamic calls)
* In addition to static visibility (visible/hidden), an Action object can request to
* become invisible dynamically based on conditions. This will work for menu-items
* for which the system will query the status before the menu is shown.
*/
virtual bool wants_enabled () const
{
return true;
}
/**
* @brief Gets the effective enabled state
* This is the combined value from is_enabled and wants_enabled.
*/
bool is_effective_enabled () const;
#if defined(HAVE_QT)
/**
* @brief Get the underlying QAction object
@ -342,8 +370,9 @@ public:
#if defined(HAVE_QT)
protected slots:
void destroyed (QObject *obj);
void was_destroyed (QObject *obj);
void qaction_triggered ();
void menu_about_to_show ();
#endif
private:
@ -381,6 +410,7 @@ private:
#endif
void configure_from_title (const std::string &s);
void sync_qaction ();
// no copying
Action (const Action &);
@ -801,7 +831,7 @@ public:
* @param group The group name
* @param A vector of all members (as actions) of the group
*/
std::vector<Action *> group_actions(const std::string &name);
std::vector<Action *> group_actions (const std::string &name);
/**
* @brief Get the configure actions for a given configuration name
@ -844,6 +874,8 @@ private:
std::vector<std::pair<AbstractMenuItem *, std::list<AbstractMenuItem>::iterator> > find_item (tl::Extractor &extr);
const AbstractMenuItem *find_item_exact (const std::string &path) const;
AbstractMenuItem *find_item_exact (const std::string &path);
const AbstractMenuItem *find_item_for_action (const Action *action, const AbstractMenuItem *from = 0) const;
AbstractMenuItem *find_item_for_action (const Action *action, AbstractMenuItem *from = 0);
#if defined(HAVE_QT)
void build (QMenu *menu, std::list<AbstractMenuItem> &items);
void build (QToolBar *tbar, std::list<AbstractMenuItem> &items);

View File

@ -227,20 +227,20 @@ RESULT
$a1.visible = false
assert_equal( menu.action( "my_menu.new_item" ).is_visible?, false )
assert_equal( menu.action( "my_menu.new_item" ).is_checked?, false )
assert_equal( menu.action( "my_menu.new_item" ).is_enabled?, false )
assert_equal( menu.action( "my_menu.new_item" ).is_enabled?, true )
$a1.checked = true
assert_equal( menu.action( "file_menu.#3" ).is_visible?, false )
assert_equal( menu.action( "file_menu.#3" ).is_checked?, false )
assert_equal( menu.action( "file_menu.#3" ).is_checkable?, false )
assert_equal( menu.action( "file_menu.#3" ).is_enabled?, false )
assert_equal( menu.action( "file_menu.#3" ).is_enabled?, true )
$a1.checked = false
$a1.checkable = true;
assert_equal( menu.action( "my_menu.new_item" ).is_visible?, false )
assert_equal( menu.action( "my_menu.new_item" ).is_checked?, false )
assert_equal( menu.action( "my_menu.new_item" ).is_checkable?, true )
assert_equal( menu.action( "my_menu.new_item" ).is_enabled?, false )
assert_equal( menu.action( "my_menu.new_item" ).is_enabled?, true )
$a1.checked = true
assert_equal( menu.action( "file_menu.#0" ).is_checked?, true )
@ -440,6 +440,50 @@ RESULT
end
class MyAction < RBA::Action
attr_accessor :dyn_visible, :dyn_enabled
def initialize
self.dyn_visible = true
self.dyn_enabled = true
end
def wants_visible
self.dyn_visible
end
def wants_enabled
self.dyn_enabled
end
end
def test_7
a = MyAction::new
assert_equal(a.is_effective_visible?, true)
a.hidden = true
assert_equal(a.is_effective_visible?, false)
a.hidden = false
assert_equal(a.is_effective_visible?, true)
a.visible = false
assert_equal(a.is_effective_visible?, false)
a.visible = true
assert_equal(a.is_effective_visible?, true)
a.dyn_visible = false
assert_equal(a.is_effective_visible?, false)
a.dyn_visible = true
assert_equal(a.is_effective_visible?, true)
assert_equal(a.is_effective_enabled?, true)
a.enabled = false
assert_equal(a.is_effective_enabled?, false)
a.enabled = true
assert_equal(a.is_effective_enabled?, true)
a.dyn_enabled = false
assert_equal(a.is_effective_enabled?, false)
a.dyn_enabled = true
assert_equal(a.is_effective_enabled?, true)
end
end
load("test_epilogue.rb")