--[[-- This module is responsible for dispatching events. To add a new action an entry must be added to `settingsList` & `dispatcher_menu_order` This can also be done at runtime via @{registerAction}(). `settingsList` contains the list of dispatchable settings. Each setting contains: * category: one of: * none: a direct event call * arg: a event that expects a gesture object or an argument * absolutenumber: event that sets a number * incrementalnumber: event that increments a number & accepts a gesture object * string: event with a list of arguments to chose from * configurable: like string but instead of an event it updates the configurable (used by kopt) * event: what to call. * title: for use in ui. * section: under which menu to display (currently: general, device, screen, filemanager, reader, rolling, paging) and optionally * min/max: for number * step: for number * default * args: allowed values for string. * toggle: display name for args * separator: put a separator after in the menu list * configurable: can be parsed from cre/kopt and used to set `document.configurable`. Should not be set manually --]]-- local CreOptions = require("ui/data/creoptions") local KoptOptions = require("ui/data/koptoptions") local Device = require("device") local Event = require("ui/event") local Notification = require("ui/widget/notification") local ReaderHighlight = require("apps/reader/modules/readerhighlight") local ReaderZooming = require("apps/reader/modules/readerzooming") local Screen = Device.screen local UIManager = require("ui/uimanager") local util = require("util") local _ = require("gettext") local NC_ = _.npgettext local T = require("ffi/util").template local Dispatcher = { initialized = false, } -- See above for description. local settingsList = { -- General reading_progress = {category="none", event="ShowReaderProgress", title=_("Reading progress"), general=true}, open_previous_document = {category="none", event="OpenLastDoc", title=_("Open previous document"), general=true}, history = {category="none", event="ShowHist", title=_("History"), general=true}, history_search = {category="none", event="SearchHistory", title=_("History search"), general=true}, favorites = {category="none", event="ShowColl", title=_("Favorites"), general=true}, collections = {category="none", event="ShowCollList", title=_("Collections"), general=true}, filemanager = {category="none", event="Home", title=_("File browser"), general=true, separator=true}, ---- dictionary_lookup = {category="none", event="ShowDictionaryLookup", title=_("Dictionary lookup"), general=true}, wikipedia_lookup = {category="none", event="ShowWikipediaLookup", title=_("Wikipedia lookup"), general=true, separator=true}, ---- show_menu = {category="none", event="ShowMenu", title=_("Show menu"), general=true}, menu_search = {category="none", event="MenuSearch", title=_("Menu search"), general=true}, screenshot = {category="none", event="Screenshot", title=_("Screenshot"), general=true, separator=true}, ---- -- Device exit_screensaver = {category="none", event="ExitScreensaver", title=_("Exit sleep screen"), device=true}, start_usbms = {category="none", event="RequestUSBMS", title=_("Start USB storage"), device=true, condition=Device:canToggleMassStorage()}, suspend = {category="none", event="RequestSuspend", title=_("Suspend"), device=true, condition=Device:canSuspend()}, restart = {category="none", event="Restart", title=_("Restart KOReader"), device=true, condition=Device:canRestart()}, reboot = {category="none", event="RequestReboot", title=_("Reboot the device"), device=true, condition=Device:canReboot()}, poweroff = {category="none", event="RequestPowerOff", title=_("Power off"), device=true, condition=Device:canPowerOff()}, exit = {category="none", event="Exit", title=_("Exit KOReader"), device=true, separator=true}, ---- toggle_hold_corners = {category="none", event="IgnoreHoldCorners", title=_("Toggle hold corners"), device=true}, touch_input_on = {category="none", event="IgnoreTouchInput", arg=false, title=_("Enable touch input"), device=true}, touch_input_off = {category="none", event="IgnoreTouchInput", arg=true, title=_("Disable touch input"), device=true}, toggle_touch_input = {category="none", event="IgnoreTouchInput", title=_("Toggle touch input"), device=true, separator=true}, ---- swap_page_turn_buttons = {category="none", event="SwapPageTurnButtons", title=_("Invert page turn buttons"), device=true, condition=Device:hasKeys(), separator=true}, ---- toggle_key_repeat = {category="none", event="ToggleKeyRepeat", title=_("Toggle key repeat"), device=true, condition=Device:hasKeys() and Device:canKeyRepeat(), separator=true}, toggle_gsensor = {category="none", event="ToggleGSensor", title=_("Toggle accelerometer"), device=true, condition=Device:hasGSensor()}, lock_gsensor = {category="none", event="LockGSensor", title=_("Lock auto rotation to current orientation"), device=true, condition=Device:hasGSensor()}, toggle_rotation = {category="none", event="SwapRotation", title=_("Toggle orientation"), device=true}, invert_rotation = {category="none", event="InvertRotation", title=_("Invert rotation"), device=true}, iterate_rotation = {category="none", event="IterateRotation", title=_("Rotate by 90° CW"), device=true}, iterate_rotation_ccw = {category="none", event="IterateRotation", arg=true, title=_("Rotate by 90° CCW"), device=true, separator=true}, ---- wifi_on = {category="none", event="InfoWifiOn", title=_("Turn on Wi-Fi"), device=true, condition=Device:hasWifiToggle()}, wifi_off = {category="none", event="InfoWifiOff", title=_("Turn off Wi-Fi"), device=true, condition=Device:hasWifiToggle()}, toggle_wifi = {category="none", event="ToggleWifi", title=_("Toggle Wi-Fi"), device=true, condition=Device:hasWifiToggle()}, toggle_fullscreen = {category="none", event="ToggleFullscreen", title=_("Toggle Fullscreen"), device=true, condition=not Device:isAlwaysFullscreen()}, show_network_info = {category="none", event="ShowNetworkInfo", title=_("Show network info"), device=true, separator=true}, ---- -- Screen and lights show_frontlight_dialog = {category="none", event="ShowFlDialog", title=_("Show frontlight dialog"), screen=true, condition=Device:hasFrontlight()}, toggle_frontlight = {category="none", event="ToggleFrontlight", title=_("Toggle frontlight"), screen=true, condition=Device:hasFrontlight()}, set_frontlight = {category="absolutenumber", event="SetFlIntensity", min=0, max=Device:getPowerDevice().fl_max, title=_("Set frontlight brightness"), screen=true, condition=Device:hasFrontlight()}, increase_frontlight = {category="incrementalnumber", event="IncreaseFlIntensity", min=0, max=Device:getPowerDevice().fl_max, title=_("Increase frontlight brightness"), screen=true, condition=Device:hasFrontlight()}, decrease_frontlight = {category="incrementalnumber", event="DecreaseFlIntensity", min=0, max=Device:getPowerDevice().fl_max, title=_("Decrease frontlight brightness"), screen=true, condition=Device:hasFrontlight()}, set_frontlight_warmth = {category="absolutenumber", event="SetFlWarmth", min=0, max=100, title=_("Set frontlight warmth"), screen=true, condition=Device:hasNaturalLight()}, increase_frontlight_warmth = {category="incrementalnumber", event="IncreaseFlWarmth", min=0, max=Device:getPowerDevice().fl_warmth_max, title=_("Increase frontlight warmth"), screen=true, condition=Device:hasNaturalLight()}, decrease_frontlight_warmth = {category="incrementalnumber", event="DecreaseFlWarmth", min=0, max=Device:getPowerDevice().fl_warmth_max, title=_("Decrease frontlight warmth"), screen=true, condition=Device:hasNaturalLight(), separator=true}, night_mode = {category="none", event="ToggleNightMode", title=_("Toggle night mode"), screen=true}, set_night_mode = {category="string", event="SetNightMode", title=_("Set night mode"), screen=true, args={true, false}, toggle={_("on"), _("off")}, separator=true}, ---- full_refresh = {category="none", event="FullRefresh", title=_("Full screen refresh"), screen=true}, set_refresh_rate = {category="absolutenumber", event="SetBothRefreshRates", min=-1, max=200, title=_("Full refresh rate (always)"), screen=true, condition=Device:hasEinkScreen()}, set_day_refresh_rate = {category="absolutenumber", event="SetDayRefreshRate", min=-1, max=200, title=_("Full refresh rate (not in night mode)"), screen=true, condition=Device:hasEinkScreen()}, set_night_refresh_rate = {category="absolutenumber", event="SetNightRefreshRate", min=-1, max=200, title=_("Full refresh rate (in night mode)"), screen=true, condition=Device:hasEinkScreen()}, set_flash_on_chapter_boundaries = {category="string", event="SetFlashOnChapterBoundaries", title=_("Always flash on chapter boundaries"), screen=true, condition=Device:hasEinkScreen(), args={true, false}, toggle={_("on"), _("off")}}, toggle_flash_on_chapter_boundaries = {category="none", event="ToggleFlashOnChapterBoundaries", title=_("Toggle flashing on chapter boundaries"), screen=true, condition=Device:hasEinkScreen()}, set_no_flash_on_second_chapter_page = {category="string", event="SetNoFlashOnSecondChapterPage", title=_("Never flash on chapter's 2nd page"), screen=true, condition=Device:hasEinkScreen(), args={true, false}, toggle={_("on"), _("off")}}, toggle_no_flash_on_second_chapter_page = {category="none", event="ToggleNoFlashOnSecondChapterPage", title=_("Toggle flashing on chapter's 2nd page"), screen=true, condition=Device:hasEinkScreen()}, set_flash_on_pages_with_images = {category="string", event="SetFlashOnPagesWithImages", title=_("Always flash on pages with images"), screen=true, condition=Device:hasEinkScreen(), args={true, false}, toggle={_("on"), _("off")}}, toggle_flash_on_pages_with_images = {category="none", event="ToggleFlashOnPagesWithImages", title=_("Toggle flashing on pages with images"), screen=true, condition=Device:hasEinkScreen(), separator=true}, ---- -- File browser folder_up = {category="none", event="FolderUp", title=_("Folder up"), filemanager=true}, show_plus_menu = {category="none", event="ShowPlusMenu", title=_("Show plus menu"), filemanager=true}, toggle_select_mode = {category="none", event="ToggleSelectMode", title=_("Toggle select mode"), filemanager=true}, refresh_content = {category="none", event="RefreshContent", title=_("Refresh content"), filemanager=true}, folder_shortcuts = {category="none", event="ShowFolderShortcutsDialog", title=_("Folder shortcuts"), filemanager=true}, file_search = {category="none", event="ShowFileSearch", title=_("File search"), filemanager=true, separator=true}, ---- -- go_to -- back -- Reader open_next_document_in_folder = {category="none", event="OpenNextDocumentInFolder", title=_("Open next document in folder"), reader=true, separator=true}, ---- show_config_menu = {category="none", event="ShowConfigMenu", title=_("Show bottom menu"), reader=true}, toggle_status_bar = {category="none", event="ToggleFooterMode", title=_("Toggle status bar"), reader=true}, toggle_chapter_progress_bar = {category="none", event="ToggleChapterProgressBar", title=_("Toggle chapter progress bar"), reader=true, separator=true}, ---- prev_chapter = {category="none", event="GotoPrevChapter", title=_("Previous chapter"), reader=true}, next_chapter = {category="none", event="GotoNextChapter", title=_("Next chapter"), reader=true}, first_page = {category="none", event="GoToBeginning", title=_("First page"), reader=true}, last_page = {category="none", event="GoToEnd", title=_("Last page"), reader=true}, random_page = {category="none", event="GoToRandomPage", title=_("Random page"), reader=true}, page_jmp = {category="absolutenumber", event="GotoViewRel", min=-100, max=100, title=_("Turn pages"), reader=true}, go_to = {category="none", event="ShowGotoDialog", title=_("Go to page"), filemanager=true, reader=true}, skim = {category="none", event="ShowSkimtoDialog", title=_("Skim document"), reader=true}, prev_bookmark = {category="none", event="GotoPreviousBookmarkFromPage", title=_("Previous bookmark"), reader=true}, next_bookmark = {category="none", event="GotoNextBookmarkFromPage", title=_("Next bookmark"), reader=true}, first_bookmark = {category="none", event="GotoFirstBookmark", title=_("First bookmark"), reader=true}, last_bookmark = {category="none", event="GotoLastBookmark", title=_("Last bookmark"), reader=true}, latest_bookmark = {category="none", event="GoToLatestBookmark", title=_("Latest bookmark"), reader=true, separator=true}, ---- back = {category="none", event="Back", title=_("Back"), filemanager=true, reader=true}, previous_location = {category="none", event="GoBackLink", arg=true, title=_("Back to previous location"), reader=true}, next_location = {category="none", event="GoForwardLink", arg=true, title=_("Forward to next location"), reader=true}, follow_nearest_link = {category="arg", event="GoToPageLink", arg={pos={x=0,y=0}}, title=_("Follow nearest link"), reader=true}, follow_nearest_internal_link = {category="arg", event="GoToInternalPageLink", arg={pos={x=0,y=0}}, title=_("Follow nearest internal link"), reader=true}, add_location_to_history = {category="none", event="AddCurrentLocationToStack", arg=true, title=_("Add current location to history"), reader=true}, clear_location_history = {category="none", event="ClearLocationStack", arg=true, title=_("Clear location history"), reader=true, separator=true}, ---- fulltext_search = {category="none", event="ShowFulltextSearchInput", title=_("Fulltext search"), reader=true}, fulltext_search_findall_results = {category="none", event="ShowFindAllResults", title=_("Last fulltext search results"), reader=true}, toc = {category="none", event="ShowToc", title=_("Table of contents"), reader=true}, book_map = {category="none", event="ShowBookMap", title=_("Book map"), reader=true, condition=Device:isTouchDevice()}, book_map_overview = {category="none", event="ShowBookMap", arg=true, title=_("Book map (overview)"), reader=true, condition=Device:isTouchDevice()}, page_browser = {category="none", event="ShowPageBrowser", title=_("Page browser"), reader=true, condition=Device:isTouchDevice()}, bookmarks = {category="none", event="ShowBookmark", title=_("Bookmarks"), reader=true}, bookmark_search = {category="none", event="SearchBookmark", title=_("Bookmark search"), reader=true}, toggle_bookmark = {category="none", event="ToggleBookmark", title=_("Toggle bookmark"), reader=true, separator=true}, ---- book_status = {category="none", event="ShowBookStatus", title=_("Book status"), reader=true}, book_info = {category="none", event="ShowBookInfo", title=_("Book information"), reader=true}, book_description = {category="none", event="ShowBookDescription", title=_("Book description"), reader=true}, book_cover = {category="none", event="ShowBookCover", title=_("Book cover"), reader=true, separator=true}, ---- translate_page = {category="none", event="TranslateCurrentPage", title=_("Translate current page"), reader=true, separator=true}, ---- toggle_page_change_animation = {category="none", event="TogglePageChangeAnimation", title=_("Toggle page turn animations"), reader=true, condition=Device:canDoSwipeAnimation()}, toggle_inverse_reading_order = {category="none", event="ToggleReadingOrder", title=_("Toggle page turn direction"), reader=true}, toggle_handmade_toc = {category="none", event="ToggleHandmadeToc", title=_("Toggle custom TOC"), reader=true}, toggle_handmade_flows = {category="none", event="ToggleHandmadeFlows", title=_("Toggle custom hidden flows"), reader=true, separator=true}, ---- set_highlight_action = {category="string", event="SetHighlightAction", title=_("Set highlight action"), args_func=ReaderHighlight.getHighlightActions, reader=true}, cycle_highlight_action = {category="none", event="CycleHighlightAction", title=_("Cycle highlight action"), reader=true}, cycle_highlight_style = {category="none", event="CycleHighlightStyle", title=_("Cycle highlight style"), reader=true, separator=true}, ---- flush_settings = {category="none", event="FlushSettings", arg=true, title=_("Save book metadata"), reader=true, separator=true}, ---- -- Reflowable documents set_font = {category="string", event="SetFont", title=_("Set font"), rolling=true, args_func=require("fontlist").getFontArgFunc,}, increase_font = {category="incrementalnumber", event="IncreaseFontSize", min=0.5, max=255, step=0.5, title=_("Increase font size"), rolling=true}, decrease_font = {category="incrementalnumber", event="DecreaseFontSize", min=0.5, max=255, step=0.5, title=_("Decrease font size"), rolling=true}, -- Page layout documents toggle_page_flipping = {category="none", event="TogglePageFlipping", title=_("Toggle page flipping"), paging=true}, toggle_bookmark_flipping = {category="none", event="ToggleBookmarkFlipping", title=_("Toggle bookmark flipping"), paging=true}, toggle_reflow = {category="none", event="ToggleReflow", title=_("Toggle reflow"), paging=true}, zoom = {category="string", event="SetZoomMode", title=_("Zoom mode"), args_func=ReaderZooming.getZoomModeActions, paging=true}, zoom_factor_change = {category="none", event="ZoomFactorChange", title=_("Change zoom factor"), paging=true, separator=true}, ---- panel_zoom_toggle = {category="none", event="TogglePanelZoomSetting", title=_("Toggle panel zoom"), paging=true, separator=true}, ---- -- parsed from CreOptions rotation_mode = {category="string", device=true}, font_size = {category="absolutenumber", title=_("Set font size"), rolling=true, step=0.5}, word_spacing = {category="string", rolling=true}, word_expansion = {category="string", rolling=true}, font_gamma = {category="string", rolling=true}, font_base_weight = {category="string", rolling=true}, font_hinting = {category="string", rolling=true}, font_kerning = {category="string", rolling=true, separator=true}, ---- visible_pages = {category="string", rolling=true, separator=true}, ---- h_page_margins = {category="string", rolling=true}, sync_t_b_page_margins = {category="string", rolling=true}, t_page_margin = {category="absolutenumber", rolling=true}, b_page_margin = {category="absolutenumber", rolling=true, separator=true}, ---- view_mode = {category="string", rolling=true}, block_rendering_mode = {category="string", rolling=true}, render_dpi = {category="string", title=_("Zoom"), rolling=true}, line_spacing = {category="absolutenumber", rolling=true, separator=true}, ---- status_line = {category="string", rolling=true}, embedded_css = {category="string", rolling=true}, embedded_fonts = {category="string", rolling=true}, smooth_scaling = {category="string", rolling=true}, nightmode_images = {category="string", rolling=true}, -- parsed from KoptOptions kopt_trim_page = {category="string", paging=true}, kopt_page_margin = {category="string", paging=true}, kopt_zoom_overlap_h = {category="absolutenumber", paging=true}, kopt_zoom_overlap_v = {category="absolutenumber", paging=true}, kopt_zoom_mode_type = {category="string", paging=true}, -- kopt_zoom_range_number = {category="string", paging=true}, kopt_zoom_factor = {category="string", paging=true}, kopt_zoom_mode_genus = {category="string", paging=true}, kopt_zoom_direction = {category="string", paging=true}, kopt_page_scroll = {category="string", paging=true}, kopt_page_gap_height = {category="string", paging=true}, kopt_full_screen = {category="string", paging=true}, kopt_line_spacing = {category="configurable", paging=true}, kopt_justification = {category="configurable", paging=true}, kopt_font_size = {category="string", paging=true, title=_("Font Size")}, kopt_font_fine_tune = {category="string", paging=true}, kopt_word_spacing = {category="configurable", paging=true}, kopt_text_wrap = {category="string", paging=true}, kopt_contrast = {category="string", paging=true}, kopt_page_opt = {category="configurable", paging=true}, kopt_hw_dithering = {category="configurable", paging=true, condition=Device:hasEinkScreen() and Device:canHWDither()}, kopt_sw_dithering = {category="configurable", paging=true, condition=Device:hasEinkScreen() and not Device:canHWDither() and Device.screen.fb_bpp == 8}, kopt_quality = {category="configurable", paging=true}, kopt_doc_language = {category="string", paging=true}, kopt_forced_ocr = {category="configurable", paging=true}, kopt_writing_direction = {category="configurable", paging=true}, kopt_defect_size = {category="string", paging=true, condition=false}, kopt_auto_straighten = {category="absolutenumber", paging=true}, kopt_detect_indent = {category="configurable", paging=true, condition=false}, kopt_max_columns = {category="configurable", paging=true}, settings = nil, -- reserved for per instance dispatcher settings } -- array for item order in menu local dispatcher_menu_order = { -- General "reading_progress", "open_previous_document", "history", "history_search", "favorites", "collections", "filemanager", ---- "dictionary_lookup", "wikipedia_lookup", ---- "show_menu", "menu_search", "screenshot", ---- -- Device "exit_screensaver", "start_usbms", "suspend", "restart", "reboot", "poweroff", "exit", ---- "toggle_hold_corners", "touch_input_on", "touch_input_off", "toggle_touch_input", ---- "swap_page_turn_buttons", ---- "toggle_key_repeat", "toggle_gsensor", "lock_gsensor", "rotation_mode", "toggle_rotation", "invert_rotation", "iterate_rotation", "iterate_rotation_ccw", ---- "wifi_on", "wifi_off", "toggle_wifi", "toggle_fullscreen", "show_network_info", ---- -- Screen and lights "show_frontlight_dialog", "toggle_frontlight", "set_frontlight", "increase_frontlight", "decrease_frontlight", "set_frontlight_warmth", "increase_frontlight_warmth", "decrease_frontlight_warmth", "night_mode", "set_night_mode", ---- "full_refresh", "set_refresh_rate", "set_day_refresh_rate", "set_night_refresh_rate", "set_flash_on_chapter_boundaries", "toggle_flash_on_chapter_boundaries", "set_no_flash_on_second_chapter_page", "toggle_no_flash_on_second_chapter_page", "set_flash_on_pages_with_images", "toggle_flash_on_pages_with_images", ---- -- File browser "folder_up", "show_plus_menu", "toggle_select_mode", "refresh_content", "folder_shortcuts", "file_search", ---- -- "go_to" -- "back" -- Reader "open_next_document_in_folder", ---- "show_config_menu", "toggle_status_bar", "toggle_chapter_progress_bar", ---- "prev_chapter", "next_chapter", "first_page", "last_page", "random_page", "page_jmp", "go_to", "skim", "prev_bookmark", "next_bookmark", "first_bookmark", "last_bookmark", "latest_bookmark", ---- "back", "previous_location", "next_location", "follow_nearest_link", "follow_nearest_internal_link", "add_location_to_history", "clear_location_history", ---- "fulltext_search", "fulltext_search_findall_results", "toc", "book_map", "book_map_overview", "page_browser", "bookmarks", "bookmark_search", "toggle_bookmark", ---- "book_status", "book_info", "book_description", "book_cover", ---- "translate_page", ---- "toggle_page_change_animation", "toggle_inverse_reading_order", "toggle_handmade_toc", "toggle_handmade_flows", ---- "set_highlight_action", "cycle_highlight_action", "cycle_highlight_style", ---- "flush_settings", ---- -- Reflowable documents "set_font", "increase_font", "decrease_font", "font_size", "word_spacing", "word_expansion", "font_gamma", "font_base_weight", "font_hinting", "font_kerning", ---- "visible_pages", ---- "h_page_margins", "sync_t_b_page_margins", "t_page_margin", "b_page_margin", ---- "view_mode", "block_rendering_mode", "render_dpi", "line_spacing", ---- "status_line", "embedded_css", "embedded_fonts", "smooth_scaling", "nightmode_images", -- Fixed layout documents "toggle_page_flipping", "toggle_bookmark_flipping", "toggle_reflow", "zoom", "zoom_factor_change", ---- "panel_zoom_toggle", ---- "kopt_trim_page", "kopt_page_margin", "kopt_zoom_overlap_h", "kopt_zoom_overlap_v", "kopt_zoom_mode_type", --"kopt_zoom_range_number", -- can't figure out how this name text func works "kopt_zoom_factor", "kopt_zoom_mode_genus", "kopt_zoom_direction", "kopt_page_scroll", "kopt_page_gap_height", "kopt_full_screen", "kopt_line_spacing", "kopt_justification", "kopt_font_size", "kopt_font_fine_tune", "kopt_word_spacing", "kopt_text_wrap", "kopt_contrast", "kopt_page_opt", "kopt_hw_dithering", "kopt_sw_dithering", "kopt_quality", "kopt_doc_language", "kopt_forced_ocr", "kopt_writing_direction", "kopt_defect_size", "kopt_auto_straighten", "kopt_detect_indent", "kopt_max_columns", } --[[-- add settings from CreOptions / KoptOptions --]]-- function Dispatcher:init() if Dispatcher.initialized then return end local parseoptions = function(base, i, prefix) for y=1, #base[i].options do local option = base[i].options[y] local name = prefix and prefix .. option.name or option.name if settingsList[name] ~= nil then if option.name ~= nil and option.values ~= nil then settingsList[name].configurable = {name = option.name, values = option.values} end if settingsList[name].event == nil then settingsList[name].event = option.event end if settingsList[name].title == nil then settingsList[name].title = option.name_text end if settingsList[name].category == "string" or settingsList[name].category == "configurable" then if settingsList[name].toggle == nil then settingsList[name].toggle = option.toggle or option.labels if settingsList[name].toggle == nil then settingsList[name].toggle = {} for z=1,#option.values do if type(option.values[z]) == "table" then settingsList[name].toggle[z] = option.values[z][1] else settingsList[name].toggle[z] = option.values[z] end end end end if settingsList[name].args == nil then settingsList[name].args = option.args or option.values end elseif settingsList[name].category == "absolutenumber" then if settingsList[name].min == nil then settingsList[name].min = option.args and option.args[1] or option.values[1] end if settingsList[name].max == nil then settingsList[name].max = option.args and option.args[#option.args] or option.values[#option.values] end if settingsList[name].default == nil then settingsList[name].default = option.default_value end end settingsList[name].unit = option.more_options_param and option.more_options_param.unit end end end for i=1,#CreOptions do parseoptions(CreOptions, i) end for i=1,#KoptOptions do parseoptions(KoptOptions, i, "kopt_") end UIManager:broadcastEvent(Event:new("DispatcherRegisterActions")) Dispatcher.initialized = true end --[[-- Adds settings at runtime. @usage function Hello:onDispatcherRegisterActions() Dispatcher:registerAction("helloworld_action", {category="none", event="HelloWorld", title=_("Hello World"), general=true}) end function Hello:init() self:onDispatcherRegisterActions() end @param name the key to use in the table @param value a table per settingsList above --]]-- function Dispatcher:registerAction(name, value) if settingsList[name] == nil then settingsList[name] = value table.insert(dispatcher_menu_order, name) end return true end --[[-- Removes settings at runtime. @param name the key to use in the table --]]-- function Dispatcher:removeAction(name) local k = util.arrayContains(dispatcher_menu_order, name) if k then table.remove(dispatcher_menu_order, k) settingsList[name] = nil end return true end local function iter_func(settings) if settings and settings.settings and settings.settings.order then return ipairs(settings.settings.order) else return pairs(settings) end end -- Returns the number of items present in the settings table function Dispatcher:_itemsCount(settings) if settings then local count = util.tableSize(settings) if count > 0 and settings.settings ~= nil then count = count - 1 end return count end end -- Returns a display name for the item. function Dispatcher:getNameFromItem(item, settings, dont_show_value) if settingsList[item] == nil then return _("Unknown item") end local title, category = settingsList[item].title, settingsList[item].category local value = settings and settings[item] if dont_show_value or value == nil or (value == 0 and category == "incrementalnumber") then return title else local display_value if category == "string" or category == "configurable" then if type(value) == "table" then display_value = string.format("%d / %d", unpack(value)) else if not settingsList[item].args and settingsList[item].args_func then settingsList[item].args, settingsList[item].toggle = settingsList[item].args_func() end local value_num = util.arrayContains(settingsList[item].args, value) display_value = settingsList[item].toggle[value_num] or string.format("%.1f", value) end elseif category == "absolutenumber" or category == "incrementalnumber" then display_value = tostring(value) end if display_value then if settingsList[item].unit and (type(value) == "table" or tonumber(display_value)) then display_value = display_value .. " " .. settingsList[item].unit end title = title .. ": " .. display_value end end return title end -- Converts copt/kopt-options values to args. function Dispatcher:getArgFromValue(item, value) local value_num = util.arrayContains(settingsList[item].configurable.values, value) return settingsList[item].args[value_num] end -- Add the item to the end of the execution order. -- If item or the order is nil all items will be added. function Dispatcher:_addToOrder(location, settings, item) if location[settings] then if not location[settings].settings then location[settings].settings = {} end if not location[settings].settings.order or item == nil then location[settings].settings.order = {} for k in pairs(location[settings]) do if settingsList[k] ~= nil then table.insert(location[settings].settings.order, k) end end else if not util.arrayContains(location[settings].settings.order, item) then table.insert(location[settings].settings.order, item) end end end end -- Remove the item from the execution order. -- If item is nil all items will be removed. -- If the resulting order is empty it will be nilled function Dispatcher:_removeFromOrder(location, settings, item) if location[settings] and location[settings].settings then if location[settings].settings.order then if item then local k = util.arrayContains(location[settings].settings.order, item) if k then table.remove(location[settings].settings.order, k) end else location[settings].settings.order = {} end if next(location[settings].settings.order) == nil then location[settings].settings.order = nil if next(location[settings].settings) == nil then location[settings].settings = nil end end end end end -- Get a textual representation of the enabled actions to display in a menu item. function Dispatcher:menuTextFunc(settings) local action_name = _("Pass through") if settings then local count = Dispatcher:_itemsCount(settings) if count == 0 then return _("Nothing") end if count == 1 then local item = next(settings) if item == "settings" then item = next(settings, item) end action_name = Dispatcher:getNameFromItem(item, settings) else action_name = T(NC_("Dispatcher", "1 action", "%1 actions", count), count) end end return action_name end -- Get a list of all enabled actions to display in a menu. function Dispatcher:getDisplayList(settings) local item_table = {} if not settings then return item_table end for item, v in iter_func(settings) do if type(item) == "number" then item = v end if settingsList[item] ~= nil and (settingsList[item].condition == nil or settingsList[item].condition == true) then table.insert(item_table, {text = Dispatcher:getNameFromItem(item, settings), key = item}) end end return item_table end -- Display a SortWidget to sort the enable actions execution order. function Dispatcher:_sortActions(caller, location, settings, touchmenu_instance) local display_list = Dispatcher:getDisplayList(location[settings]) local SortWidget = require("ui/widget/sortwidget") local sort_widget sort_widget = SortWidget:new{ title = _("Arrange actions"), item_table = display_list, callback = function() if location[settings] and next(location[settings]) ~= nil then if not location[settings].settings then location[settings].settings = {} end location[settings].settings.order = {} for i, v in ipairs(sort_widget.item_table) do location[settings].settings.order[i] = v.key end end if touchmenu_instance then touchmenu_instance:updateItems() end caller.updated = true end } UIManager:show(sort_widget) end function Dispatcher:_addItem(caller, menu, location, settings, section) for _, k in ipairs(dispatcher_menu_order) do if settingsList[k][section] == true and (settingsList[k].condition == nil or settingsList[k].condition) then if settingsList[k].category == "none" or settingsList[k].category == "arg" then table.insert(menu, { text = settingsList[k].title, checked_func = function() return location[settings] ~= nil and location[settings][k] ~= nil end, callback = function(touchmenu_instance) if location[settings] == nil then location[settings] = {} end if location[settings][k] then location[settings][k] = nil Dispatcher:_removeFromOrder(location, settings, k) else location[settings][k] = true Dispatcher:_addToOrder(location, settings, k) end caller.updated = true if touchmenu_instance then touchmenu_instance:updateItems() end end, separator = settingsList[k].separator, }) elseif settingsList[k].category == "absolutenumber" then table.insert(menu, { text_func = function() return Dispatcher:getNameFromItem(k, location[settings]) end, checked_func = function() return location[settings] ~= nil and location[settings][k] ~= nil end, callback = function(touchmenu_instance) local SpinWidget = require("ui/widget/spinwidget") local precision if settingsList[k].step and math.floor(settingsList[k].step) ~= settingsList[k].step then precision = "%0.1f" end local items = SpinWidget:new{ value = location[settings] ~= nil and location[settings][k] or settingsList[k].default or settingsList[k].min, value_min = settingsList[k].min, value_step = settingsList[k].step or 1, precision = precision, value_hold_step = 5, value_max = settingsList[k].max, default_value = settingsList[k].default, title_text = Dispatcher:getNameFromItem(k, location[settings], true), ok_always_enabled = true, callback = function(spin) if location[settings] == nil then location[settings] = {} end location[settings][k] = spin.value Dispatcher:_addToOrder(location, settings, k) caller.updated = true if touchmenu_instance then touchmenu_instance:updateItems() end end } UIManager:show(items) end, hold_callback = function(touchmenu_instance) if location[settings] ~= nil and location[settings][k] ~= nil then location[settings][k] = nil Dispatcher:_removeFromOrder(location, settings, k) caller.updated = true end if touchmenu_instance then touchmenu_instance:updateItems() end end, separator = settingsList[k].separator, }) elseif settingsList[k].category == "incrementalnumber" then table.insert(menu, { text_func = function() return Dispatcher:getNameFromItem(k, location[settings]) end, checked_func = function() return location[settings] ~= nil and location[settings][k] ~= nil end, callback = function(touchmenu_instance) local _ = require("gettext") local precision if settingsList[k].step and math.floor(settingsList[k].step) ~= settingsList[k].step then precision = "%0.1f" end local SpinWidget = require("ui/widget/spinwidget") local items = SpinWidget:new{ value = location[settings] ~= nil and location[settings][k] or settingsList[k].min, value_min = settingsList[k].min, value_step = settingsList[k].step or 1, precision = precision, value_hold_step = 5, value_max = settingsList[k].max, title_text = Dispatcher:getNameFromItem(k, location[settings], true), info_text = _([[When set to 0, the gesture's distance (if any) is used]]), ok_always_enabled = true, callback = function(spin) if location[settings] == nil then location[settings] = {} end location[settings][k] = spin.value Dispatcher:_addToOrder(location, settings, k) caller.updated = true if touchmenu_instance then touchmenu_instance:updateItems() end end } UIManager:show(items) end, hold_callback = function(touchmenu_instance) if location[settings] ~= nil and location[settings][k] ~= nil then location[settings][k] = nil Dispatcher:_removeFromOrder(location, settings, k) caller.updated = true end if touchmenu_instance then touchmenu_instance:updateItems() end end, separator = settingsList[k].separator, }) elseif settingsList[k].category == "string" or settingsList[k].category == "configurable" then local sub_item_table = {} if not settingsList[k].args and settingsList[k].args_func then settingsList[k].args, settingsList[k].toggle = settingsList[k].args_func() end for i=1,#settingsList[k].args do table.insert(sub_item_table, { text = tostring(settingsList[k].toggle[i]), checked_func = function() if location[settings] ~= nil and location[settings][k] ~= nil then if type(location[settings][k]) == "table" then return location[settings][k][1] == settingsList[k].args[i][1] else return location[settings][k] == settingsList[k].args[i] end end end, callback = function() if location[settings] == nil then location[settings] = {} end location[settings][k] = settingsList[k].args[i] Dispatcher:_addToOrder(location, settings, k) caller.updated = true end, }) end table.insert(menu, { text_func = function() return Dispatcher:getNameFromItem(k, location[settings]) end, checked_func = function() return location[settings] ~= nil and location[settings][k] ~= nil end, sub_item_table = sub_item_table, keep_menu_open = true, hold_callback = function(touchmenu_instance) if location[settings] ~= nil and location[settings][k] ~= nil then location[settings][k] = nil Dispatcher:_removeFromOrder(location, settings, k) caller.updated = true end if touchmenu_instance then touchmenu_instance:updateItems() end end, separator = settingsList[k].separator, menu_item_id = k, }) end end end end --[[-- Add a submenu to edit which items are dispatched arguments are: 1) the caller so dispatcher can set the updated flag 2) the table representing the submenu (can be empty) 3) the object (table) in which the settings table is found 4) the name of the settings table example usage: Dispatcher:addSubMenu(self, sub_items, self.data, "profile1") --]]-- function Dispatcher:addSubMenu(caller, menu, location, settings) Dispatcher:init() menu.ignored_by_menu_search = true -- all those would be duplicated table.insert(menu, { text = _("Nothing"), separator = true, checked_func = function() return location[settings] ~= nil and Dispatcher:_itemsCount(location[settings]) == 0 end, callback = function(touchmenu_instance) local name = location[settings] and location[settings].settings and location[settings].settings.name location[settings] = {} if name then location[settings].settings = { name = name } end caller.updated = true if touchmenu_instance then touchmenu_instance:updateItems() end end, }) local section_list = { {"general", _("General")}, {"device", _("Device")}, {"screen", _("Screen and lights")}, {"filemanager", _("File browser")}, {"reader", _("Reader")}, {"rolling", _("Reflowable documents (epub, fb2, txt…)")}, {"paging", _("Fixed layout documents (pdf, djvu, pics…)")}, } menu.max_per_page = 1 + #section_list -- settings in page 2 for _, section in ipairs(section_list) do local submenu = {} Dispatcher:_addItem(caller, submenu, location, settings, section[1]) table.insert(menu, { text = section[2], checked_func = function() if location[settings] ~= nil then for k, _ in pairs(location[settings]) do if settingsList[k] ~= nil and settingsList[k][section[1]] == true and (settingsList[k].condition == nil or settingsList[k].condition) then return true end end end end, hold_callback = function(touchmenu_instance) if location[settings] ~= nil then for k, _ in pairs(location[settings]) do if settingsList[k] ~= nil and settingsList[k][section[1]] == true then location[settings][k] = nil Dispatcher:_removeFromOrder(location, settings, k) caller.updated = true end end if touchmenu_instance then touchmenu_instance:updateItems() end end end, sub_item_table = submenu, }) end menu[#menu].separator = true table.insert(menu, { text = _("Arrange actions"), checked_func = function() return location[settings] ~= nil and location[settings].settings ~= nil and location[settings].settings.order ~= nil end, callback = function(touchmenu_instance) Dispatcher:_sortActions(caller, location, settings, touchmenu_instance) end, hold_callback = function(touchmenu_instance) if location[settings] and location[settings].settings and location[settings].settings.order then Dispatcher:_removeFromOrder(location, settings) caller.updated = true if touchmenu_instance then touchmenu_instance:updateItems() end end end, }) table.insert(menu, { text = _("Show as QuickMenu"), checked_func = function() return location[settings] ~= nil and location[settings].settings ~= nil and location[settings].settings.show_as_quickmenu end, callback = function() if location[settings] then if location[settings].settings then if location[settings].settings.show_as_quickmenu then location[settings].settings.show_as_quickmenu = nil if next(location[settings].settings) == nil then location[settings].settings = nil end else location[settings].settings.show_as_quickmenu = true end else location[settings].settings = {["show_as_quickmenu"] = true} end caller.updated = true end end, }) table.insert(menu, { text = _("Keep QuickMenu open"), checked_func = function() return location[settings] ~= nil and location[settings].settings ~= nil and location[settings].settings.keep_open_on_apply end, callback = function() if location[settings] then if location[settings].settings then if location[settings].settings.keep_open_on_apply then location[settings].settings.keep_open_on_apply = nil if next(location[settings].settings) == nil then location[settings].settings = nil end else location[settings].settings.keep_open_on_apply = true end else location[settings].settings = {["keep_open_on_apply"] = true} end caller.updated = true end end, }) end function Dispatcher:isActionEnabled(action) local disabled = true if action and (action.condition == nil or action.condition == true) then local ui = require("apps/reader/readerui").instance local context = ui and (ui.paging and "paging" or "rolling") if context == "paging" then disabled = action["rolling"] elseif context == "rolling" then disabled = action["paging"] else -- FM disabled = (action["reader"] or action["rolling"] or action["paging"]) and not action["filemanager"] end end return not disabled end function Dispatcher:_showAsMenu(settings, exec_props) local title = settings.settings.name or _("QuickMenu") local keep_open_on_apply = settings.settings.keep_open_on_apply local display_list = Dispatcher:getDisplayList(settings) local quickmenu local buttons = {} if exec_props and exec_props.qm_show then table.insert(buttons, {{ text = _("Execute all"), align = "left", font_face = "smallinfofont", font_size = 22, callback = function() UIManager:close(quickmenu) Dispatcher:execute(settings, { qm_show = false }) end, }}) end for _, v in ipairs(display_list) do table.insert(buttons, {{ text = v.text, enabled = Dispatcher:isActionEnabled(settingsList[v.key]), align = "left", font_face = "smallinfofont", font_size = 22, font_bold = false, callback = function() UIManager:close(quickmenu) Dispatcher:execute({[v.key] = settings[v.key]}) if keep_open_on_apply and not util.stringStartsWith(v.key, "touch_input") then quickmenu:setTitle(title) UIManager:show(quickmenu) end end, hold_callback = function() if v.key:sub(1, 13) == "profile_exec_" then UIManager:close(quickmenu) UIManager:sendEvent(Event:new(settingsList[v.key].event, settingsList[v.key].arg, { qm_show = true })) end end, }}) end local ButtonDialog = require("ui/widget/buttondialog") quickmenu = ButtonDialog:new{ title = title, title_align = "center", shrink_unneeded_width = true, shrink_min_width = math.floor(0.6 * Screen:getWidth()), use_info_style = false, buttons = buttons, anchor = exec_props and exec_props.qm_anchor, } UIManager:show(quickmenu) end --[[-- Calls the events in a settings list arguments are: 1) the settings table 2) execution management table: { qm_show = true|false} - forcibly show QM / run { qm_anchor = ges.pos } - anchor position { gesture = ges } - a `gestures` object --]]-- function Dispatcher:execute(settings, exec_props) if ((exec_props == nil or exec_props.qm_show == nil) and settings.settings and settings.settings.show_as_quickmenu) or (exec_props and exec_props.qm_show) then return Dispatcher:_showAsMenu(settings, exec_props) end local has_many = Dispatcher:_itemsCount(settings) > 1 if has_many then UIManager:broadcastEvent(Event:new("BatchedUpdate")) end local gesture = exec_props and exec_props.gesture for k, v in iter_func(settings) do if type(k) == "number" then k = v v = settings[k] end if Dispatcher:isActionEnabled(settingsList[k]) then Notification:setNotifySource(Notification.SOURCE_DISPATCHER) if settingsList[k].configurable then local value = v if type(v) ~= "number" then for i, r in ipairs(settingsList[k].args) do if v == r then value = settingsList[k].configurable.values[i] break end end end UIManager:sendEvent(Event:new("ConfigChange", settingsList[k].configurable.name, value)) end local category = settingsList[k].category local event = settingsList[k].event if category == "none" then if settingsList[k].arg ~= nil then UIManager:sendEvent(Event:new(event, settingsList[k].arg, exec_props)) else UIManager:sendEvent(Event:new(event)) end elseif category == "absolutenumber" or category == "string" then UIManager:sendEvent(Event:new(event, v)) elseif category == "arg" then -- the event can accept a gesture object or an argument local arg = gesture or settingsList[k].arg UIManager:sendEvent(Event:new(event, arg)) elseif category == "incrementalnumber" then -- the event can accept a gesture object or a number local arg = v ~= 0 and v or gesture or 0 UIManager:sendEvent(Event:new(event, arg)) end end Notification:resetNotifySource() end if has_many then UIManager:broadcastEvent(Event:new("BatchedUpdateDone")) end end return Dispatcher