From 945a1cc76f2d1a3968eaeaca4831da1f7b4872c9 Mon Sep 17 00:00:00 2001 From: poire-z Date: Sun, 5 Mar 2023 13:54:23 +0100 Subject: [PATCH] TextViewer: add support for long-press on text As done in DictQuickLookup (more event handlers are needed to cohabitate well with MovableContainer). By default, selected word or text is copied to clipboard. Also provide indexes to any long-press callback, as we'll need them for View HTML. --- frontend/ui/widget/textboxwidget.lua | 17 +++- frontend/ui/widget/textviewer.lua | 128 +++++++++++++++++++++++---- 2 files changed, 127 insertions(+), 18 deletions(-) diff --git a/frontend/ui/widget/textboxwidget.lua b/frontend/ui/widget/textboxwidget.lua index 65cca2fdf..355b7004b 100644 --- a/frontend/ui/widget/textboxwidget.lua +++ b/frontend/ui/widget/textboxwidget.lua @@ -2012,7 +2012,10 @@ function TextBoxWidget:onHoldReleaseText(callback, ges) logger.dbg("onHoldReleaseText (duration:", time.format_time(hold_duration), ") :", sel_start_idx, ">", sel_end_idx, "=", selected_text) - callback(selected_text, hold_duration) + -- We give index in the charlist (unicode chars), and provide a function + -- to convert these indices as in the utf8 text, to be used by caller + -- only if needed, as it may be expensive. + callback(selected_text, hold_duration, sel_start_idx, sel_end_idx, function(idx) return self:getSourceIndex(idx) end) return true end @@ -2029,7 +2032,7 @@ function TextBoxWidget:onHoldReleaseText(callback, ges) local selected_text = table.concat(self.charlist, "", sel_start_idx, sel_end_idx) logger.dbg("onHoldReleaseText (duration:", time.format_time(hold_duration), ") :", sel_start_idx, ">", sel_end_idx, "=", selected_text) - callback(selected_text, hold_duration) + callback(selected_text, hold_duration, sel_start_idx, sel_end_idx, function(idx) return self:getSourceIndex(idx) end) return true end @@ -2085,4 +2088,14 @@ function TextBoxWidget:_findWordEdge(x, y, side) return edge_idx end +function TextBoxWidget:getSourceIndex(char_idx) + if self._xtext then + local utf8 = self._xtext:getText(1, char_idx) + return #utf8 + else + local utf8 = table.concat(self.charlist, "", 1, char_idx) + return #utf8 + end +end + return TextBoxWidget diff --git a/frontend/ui/widget/textviewer.lua b/frontend/ui/widget/textviewer.lua index fa7055287..d89238f3d 100644 --- a/frontend/ui/widget/textviewer.lua +++ b/frontend/ui/widget/textviewer.lua @@ -86,37 +86,57 @@ function TextViewer:init() end if Device:isTouchDevice() then + local range = Geom:new{ + x = 0, y = 0, + w = Screen:getWidth(), + h = Screen:getHeight(), + } self.ges_events = { TapClose = { GestureRange:new{ ges = "tap", - range = Geom:new{ - x = 0, y = 0, - w = Screen:getWidth(), - h = Screen:getHeight(), - } + range = range, }, }, Swipe = { GestureRange:new{ ges = "swipe", - range = Geom:new{ - x = 0, y = 0, - w = Screen:getWidth(), - h = Screen:getHeight(), - } + range = range, }, }, MultiSwipe = { GestureRange:new{ ges = "multiswipe", - range = Geom:new{ - x = 0, y = 0, - w = Screen:getWidth(), - h = Screen:getHeight(), - } + range = range, + }, + }, + -- Allow selection of one or more words (see textboxwidget.lua): + HoldStartText = { + GestureRange:new{ + ges = "hold", + range = range, + }, + }, + HoldPanText = { + GestureRange:new{ + ges = "hold", + range = range, + }, + }, + HoldReleaseText = { + GestureRange:new{ + ges = "hold_release", + range = range, }, + -- callback function when HoldReleaseText is handled as args + args = function(text, hold_duration, start_idx, end_idx, to_source_index_func) + self:handleTextSelection(text, hold_duration, start_idx, end_idx, to_source_index_func) + end }, + -- These will be forwarded to MovableContainer after some checks + ForwardingTouch = { GestureRange:new{ ges = "touch", range = range, }, }, + ForwardingPan = { GestureRange:new{ ges = "pan", range = range, }, }, + ForwardingPanRelease = { GestureRange:new{ ges = "pan_release", range = range, }, }, } end @@ -271,7 +291,17 @@ function TextViewer:init() } } self.movable = MovableContainer:new{ - ignore_events = {"swipe"}, + -- We'll handle these events ourselves, and call appropriate + -- MovableContainer's methods when we didn't process the event + ignore_events = { + -- These have effects over the text widget, and may + -- or may not be processed by it + "swipe", "hold", "hold_release", "hold_pan", + -- These do not have direct effect over the text widget, + -- but may happen while selecting text: we need to check + -- a few things before forwarding them + "touch", "pan", "pan_release", + }, self.frame, } self[1] = WidgetContainer:new{ @@ -338,6 +368,58 @@ function TextViewer:onSwipe(arg, ges) return self.movable:onMovableSwipe(arg, ges) end +-- The following handlers are similar to the ones in DictQuickLookup: +-- we just forward to our MoveableContainer the events that our +-- TextBoxWidget has not handled with text selection. +function TextViewer:onHoldStartText(_, ges) + -- Forward Hold events not processed by TextBoxWidget event handler + -- to our MovableContainer + return self.movable:onMovableHold(_, ges) +end + +function TextViewer:onHoldPanText(_, ges) + -- Forward Hold events not processed by TextBoxWidget event handler + -- to our MovableContainer + -- We only forward it if we did forward the Touch + if self.movable._touch_pre_pan_was_inside then + return self.movable:onMovableHoldPan(arg, ges) + end +end + +function TextViewer:onHoldReleaseText(_, ges) + -- Forward Hold events not processed by TextBoxWidget event handler + -- to our MovableContainer + return self.movable:onMovableHoldRelease(_, ges) +end + +-- These 3 event processors are just used to forward these events +-- to our MovableContainer, under certain conditions, to avoid +-- unwanted moves of the window while we are selecting text in +-- the definition widget. +function TextViewer:onForwardingTouch(arg, ges) + -- This Touch may be used as the Hold we don't get (for example, + -- when we start our Hold on the bottom buttons) + if not ges.pos:intersectWith(self.textw.dimen) then + return self.movable:onMovableTouch(arg, ges) + else + -- Ensure this is unset, so we can use it to not forward HoldPan + self.movable._touch_pre_pan_was_inside = false + end +end + +function TextViewer:onForwardingPan(arg, ges) + -- We only forward it if we did forward the Touch or are currently moving + if self.movable._touch_pre_pan_was_inside or self.movable._moving then + return self.movable:onMovablePan(arg, ges) + end +end + +function TextViewer:onForwardingPanRelease(arg, ges) + -- We can forward onMovablePanRelease() does enough checks + return self.movable:onMovablePanRelease(arg, ges) +end + + function TextViewer:findDialog() local input_dialog input_dialog = InputDialog:new{ @@ -422,4 +504,18 @@ function TextViewer:findCallback(input_dialog) end end +function TextViewer:handleTextSelection(text, hold_duration, start_idx, end_idx, to_source_index_func) + if self.text_selection_callback then + self.text_selection_callback(text, hold_duration, start_idx, end_idx, to_source_index_func) + return + end + if Device:hasClipboard() then + Device.input.setClipboardText(text) + UIManager:show(Notification:new{ + text = start_idx == end_idx and _("Word copied to clipboard.") + or _("Selection copied to clipboard."), + }) + end +end + return TextViewer