<commit_before>local ConfirmBox = require("ui/widget/confirmbox")
local DataStorage = require("datastorage")
local Device = require("device")
local Event = require("ui/event")
local InputContainer = require("ui/widget/container/inputcontainer")
local Screen = require("device").screen
local UIManager = require("ui/uimanager")
local T = require("ffi/util").template
local _ = require("gettext")
local util = require("util")

local default_gesture = {
    tap_right_bottom_corner = "nothing",
    tap_left_bottom_corner = Device:hasFrontlight() and "toggle_frontlight" or "nothing",
    short_diagonal_swipe = "full_refresh",
    multiswipe = "nothing", -- otherwise registerGesture() won't pick up on multiswipes
    multiswipe_west_east = "previous_location",
}

local ReaderGesture = InputContainer:new{
    multiswipes_enabled = G_reader_settings:readSetting("multiswipes_enabled"),
}

local multiswipes_info_text = _([[
Multiswipes allow you to perform complex gestures built up out of multiple straight swipes.]])

function ReaderGesture:init()
    if not Device:isTouchDevice() then return end
    self.is_docless = self.ui == nil or self.ui.document == nil
    self.ges_mode = self.is_docless and "gesture_fm" or "gesture_reader"
    local gm = G_reader_settings:readSetting(self.ges_mode)
    if gm == nil then G_reader_settings:saveSetting(self.ges_mode, {}) end
    self.ui.menu:registerToMainMenu(self)
    self:initGesture()
end

function ReaderGesture:initGesture()
    local gesture_manager = G_reader_settings:readSetting(self.ges_mode)
    for gesture, action in pairs(default_gesture) do
        if not gesture_manager[gesture] then
            gesture_manager[gesture] = action
        end
    end
    for gesture, action in pairs(gesture_manager) do
        self:setupGesture(gesture, action)
    end
    G_reader_settings:saveSetting(self.ges_mode, gesture_manager)
end

function ReaderGesture:addToMainMenu(menu_items)
    menu_items.gesture = {
        text = _("Gesture manager"),
        sub_item_table = {
            {
                text = _("Enable multiswipes"),
                checked_func = function() return self.multiswipes_enabled end,
                callback = function()
                    G_reader_settings:saveSetting("multiswipes_enabled", not self.multiswipes_enabled)
                    self.multiswipes_enabled = G_reader_settings:isTrue("multiswipes_enabled")
                end,
                help_text = multiswipes_info_text,
            },
            {
                text = _("Multiswipe"),
                sub_item_table = self:buildMultiswipeMenu(),
                enabled_func = function() return self.multiswipes_enabled end,
                separator = true,
            },
            {
                text = _("Tap bottom left corner"),
                sub_item_table = self:buildMenu("tap_left_bottom_corner", default_gesture["tap_left_bottom_corner"]),
            },
            {
                text = _("Tap bottom right corner"),
                sub_item_table = self:buildMenu("tap_right_bottom_corner", default_gesture["tap_right_bottom_corner"]),
            },
            {
                text = _("Short diagonal swipe"),
                sub_item_table = self:buildMenu("short_diagonal_swipe", default_gesture["short_diagonal_swipe"]),
            },
        },
    }
end

