New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
MenuSorter: initial implementation #2601
Conversation
* Menus are now sanely configurable * Custom separator placement for clearer menus
I assume the configurable here means developers can change its order without needing to fight against the menu item generating logic, rather than users can configure it. Right? |
Both, actually. |
In this case it is indispensable a tutorial or guide with examples on the wiki so users can know that menus are configurable and how to configure them. |
Right now the menu definition is caught up in the code itself, but I was actually thinking of sticking it in separate files (e.g., in The way it works in the proposed PR is to create a file in This is part of the default reader top menu definition: return {
["KOMenu:menu_buttons"] = {
"navi",
"typeset",
"setting",
"tools",
"search",
"filemanager",
"main",
},
-- […]
["main"] = {
"history",
"book_status",
"----------------------------",
"ota_update",
"version",
"help",
"----------------------------",
"exit",
},
} You could cut it down significantly to only your favorite functions by sticking something like this in return {
["KOMenu:menu_buttons"] = {
"navi",
"main",
},
["main"] = {
"history",
"----------------------------",
"ota_update",
"setting",
"exit",
},
-- I'll implement KOMENU:disabled soon
-- if you don't explicitly disable my intent is to add
-- unused items to the first menu with something like a "New: " prefix
-- or possibly additionally to give menu items a "menu_hint" property
-- so an orphaned orphaned_item.menu_hint == existing_menu would
-- be added to the bottom of that menu, or to the first menu if menu_hint
-- doesn't match any existing menus
["KOMenu:disabled"] = {
"typeset",
"tools",
"search",
"filemanager",
"book_status",
}
} Note that in principle you shouldn't copy the entire menu definition, but only those specific menus you wish to override. Also there will be a menu item to get out of the customized menu that cannot be disabled. Something like in settings by default, first menu if settings is disabled. The most revolutionary aspect of this will take a little more time to implement and might only be part of this PR in a very limited capacity: the ability to stick many submenu items wherever you want. For example, if you want Developer options → Clear readers' cache (sounds like that should be |
frontend/ui/menusorter.lua
Outdated
self:findById(v, needle_id) | ||
end | ||
end | ||
end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
With an eye on broader usability this needs to return self.sub_menu_position. I originally passed a result variable to the function itself to return but I couldn't figure out why that didn't work.
The test that fails is based on
koreader/spec/unit/readerfooter_spec.lua
Lines 6 to 16 in 8a4d99d
for _, item in ipairs(menu_tab_items.setting) do | |
if item.text == "Status bar" then | |
for _, subitem in ipairs(item.sub_item_table) do | |
if subitem.text == menu_title then | |
subitem.callback() | |
return | |
end | |
end | |
error('Menu item not found: "' .. menu_title .. '"!') | |
end | |
end |
Then you should be able to do something like this:
for _, item in ipairs(MenuSorter:findById(menu_tab_items, "setting") do
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Haven't read your whole code, and I may be off topic, but just sharing something i discovered after hours of pulling hairs : ipairs stops iteration at first nil:
> a={"abd", "def", nil, "ghi"}
> for n, i in ipairs(a) do print(n, i) end
1 abd
2 def
> for n, i in pairs(a) do print(n, i) end
1 abd
2 def
4 ghi
(It's the same for function arguments func("abd", "def", nil, "ghi")
will see it's 4th argument be nil and not "ghi")
But one can use false
instead, which does not stop iteration.
Dunno if that's in play there, but sharing that just in case it is.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Totally off topic as it doesn't apply, but a helpful caveat nonetheless.[1] I was talking about fixing the failing unit test. :-P
[1] I did not know, for example, that removing an index would shift the other indexes down.
> a={"abd", "def", "asdf", "ghi"}
> table.remove(a,3)
> for n, i in ipairs(a) do print(n, i) end
1 abd
2 def
3 ghi
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I assume you want to pass in an argument to store the result because of this recursive search structure? If so, what you can do here is return v
directly in self:findById, then in the go deeper branch, check the return value of self:findById, if it's not nil, then we found the item, if not, keep going in the loop.
If fact, every recursive code be reconstructed with non-recursive code. Since the order definition is a strict tree structure, you can apply any non-recursive tree walk algorithm here to avoid the overhead of nested stacks caused by recursive function calls.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If so, what you can do here is return v directly in self:findById, then in the go deeper branch, check the return value of self:findById, if it's not nil, then we found the item, if not, keep going in the loop.
That's what I did, but for some reason the result variable didn't want to cooperate.
you can apply any non-recursive tree walk algorithm here
That actually sounds probably much easier than this recursive nonsense. :-P
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@@ -21,12 +21,12 @@ end | |||
|
|||
function FileManagerHistory:addToMainMenu(tab_item_table) | |||
-- insert table to main tab of filemanager menu | |||
table.insert(tab_item_table.main, { | |||
self.ui.menu.menu_items["history"] = { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nitpick, to keep style consistent, should use self.ui.menu.menu_items.history
instead of self.ui.menu.menu_items["history"]
. It's also a little bit faster than access with a string key.
Same for the rest of the patch, so I am not going to point out each of them separately.
icon = "resources/icons/appbar.magnify.browse.png", | ||
} | ||
self.menu_items["main"] = { | ||
icon = "resources/icons/menu-icon.png", | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why not just do:
self.menu_items = {
"KOMenu:menu_buttons" = {},
setting = {},
tools = {},
search = {},
main = {},
}
frontend/ui/menusorter.lua
Outdated
self:findById(v, needle_id) | ||
end | ||
end | ||
end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I assume you want to pass in an argument to store the result because of this recursive search structure? If so, what you can do here is return v
directly in self:findById, then in the go deeper branch, check the return value of self:findById, if it's not nil, then we found the item, if not, keep going in the loop.
If fact, every recursive code be reconstructed with non-recursive code. Since the order definition is a strict tree structure, you can apply any non-recursive tree walk algorithm here to avoid the overhead of nested stacks caused by recursive function calls.
spec/unit/readerfooter_spec.lua
Outdated
@@ -1,9 +1,9 @@ | |||
describe("Readerfooter module", function() | |||
local DocumentRegistry, ReaderUI, DocSettings, UIManager, DEBUG | |||
local DocumentRegistry, ReaderUI, MenuSorter DocSettings, UIManager, DEBUG |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
missed one comma?
e7fd572
to
e3297ee
Compare
spec/unit/readerfooter_spec.lua
Outdated
setup(function() | ||
require("commonrequire") | ||
DocumentRegistry = require("document/documentregistry") | ||
ReaderUI = require("apps/reader/readerui") | ||
DocSettings = require("docsettings") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh ffs, I'm responsible for deleting this. /smacks forehead
5bde2b8
to
c514263
Compare
Okay, I've worked out all the quirks, not counting the fact that storagestat is currently orphaned and that the readerfooter test is failing but I don't remember how to setup Busted properly for local testing. As far as I've been able to ascertain everything's now working exactly as intended. |
frontend/ui/widget/touchmenu.lua
Outdated
if c ~= self.perpage then | ||
table.insert(self.item_group, self.split_line) | ||
local item = self.item_table[i] | ||
-- due to the menu ordering system index can be missing |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is no longer true, should we keep this just in case?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's remove it :)
frontend/ui/menusorter.lua
Outdated
-- remove top level reference before orphan handling | ||
item_table["KOMenu:menu_buttons"] = nil | ||
--attach orphans based on menu_hint | ||
DEBUG("ORPHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAANSS", util.tableSize(item_table)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should use the new logger functionality. And of course I'll tone down the debug statements. :-P
7bf8a16
to
ca43b19
Compare
* fixed wrongful retention of submenus variable and added return to MenuSorter:findById * fixed readerfooter_spec.lua error * fixed review comments
MenuSorter: forgot to add plugin style change MenuSorter: worked out the final quirks * Menu always compressed into tables without missing indexes for ipairs compatibility * Orphans attached * Separators no longer count as items
ca43b19
to
048f340
Compare
@houqp It's unclear to me what about the Travis build is failing? Running the tests locally gives me:
and it's the same on Travis:
|
3b1e704
to
dd7ee17
Compare
I think that ought to do it. The most important behavior is defined and protected by unit tests. |
e59e5fe
to
a1814a6
Compare
Okay, I think this is ready to be merged. |
frontend/ui/menusorter.lua
Outdated
local MenuSorter = { | ||
orphaned_prefix = _("NEW: "), | ||
separator = { | ||
id = "----------------------------", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's define this special string as a module constant at the top of this file and reference it everywhere, including in MenuSorter:sort
.
frontend/ui/menusorter.lua
Outdated
i = i + 1 | ||
end | ||
else | ||
DEBUG("menu id not found:", order_id) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
DEBUG is deprecated, use logger instead. Missing id is probably logger.warn
worthy ;)
frontend/ui/menusorter.lua
Outdated
|
||
-- now do the submenus | ||
for i,sub_menu in ipairs(sub_menus) do | ||
local sub_menu_position = self:findById(menu_table["KOMenu:menu_buttons"], sub_menu) or nil |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
findById
already returns nil
if not found.
frontend/ui/menusorter.lua
Outdated
-- now do the submenus | ||
for i,sub_menu in ipairs(sub_menus) do | ||
local sub_menu_position = self:findById(menu_table["KOMenu:menu_buttons"], sub_menu) or nil | ||
if sub_menu_position and sub_menu_position.id then |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
under what circumstance will sub_menu_position.id
be nil
when sub_menu_position
is not nil
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hm, I have no idea. Maybe during development circumstances when other parts of the code still needed polishing? I'll take it out.
frontend/ui/menusorter.lua
Outdated
end | ||
|
||
-- handle disabled | ||
if order.KOMenu__disabled then |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This key's naming convention is inconsistent with other magic keys like KOMenu:menu_buttons
. We should either name it as order['KOMenu:disabled']
or rename KOMenu:menu_buttons
to KOMenu__menu_buttons
.
The rest looks good to me. Feel free to squash merge this yourself after you go through my last round of reviews ;) |
* simplify user config loop * simplify unit test for Travis memory use * remove unused util variable
0f54e3f
to
edb0d3c
Compare
This PR should have been squashed locally with a force push or merged with the squash merge button ;) |
I did squash it actually, from 30+ commits into a dozen that I believe meaningfully reflect the development process. See my position here.
We could codify contribution rules to squash more, but in that case I'd like to voice a contrary opinion. :-) |
@Frenzie what ever became of this project. I was researching this because I had an idea not too dissimilar. A new main menu option (a quick view of sorts) whereby it is populated by user generated selections that point to, rather than move, menu options from anywhere. i.e. WiFi toggle, night mode, font selection, profile activation, etc. The main menus wouldn’t have to be altered. Just the the quick view menu. |
It works as described in #2601 (comment) I think a "quick view" would fit in more with Dispatcher in some way than with MenuSorter. |
Thanks for getting back. That’s a great point about dispatcher. Would a user defined menu taking advantage of dispatcher also be able to show user defined status states? |
We have code and an option for that on Kobo (dropping Wi-Fi on network inactivity, I mean). Should be easy to adapt to other platforms, because Linux. Except possibly the chatty ones, like Kindle. |
thanks for a good suggestion. I use a few kindles with KOREADER. I guess that means it wouldn’t be as useful to me. But maybe it might be a good idea to add that function for others to enable. |
References #2564 and #2555.
I wouldn't merge this just yet unless you're interested in potential crashes or other odd behavior on account of minimal testing, plus I may have overlooked a menu item or some such. Here are some screenshots to illustrate the separators but other than that in principle nothing should've really changed (yet).