function ReaderGesture:buildMenu(ges, default)
    local gesture_manager = G_reader_settings:readSetting(self.ges_mode)
    local menu = {
        {_("Nothing"), "nothing", true },
        {_("Back 10 pages"), "page_jmp_back_10", not self.is_docless},
        {_("Previous page"), "page_jmp_back_1", not self.is_docless},
        {_("Forward 10 pages"), "page_jmp_fwd_10", not self.is_docless},
        {_("Next page"), "page_jmp_fwd_1", not self.is_docless},
        {_("Back to previous location"), "previous_location", not self.is_docless},
        {_("Folder up"), "folder_up", self.is_docless},
        {_("Bookmarks"), "bookmarks", not self.is_docless},
        {_("History"), "history", true},
        {_("Open previous document"), "open_previous_document", true, true},
        {_("Table of contents"), "toc", not self.is_docless},
        {_("Reading progress"), "reading_progress", ReaderGesture.getReaderProgress ~= nil},
        {_("Full screen refresh"), "full_refresh", true},
        {_("Night mode"), "night_mode", true},
        {_("Suspend"), "suspend", true},
        {_("Toggle frontlight"), "toggle_frontlight", Device:hasFrontlight()},
        {_("Toggle accelerometer"), "toggle_gsensor", Device:canToggleGSensor()},
        {_("Toggle rotation"), "toggle_rotation", not self.is_docless},
        {_("Zoom to fit content width"), "zoom_contentwidth", not self.is_docless},
        {_("Zoom to fit content height"), "zoom_contentheight", not self.is_docless},
        {_("Zoom to fit page width"), "zoom_pagewidth", not self.is_docless},
        {_("Zoom to fit page height"), "zoom_pageheight", not self.is_docless},
        {_("Zoom to fit column"), "zoom_column", not self.is_docless},
        {_("Zoom to fit content"), "zoom_content", not self.is_docless},
        {_("Zoom to fit page"), "zoom_page", not self.is_docless},
    }
    local return_menu = {}
    -- add default action to the top of the submenu
    for __, entry in pairs(menu) do
        if entry[2] == default then
            local menu_entry_default = T(_("%1 (default)"), entry[1])
            table.insert(return_menu, self:createSubMenu(menu_entry_default, entry[2], ges, true))

            if not gesture_manager[ges] then
                gesture_manager[ges] = default
                G_reader_settings:saveSetting(self.ges_mode, gesture_manager)
            end
            break
        end
    end
    -- another elements
    for _, entry in pairs(menu) do
        if not entry[3] and gesture_manager[ges] == entry[2] then
            gesture_manager[ges] = "nothing"
            G_reader_settings:saveSetting(self.ges_mode, gesture_manager)
        end
        if entry[2] ~= default and entry[3] then
            local sep = entry[2] == "nothing" or entry[4] == true
            table.insert(return_menu, self:createSubMenu(entry[1], entry[2], ges, sep))
        end
    end
    return return_menu
end

local multiswipes = {
    "west east",
    "east west",
    "north south",
    "south north",
    "south west",
    "south east",
    "east south",
    "west south",
}
local custom_multiswipes_path = DataStorage:getSettingsDir().."/multiswipes"
if util.pathExists(custom_multiswipes_path..".lua") then
    for k, v in pairs(require(custom_multiswipes_path)) do
        table.insert(multiswipes, v)
    end
end<commit_msg>[feat, UX] Add multiswipe recorder (#4644)<commit_after>local ConfirmBox = require("ui/widget/confirmbox")
local DataStorage = require("datastorage")
local Device = require("device")
local Event = require("ui/event")
local Geom = require("ui/geometry")
local GestureRange = require("ui/gesturerange")
local InputContainer = require("ui/widget/container/inputcontainer")
local InputDialog = require("ui/widget/inputdialog")
local LuaData = require("luadata")
local Screen = require("device").screen
local UIManager = require("ui/uimanager")
local T = require("ffi/util").template
local _ = require("gettext")
local logger = require("logger")
local util = require("util")

local default_gesture = {
    tap_right_bottom_corner = "nothing",
    tap_left_bottom_corner = Device:hasFrontlight() and "toggle_frontlight" or "nothing",
    short_diagonal_swipe = "full_refresh",
    multiswipe = "nothing", -- otherwise registerGesture() won't pick up on multiswipes
    multiswipe_west_east = "previous_location",
}

local ReaderGesture = InputContainer:new{
    multiswipes_enabled = G_reader_settings:readSetting("multiswipes_enabled"),
}

local custom_multiswipes_path = DataStorage:getSettingsDir().."/multiswipes.lua"
local custom_multiswipes = LuaData:open(custom_multiswipes_path, { name = "MultiSwipes" })
local custom_multiswipes_table = custom_multiswipes:readSetting("multiswipes")

local multiswipes_info_text = _([[
Multiswipes allow you to perform complex gestures built up out of multiple straight swipes.]])

function ReaderGesture:init()
    if not Device:isTouchDevice() then return end
    self.is_docless = self.ui == nil or self.ui.document == nil
    self.ges_mode = self.is_docless and "gesture_fm" or "gesture_reader"
    local gm = G_reader_settings:readSetting(self.ges_mode)
    if gm == nil then G_reader_settings:saveSetting(self.ges_mode, {}) end
    self.ui.menu:registerToMainMenu(self)
    self:initGesture()
end

function ReaderGesture:initGesture()
    local gesture_manager = G_reader_settings:readSetting(self.ges_mode)
    for gesture, action in pairs(default_gesture) do
        if not gesture_manager[gesture] then
            gesture_manager[gesture] = action
        end
    end
    for gesture, action in pairs(gesture_manager) do
        self:setupGesture(gesture, action)
    end
    G_reader_settings:saveSetting(self.ges_mode, gesture_manager)
end

function ReaderGesture:addToMainMenu(menu_items)
    menu_items.gesture = {
        text = _("Gesture manager"),
        sub_item_table = {
            {
                text = _("Enable multiswipes"),
                checked_func = function() return self.multiswipes_enabled end,
                callback = function()
                    G_reader_settings:saveSetting("multiswipes_enabled", not self.multiswipes_enabled)
                    self.multiswipes_enabled = G_reader_settings:isTrue("multiswipes_enabled")
                end,
                help_text = multiswipes_info_text,
            },
            {
                text = _("Multiswipe recorder"),
                enabled_func = function() return self.multiswipes_enabled end,
                callback = function()
                    local multiswipe_recorder
                    multiswipe_recorder = InputDialog:new{
                        title = _("Multiswipe recorder"),
                        input_hint = _("Make a multiswipe gesture"),
                        buttons = {
                            {
                                {
                                    text = _("Cancel"),
                                    callback = function()
                                        UIManager:close(multiswipe_recorder)
                                    end,
                                },
                                {
                                    text = _("Save"),
                                    is_enter_default = true,
                                    callback = function()
                                        local recorded_multiswipe = multiswipe_recorder._raw_multiswipe
                                        if not recorded_multiswipe then return end
                                        logger.dbg("Multiswipe recorder detected:", recorded_multiswipe)
                                        custom_multiswipes:addTableItem("multiswipes", recorded_multiswipe)
                                        UIManager:close(multiswipe_recorder)
                                    end,
                                },
                            }
                        },
                    }

                    multiswipe_recorder.ges_events.Multiswipe = {
                        GestureRange:new{
                            ges = "multiswipe",
                            range = Geom:new{
                                x = 0, y = 0,
                                w = Screen:getWidth(),
                                h = Screen:getHeight(),
                            },
                            doc = "Multiswipe in gesture creator"
                        }
                    }

                    function multiswipe_recorder:onMultiswipe(arg, ges)
                        multiswipe_recorder._raw_multiswipe = ges.multiswipe_directions
                        multiswipe_recorder:setInputText(ReaderGesture:friendlyMultiswipeName(multiswipe_recorder._raw_multiswipe))
                    end

                    UIManager:show(multiswipe_recorder)
                end,
                help_text = _("The number of possible multiswipe gestures is theoretically infinite. With the multiswipe recorder you can easily record your own."),
            },
            {
                text = _("Multiswipe"),
                sub_item_table = self:buildMultiswipeMenu(),
                enabled_func = function() return self.multiswipes_enabled end,
                separator = true,
            },
            {
                text = _("Tap bottom left corner"),
                sub_item_table = self:buildMenu("tap_left_bottom_corner", default_gesture["tap_left_bottom_corner"]),
            },
            {
                text = _("Tap bottom right corner"),
                sub_item_table = self:buildMenu("tap_right_bottom_corner", default_gesture["tap_right_bottom_corner"]),
            },
            {
                text = _("Short diagonal swipe"),
                sub_item_table = self:buildMenu("short_diagonal_swipe", default_gesture["short_diagonal_swipe"]),
            },
        },
    }
end

function ReaderGesture:buildMenu(ges, default)
    local gesture_manager = G_reader_settings:readSetting(self.ges_mode)
    local menu = {
        {_("Nothing"), "nothing", true },
        {_("Back 10 pages"), "page_jmp_back_10", not self.is_docless},
        {_("Previous page"), "page_jmp_back_1", not self.is_docless},
        {_("Forward 10 pages"), "page_jmp_fwd_10", not self.is_docless},
        {_("Next page"), "page_jmp_fwd_1", not self.is_docless},
        {_("Back to previous location"), "previous_location", not self.is_docless},
        {_("Folder up"), "folder_up", self.is_docless},
        {_("Bookmarks"), "bookmarks", not self.is_docless},
        {_("History"), "history", true},
        {_("Open previous document"), "open_previous_document", true, true},
        {_("Table of contents"), "toc", not self.is_docless},
        {_("Reading progress"), "reading_progress", ReaderGesture.getReaderProgress ~= nil},
        {_("Full screen refresh"), "full_refresh", true},
        {_("Night mode"), "night_mode", true},
        {_("Suspend"), "suspend", true},
        {_("Toggle frontlight"), "toggle_frontlight", Device:hasFrontlight()},
        {_("Toggle accelerometer"), "toggle_gsensor", Device:canToggleGSensor()},
        {_("Toggle rotation"), "toggle_rotation", not self.is_docless},
        {_("Zoom to fit content width"), "zoom_contentwidth", not self.is_docless},
        {_("Zoom to fit content height"), "zoom_contentheight", not self.is_docless},
        {_("Zoom to fit page width"), "zoom_pagewidth", not self.is_docless},
        {_("Zoom to fit page height"), "zoom_pageheight", not self.is_docless},
        {_("Zoom to fit column"), "zoom_column", not self.is_docless},
        {_("Zoom to fit content"), "zoom_content", not self.is_docless},
        {_("Zoom to fit page"), "zoom_page", not self.is_docless},
    }
    local return_menu = {}
    -- add default action to the top of the submenu
    for __, entry in pairs(menu) do
        if entry[2] == default then
            local menu_entry_default = T(_("%1 (default)"), entry[1])
            table.insert(return_menu, self:createSubMenu(menu_entry_default, entry[2], ges, true))

            if not gesture_manager[ges] then
                gesture_manager[ges] = default
                G_reader_settings:saveSetting(self.ges_mode, gesture_manager)
            end
            break
        end
    end
    -- another elements
    for _, entry in pairs(menu) do
        if not entry[3] and gesture_manager[ges] == entry[2] then
            gesture_manager[ges] = "nothing"
            G_reader_settings:saveSetting(self.ges_mode, gesture_manager)
        end
        if entry[2] ~= default and entry[3] then
            local sep = entry[2] == "nothing" or entry[4] == true
            table.insert(return_menu, self:createSubMenu(entry[1], entry[2], ges, sep))
        end
    end
    return return_menu
end

local multiswipes = {
    "west east",
    "east west",
    "north south",
    "south north",
    "south west",
    "south east",
    "east south",
    "west south",
}

for k, v in pairs(custom_multiswipes_table) do
    table.insert(multiswipes, v)
end
{'fraction_non_alphanumeric': 0.10783775541323574, 'fraction_numerical': 0.0030497102775236353, 'mean_word_length': 2.4080232799833716, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 3, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 20, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2227045', 'n_tokens_mistral': 4813, 'n_tokens_neox': 4637, 'n_words': 1043}