2019-12-06 22:55:39 +01:00
local BD = require ( " ui/bidi " )
2024-08-30 13:10:43 -07:00
local BlitBuffer = require ( " ffi/blitbuffer " )
2017-07-28 22:39:54 +02:00
local ButtonDialog = require ( " ui/widget/buttondialog " )
2022-11-18 21:17:25 +02:00
local ConfirmBox = require ( " ui/widget/confirmbox " )
2014-10-30 19:42:18 +01:00
local Device = require ( " device " )
2013-10-22 17:11:31 +02:00
local Event = require ( " ui/event " )
2021-10-23 21:12:56 +11:00
local Geom = require ( " ui/geometry " )
2018-03-05 16:38:04 +01:00
local InfoMessage = require ( " ui/widget/infomessage " )
2017-07-28 22:39:54 +02:00
local InputContainer = require ( " ui/widget/container/inputcontainer " )
2019-10-25 11:25:26 -04:00
local Notification = require ( " ui/widget/notification " )
2024-05-03 09:08:57 +03:00
local RadioButtonWidget = require ( " ui/widget/radiobuttonwidget " )
2024-09-07 17:56:40 +03:00
local SpinWidget = require ( " ui/widget/spinwidget " )
2022-01-02 20:09:53 +02:00
local TextViewer = require ( " ui/widget/textviewer " )
2018-12-17 14:15:13 +01:00
local Translator = require ( " ui/translator " )
2013-10-22 17:11:31 +02:00
local UIManager = require ( " ui/uimanager " )
2024-11-21 07:54:23 +02:00
local ffiUtil = require ( " ffi/util " )
2016-12-29 00:10:38 -08:00
local logger = require ( " logger " )
2020-06-08 20:47:31 +02:00
local util = require ( " util " )
2022-03-12 19:16:50 +08:00
local Size = require ( " ui/size " )
2022-05-05 21:00:22 +02:00
local time = require ( " ui/time " )
2013-10-18 22:38:07 +02:00
local _ = require ( " gettext " )
2019-08-24 13:45:07 +02:00
local C_ = _.pgettext
2024-06-16 09:38:02 +01:00
local N_ = _.ngettext
2023-11-19 09:52:51 +02:00
local T = ffiUtil.template
2019-02-03 10:01:58 +01:00
local Screen = Device.screen
2013-04-24 06:59:52 +08:00
2024-08-30 13:10:43 -07:00
local ReaderHighlight = InputContainer : extend {
-- Matches what is available in BlitBuffer.HIGHLIGHT_COLORS
highlight_colors = {
{ _ ( " Red " ) , " red " } ,
{ _ ( " Orange " ) , " orange " } ,
{ _ ( " Yellow " ) , " yellow " } ,
{ _ ( " Green " ) , " green " } ,
{ _ ( " Olive " ) , " olive " } ,
{ _ ( " Cyan " ) , " cyan " } ,
{ _ ( " Blue " ) , " blue " } ,
{ _ ( " Purple " ) , " purple " } ,
{ _ ( " Gray " ) , " gray " } ,
2024-09-07 17:56:40 +03:00
} ,
2024-08-30 13:10:43 -07:00
}
2020-11-08 13:07:51 +01:00
local function inside_box ( pos , box )
if pos then
local x , y = pos.x , pos.y
if box.x <= x and box.y <= y
and box.x + box.w >= x
and box.y + box.h >= y then
return true
end
end
end
2013-04-24 06:59:52 +08:00
function ReaderHighlight : init ( )
2023-11-19 09:52:51 +02:00
self.screen_w = Screen : getWidth ( )
self.screen_h = Screen : getHeight ( )
2021-11-21 19:31:10 +02:00
self.select_mode = false -- extended highlighting
2022-03-12 19:16:50 +08:00
self._start_indicator_highlight = false
self._current_indicator_pos = nil
self._previous_indicator_pos = nil
2022-05-05 21:00:22 +02:00
self._last_indicator_move_args = { dx = 0 , dy = 0 , distance = 0 , time = time : now ( ) }
2024-09-07 17:56:40 +03:00
self._fallback_drawer = self.view . highlight.saved_drawer -- "lighten"
self._fallback_color = self.view . highlight.saved_color -- "yellow" or "gray"
2022-03-12 19:16:50 +08:00
2022-11-01 23:22:07 +01:00
self : registerKeyEvents ( )
2021-11-21 19:31:10 +02:00
2020-11-08 13:07:51 +01:00
self._highlight_buttons = {
-- highlight and add_note are for the document itself,
-- so we put them first.
2022-06-11 19:06:06 +02:00
[ " 01_select " ] = function ( this )
2020-11-08 13:07:51 +01:00
return {
2021-11-21 19:31:10 +02:00
text = _ ( " Select " ) ,
2022-06-11 19:06:06 +02:00
enabled = this.hold_pos ~= nil ,
2020-11-08 13:07:51 +01:00
callback = function ( )
2022-06-11 19:06:06 +02:00
this : startSelection ( )
this : onClose ( )
2020-11-08 13:07:51 +01:00
end ,
}
end ,
2022-06-11 19:06:06 +02:00
[ " 02_highlight " ] = function ( this )
2020-11-08 13:07:51 +01:00
return {
2021-11-21 19:31:10 +02:00
text = _ ( " Highlight " ) ,
2024-05-03 09:08:57 +03:00
enabled = this.hold_pos ~= nil ,
2020-11-08 13:07:51 +01:00
callback = function ( )
2022-06-11 11:08:40 +02:00
this : saveHighlight ( true )
2022-06-11 19:06:06 +02:00
this : onClose ( )
2020-11-08 13:07:51 +01:00
end ,
}
end ,
2022-06-11 19:06:06 +02:00
[ " 03_copy " ] = function ( this )
2020-11-08 13:07:51 +01:00
return {
text = C_ ( " Text " , " Copy " ) ,
enabled = Device : hasClipboard ( ) ,
callback = function ( )
2024-09-06 23:06:28 +02:00
Device.input . setClipboardText ( util.cleanupSelectedText ( this.selected_text . text ) )
2022-06-11 19:06:06 +02:00
this : onClose ( )
2021-05-31 21:36:12 +03:00
UIManager : show ( Notification : new {
text = _ ( " Selection copied to clipboard. " ) ,
} )
2020-11-08 13:07:51 +01:00
end ,
}
end ,
2022-06-11 19:06:06 +02:00
[ " 04_add_note " ] = function ( this )
2020-11-08 13:07:51 +01:00
return {
2022-11-02 13:58:12 -04:00
text = _ ( " Add note " ) ,
2024-05-03 09:08:57 +03:00
enabled = this.hold_pos ~= nil ,
2020-11-08 13:07:51 +01:00
callback = function ( )
2022-06-11 19:06:06 +02:00
this : addNote ( )
this : onClose ( )
2020-11-08 13:07:51 +01:00
end ,
}
end ,
-- then information lookup functions, putting on the left those that
-- depend on an internet connection.
2022-06-11 19:06:06 +02:00
[ " 05_wikipedia " ] = function ( this )
2020-11-08 13:07:51 +01:00
return {
text = _ ( " Wikipedia " ) ,
callback = function ( )
UIManager : scheduleIn ( 0.1 , function ( )
2022-06-11 19:06:06 +02:00
this : lookupWikipedia ( )
-- We don't call this:onClose(), we need the highlight
2020-11-08 13:07:51 +01:00
-- to still be there, as we may Highlight it from the
2021-03-21 13:57:18 +01:00
-- dict lookup widget.
2020-11-08 13:07:51 +01:00
end )
end ,
}
end ,
2024-12-05 18:53:59 +02:00
[ " 06_dictionary " ] = function ( this , index )
2020-11-08 13:07:51 +01:00
return {
text = _ ( " Dictionary " ) ,
callback = function ( )
2024-12-05 18:53:59 +02:00
this : lookupDict ( index )
2022-06-11 19:06:06 +02:00
-- We don't call this:onClose(), same reason as above
2020-11-08 13:07:51 +01:00
end ,
}
end ,
2024-05-03 09:08:57 +03:00
[ " 07_translate " ] = function ( this , index )
2020-11-08 13:07:51 +01:00
return {
text = _ ( " Translate " ) ,
callback = function ( )
2024-05-03 09:08:57 +03:00
this : translate ( index )
2022-06-11 19:06:06 +02:00
-- We don't call this:onClose(), so one can still see
2020-11-08 13:07:51 +01:00
-- the highlighted text when moving the translated
-- text window, and also if NetworkMgr:promptWifiOn()
-- is needed, so the user can just tap again on this
-- button and does not need to select the text again.
end ,
}
end ,
2021-11-21 19:31:10 +02:00
-- buttons 08-11 are conditional ones, so the number of buttons can be even or odd
-- let the Search button be the last, occasionally narrow or wide, less confusing
2022-06-11 19:06:06 +02:00
[ " 12_search " ] = function ( this )
2020-11-08 13:07:51 +01:00
return {
2021-11-21 19:31:10 +02:00
text = _ ( " Search " ) ,
2020-11-08 13:07:51 +01:00
callback = function ( )
2022-06-11 19:06:06 +02:00
this : onHighlightSearch ( )
-- We don't call this:onClose(), crengine will highlight
2021-11-21 19:31:10 +02:00
-- search matches on the current page, and self:clear()
-- would redraw and remove crengine native highlights
2020-11-08 13:07:51 +01:00
end ,
}
2021-11-21 19:31:10 +02:00
end ,
}
2020-11-08 13:07:51 +01:00
2021-11-21 19:31:10 +02:00
-- Android devices
2020-11-08 13:07:51 +01:00
if Device : canShareText ( ) then
2022-06-10 01:53:44 +02:00
local action = _ ( " Share Text " )
2022-06-11 19:06:06 +02:00
self : addToHighlightDialog ( " 08_share_text " , function ( this )
2020-11-08 13:07:51 +01:00
return {
2022-06-10 01:53:44 +02:00
text = action ,
2020-11-08 13:07:51 +01:00
callback = function ( )
2024-09-06 23:06:28 +02:00
local text = util.cleanupSelectedText ( this.selected_text . text )
2020-11-08 13:07:51 +01:00
-- call self:onClose() before calling the android framework
2022-06-11 19:06:06 +02:00
this : onClose ( )
2022-06-10 01:53:44 +02:00
Device : doShareText ( text , action )
2020-11-08 13:07:51 +01:00
end ,
}
end )
end
2021-11-21 19:31:10 +02:00
-- cre documents only
2021-12-21 14:57:19 +02:00
if not self.document . info.has_pages then
2022-06-11 19:06:06 +02:00
self : addToHighlightDialog ( " 09_view_html " , function ( this )
2021-11-21 19:31:10 +02:00
return {
text = _ ( " View HTML " ) ,
callback = function ( )
2022-06-11 19:06:06 +02:00
this : viewSelectionHTML ( )
2021-11-21 19:31:10 +02:00
end ,
}
end )
end
-- User hyphenation dict
2022-06-11 19:06:06 +02:00
self : addToHighlightDialog ( " 10_user_dict " , function ( this )
2021-02-06 16:59:09 +01:00
return {
2021-11-21 19:31:10 +02:00
text = _ ( " Hyphenate " ) ,
2021-02-06 16:59:09 +01:00
show_in_highlight_dialog_func = function ( )
2022-06-11 19:06:06 +02:00
return this.ui . userhyph and this.ui . userhyph : isAvailable ( )
and not this.selected_text . text : find ( " [ ,;-%. \n ] " )
2021-02-06 16:59:09 +01:00
end ,
callback = function ( )
2022-06-11 19:06:06 +02:00
this.ui . userhyph : modifyUserEntry ( this.selected_text . text )
this : onClose ( )
2021-02-06 16:59:09 +01:00
end ,
}
end )
2020-11-08 13:07:51 +01:00
2021-11-21 19:31:10 +02:00
-- Links
2022-06-11 19:06:06 +02:00
self : addToHighlightDialog ( " 11_follow_link " , function ( this )
2021-06-01 12:06:55 +02:00
return {
2021-11-21 19:31:10 +02:00
text = _ ( " Follow Link " ) ,
2021-06-01 12:06:55 +02:00
show_in_highlight_dialog_func = function ( )
2022-06-11 19:06:06 +02:00
return this.selected_link ~= nil
2021-06-01 12:06:55 +02:00
end ,
callback = function ( )
2022-06-11 19:06:06 +02:00
local link = this.selected_link . link or this.selected_link
this.ui . link : onGotoLink ( link )
this : onClose ( )
2021-06-01 12:06:55 +02:00
end ,
}
end )
2014-10-09 17:41:23 +08:00
self.ui : registerPostInitCallback ( function ( )
self.ui . menu : registerToMainMenu ( self )
end )
2022-10-30 23:05:04 +01:00
-- delegate gesture listener to readerui, NOP our own
self.ges_events = nil
2013-04-24 06:59:52 +08:00
end
2023-11-19 09:52:51 +02:00
function ReaderHighlight : onSetDimensions ( dimen )
self.screen_w , self.screen_h = dimen.w , dimen.h
end
2022-10-30 23:05:04 +01:00
function ReaderHighlight : onGesture ( ) end
2017-01-23 06:54:14 -08:00
2022-11-01 23:22:07 +01:00
function ReaderHighlight : registerKeyEvents ( )
2024-05-20 20:20:50 +01:00
if Device : hasDPad ( ) then
self.key_events . StopHighlightIndicator = { { Device.input . group.Back } , args = true } -- true: clear highlight selection
self.key_events . UpHighlightIndicator = { { " Up " } , event = " MoveHighlightIndicator " , args = { 0 , - 1 } }
self.key_events . DownHighlightIndicator = { { " Down " } , event = " MoveHighlightIndicator " , args = { 0 , 1 } }
-- let hasFewKeys device move the indicator left
self.key_events . LeftHighlightIndicator = { { " Left " } , event = " MoveHighlightIndicator " , args = { - 1 , 0 } }
self.key_events . RightHighlightIndicator = { { " Right " } , event = " MoveHighlightIndicator " , args = { 1 , 0 } }
self.key_events . HighlightPress = { { " Press " } }
end
if Device : hasKeyboard ( ) then
2022-11-01 00:17:25 +01:00
-- Used for text selection with dpad/keys
local QUICK_INDICATOR_MOVE = true
2022-11-01 23:22:07 +01:00
self.key_events . QuickUpHighlightIndicator = { { " Shift " , " Up " } , event = " MoveHighlightIndicator " , args = { 0 , - 1 , QUICK_INDICATOR_MOVE } }
self.key_events . QuickDownHighlightIndicator = { { " Shift " , " Down " } , event = " MoveHighlightIndicator " , args = { 0 , 1 , QUICK_INDICATOR_MOVE } }
self.key_events . QuickLeftHighlightIndicator = { { " Shift " , " Left " } , event = " MoveHighlightIndicator " , args = { - 1 , 0 , QUICK_INDICATOR_MOVE } }
self.key_events . QuickRightHighlightIndicator = { { " Shift " , " Right " } , event = " MoveHighlightIndicator " , args = { 1 , 0 , QUICK_INDICATOR_MOVE } }
self.key_events . StartHighlightIndicator = { { " H " } }
2024-05-26 21:25:54 +02:00
elseif Device : hasScreenKB ( ) then
2024-05-20 20:20:50 +01:00
local QUICK_INDICATOR_MOVE = true
self.key_events . QuickUpHighlightIndicator = { { " ScreenKB " , " Up " } , event = " MoveHighlightIndicator " , args = { 0 , - 1 , QUICK_INDICATOR_MOVE } }
self.key_events . QuickDownHighlightIndicator = { { " ScreenKB " , " Down " } , event = " MoveHighlightIndicator " , args = { 0 , 1 , QUICK_INDICATOR_MOVE } }
self.key_events . QuickLeftHighlightIndicator = { { " ScreenKB " , " Left " } , event = " MoveHighlightIndicator " , args = { - 1 , 0 , QUICK_INDICATOR_MOVE } }
self.key_events . QuickRightHighlightIndicator = { { " ScreenKB " , " Right " } , event = " MoveHighlightIndicator " , args = { 1 , 0 , QUICK_INDICATOR_MOVE } }
2022-11-01 00:17:25 +01:00
end
end
ReaderHighlight.onPhysicalKeyboardConnected = ReaderHighlight.registerKeyEvents
2022-10-30 23:05:04 +01:00
function ReaderHighlight : setupTouchZones ( )
2017-01-23 06:54:14 -08:00
if not Device : isTouchDevice ( ) then return end
2020-08-06 00:13:26 +02:00
local hold_pan_rate = G_reader_settings : readSetting ( " hold_pan_rate " )
if not hold_pan_rate then
hold_pan_rate = Screen.low_pan_rate and 5.0 or 30.0
end
2022-11-18 21:17:25 +02:00
local DTAP_ZONE_TOP_LEFT = G_defaults : readSetting ( " DTAP_ZONE_TOP_LEFT " )
2017-01-23 06:54:14 -08:00
self.ui : registerTouchZones ( {
2022-11-18 21:17:25 +02:00
{
id = " readerhighlight_tap_select_mode " ,
ges = " tap " ,
screen_zone = {
ratio_x = DTAP_ZONE_TOP_LEFT.x , ratio_y = DTAP_ZONE_TOP_LEFT.y ,
ratio_w = DTAP_ZONE_TOP_LEFT.w , ratio_h = DTAP_ZONE_TOP_LEFT.h ,
} ,
overrides = {
" readerhighlight_tap " ,
" tap_top_left_corner " ,
" readermenu_ext_tap " ,
" readermenu_tap " ,
" tap_forward " ,
" tap_backward " ,
} ,
handler = function ( ges ) return self : onTapSelectModeIcon ( ) end
} ,
2017-01-23 06:54:14 -08:00
{
id = " readerhighlight_tap " ,
ges = " tap " ,
screen_zone = {
ratio_x = 0 , ratio_y = 0 , ratio_w = 1 , ratio_h = 1 ,
} ,
2019-03-30 21:05:44 +01:00
overrides = {
2020-01-24 20:05:21 +01:00
-- Tap on existing highlights have priority over
-- everything but tap on links (as links can be
-- part of some highlighted text)
" tap_top_left_corner " ,
" tap_top_right_corner " ,
" tap_left_bottom_corner " ,
" tap_right_bottom_corner " ,
2020-12-03 17:33:54 +01:00
" readerfooter_tap " ,
" readerconfigmenu_ext_tap " ,
" readerconfigmenu_tap " ,
" readermenu_ext_tap " ,
" readermenu_tap " ,
" tap_forward " ,
" tap_backward " ,
2019-03-30 21:05:44 +01:00
} ,
2017-01-23 06:54:14 -08:00
handler = function ( ges ) return self : onTap ( nil , ges ) end
2014-03-13 21:52:43 +08:00
} ,
2017-01-23 06:54:14 -08:00
{
id = " readerhighlight_hold " ,
ges = " hold " ,
screen_zone = {
ratio_x = 0 , ratio_y = 0 , ratio_w = 1 , ratio_h = 1 ,
} ,
handler = function ( ges ) return self : onHold ( nil , ges ) end
2014-03-13 21:52:43 +08:00
} ,
2017-01-23 06:54:14 -08:00
{
id = " readerhighlight_hold_release " ,
ges = " hold_release " ,
screen_zone = {
ratio_x = 0 , ratio_y = 0 , ratio_w = 1 , ratio_h = 1 ,
} ,
handler = function ( ) return self : onHoldRelease ( ) end
2014-03-13 21:52:43 +08:00
} ,
2017-01-23 06:54:14 -08:00
{
id = " readerhighlight_hold_pan " ,
ges = " hold_pan " ,
2020-08-06 00:13:26 +02:00
rate = hold_pan_rate ,
2017-01-23 06:54:14 -08:00
screen_zone = {
ratio_x = 0 , ratio_y = 0 , ratio_w = 1 , ratio_h = 1 ,
} ,
handler = function ( ges ) return self : onHoldPan ( nil , ges ) end
2014-03-13 21:52:43 +08:00
} ,
2017-01-23 06:54:14 -08:00
} )
end
function ReaderHighlight : onReaderReady ( )
self : setupTouchZones ( )
2024-09-14 15:21:26 +03:00
if self.ui . paging and G_reader_settings : isTrue ( " highlight_write_into_pdf_notify " ) then
UIManager : show ( Notification : new {
text = T ( _ ( " Write highlights into PDF: %1 " ) , self.highlight_write_into_pdf and _ ( " on " ) or _ ( " off " ) ) ,
} )
end
2013-04-24 06:59:52 +08:00
end
2021-10-18 20:17:37 +03:00
local highlight_style = {
{ _ ( " Lighten " ) , " lighten " } ,
{ _ ( " Underline " ) , " underscore " } ,
2024-04-04 10:22:36 +01:00
{ _ ( " Strikethrough " ) , " strikeout " } ,
2021-10-18 20:17:37 +03:00
{ _ ( " Invert " ) , " invert " } ,
}
2022-08-03 16:51:57 +03:00
local note_mark = {
{ _ ( " None " ) , " none " } ,
{ _ ( " Underline " ) , " underline " } ,
{ _ ( " Side line " ) , " sideline " } ,
{ _ ( " Side mark " ) , " sidemark " } ,
}
2021-09-11 12:04:48 +03:00
local long_press_action = {
{ _ ( " Ask with popup dialog " ) , " ask " } ,
{ _ ( " Do nothing " ) , " nothing " } ,
{ _ ( " Highlight " ) , " highlight " } ,
2021-11-21 19:31:10 +02:00
{ _ ( " Select and highlight " ) , " select " } ,
2022-11-02 13:58:12 -04:00
{ _ ( " Add note " ) , " note " } ,
2021-09-11 12:04:48 +03:00
{ _ ( " Translate " ) , " translate " } ,
{ _ ( " Wikipedia " ) , " wikipedia " } ,
{ _ ( " Dictionary " ) , " dictionary " } ,
{ _ ( " Fulltext search " ) , " search " } ,
}
2023-11-19 09:52:51 +02:00
local highlight_dialog_position = {
{ _ ( " Top " ) , " top " } ,
{ _ ( " Center " ) , " center " } ,
{ _ ( " Bottom " ) , " bottom " } ,
2025-01-26 18:33:31 +00:00
{ _ ( " Highlight position " ) , " gesture " } ,
2023-11-19 09:52:51 +02:00
}
2017-03-04 14:46:38 +01:00
function ReaderHighlight : addToMainMenu ( menu_items )
2014-03-13 21:52:43 +08:00
-- insert table to main reader menu
2024-05-26 21:25:54 +02:00
if not Device : isTouchDevice ( ) and Device : hasDPad ( ) and not Device : useDPadAsActionKeys ( ) then
2022-03-12 19:16:50 +08:00
menu_items.start_content_selection = {
text = _ ( " Start content selection " ) ,
callback = function ( )
self : onStartHighlightIndicator ( )
end ,
}
end
2023-05-17 07:34:37 +03:00
-- main menu Typeset
2024-09-14 15:21:26 +03:00
local star = " ★ "
local hl_sub_item_table = { }
2017-03-04 14:46:38 +01:00
menu_items.highlight_options = {
2024-09-14 15:21:26 +03:00
text = _ ( " Highlights " ) ,
sub_item_table = hl_sub_item_table ,
2017-02-28 22:46:32 +01:00
}
2024-09-14 15:21:26 +03:00
for _ , v in ipairs ( highlight_style ) do
local style_text , style = unpack ( v )
table.insert ( hl_sub_item_table , {
2021-10-18 20:17:37 +03:00
text_func = function ( )
2024-09-14 15:21:26 +03:00
return style == ( G_reader_settings : readSetting ( " highlight_drawing_style " ) or self._fallback_drawer )
and style_text .. star or style_text
2021-10-18 20:17:37 +03:00
end ,
checked_func = function ( )
2024-09-14 15:21:26 +03:00
return self.view . highlight.saved_drawer == style
2021-10-18 20:17:37 +03:00
end ,
2022-02-01 21:56:28 +02:00
radio = true ,
2021-10-18 20:17:37 +03:00
callback = function ( )
2024-09-14 15:21:26 +03:00
self.view . highlight.saved_drawer = style
2021-10-18 20:17:37 +03:00
end ,
hold_callback = function ( touchmenu_instance )
2024-09-14 15:21:26 +03:00
G_reader_settings : saveSetting ( " highlight_drawing_style " , style )
touchmenu_instance : updateItems ( )
2021-10-18 20:17:37 +03:00
end ,
} )
end
2024-09-14 15:21:26 +03:00
hl_sub_item_table [ # highlight_style ] . separator = true
table.insert ( hl_sub_item_table , {
2021-10-18 20:17:37 +03:00
text_func = function ( )
2024-09-07 17:56:40 +03:00
local saved_color = self.view . highlight.saved_color
local text
for _ , v in ipairs ( self.highlight_colors ) do
2024-08-30 13:10:43 -07:00
if v [ 2 ] == saved_color then
2024-09-07 17:56:40 +03:00
text = v [ 1 ] : lower ( )
break
2024-08-30 13:10:43 -07:00
end
end
2024-09-07 17:56:40 +03:00
text = text or saved_color -- nonstandard color
local default_color = G_reader_settings : readSetting ( " highlight_color " ) or self._fallback_color
if saved_color == default_color then
2024-09-14 15:21:26 +03:00
text = text .. star
2024-09-07 17:56:40 +03:00
end
return T ( _ ( " Highlight color: %1 " ) , text )
2024-08-30 13:10:43 -07:00
end ,
enabled_func = function ( )
return self.view . highlight.saved_drawer ~= " invert "
end ,
2024-09-07 17:56:40 +03:00
keep_menu_open = true ,
callback = function ( touchmenu_instance ) -- set color for new highlights in this book
local function apply_color ( color )
self.view . highlight.saved_color = color
touchmenu_instance : updateItems ( )
2024-08-30 13:10:43 -07:00
end
2024-09-07 17:56:40 +03:00
self : showHighlightColorDialog ( apply_color )
2024-08-30 13:10:43 -07:00
end ,
2024-09-07 17:56:40 +03:00
hold_callback = function ( touchmenu_instance ) -- set color for new highlights in new books
2024-08-30 13:10:43 -07:00
G_reader_settings : saveSetting ( " highlight_color " , self.view . highlight.saved_color )
2024-09-07 17:56:40 +03:00
touchmenu_instance : updateItems ( )
2024-08-30 13:10:43 -07:00
end ,
} )
2024-09-14 15:21:26 +03:00
table.insert ( hl_sub_item_table , {
2024-08-30 13:10:43 -07:00
text_func = function ( )
return T ( _ ( " Gray highlight opacity: %1 " ) , G_reader_settings : readSetting ( " highlight_lighten_factor " , 0.2 ) )
2021-10-18 20:17:37 +03:00
end ,
enabled_func = function ( )
return self.view . highlight.saved_drawer == " lighten "
end ,
callback = function ( touchmenu_instance )
local spin_widget = SpinWidget : new {
2024-09-14 15:21:26 +03:00
value = G_reader_settings : readSetting ( " highlight_lighten_factor " ) ,
2021-10-18 20:17:37 +03:00
value_min = 0 ,
value_max = 1 ,
precision = " %.2f " ,
value_step = 0.1 ,
2024-09-14 15:21:26 +03:00
value_hold_step = 0.2 ,
2021-10-18 20:17:37 +03:00
default_value = 0.2 ,
keep_shown_on_apply = true ,
2024-08-30 13:10:43 -07:00
title_text = _ ( " Gray highlight opacity " ) ,
info_text = _ ( " The higher the value, the darker the gray. " ) ,
2021-10-18 20:17:37 +03:00
callback = function ( spin )
G_reader_settings : saveSetting ( " highlight_lighten_factor " , spin.value )
self.view . highlight.lighten_factor = spin.value
UIManager : setDirty ( self.dialog , " ui " )
2024-09-14 15:21:26 +03:00
touchmenu_instance : updateItems ( )
2024-05-03 09:08:57 +03:00
end ,
2021-10-18 20:17:37 +03:00
}
UIManager : show ( spin_widget )
end ,
} )
2024-11-21 07:54:23 +02:00
table.insert ( hl_sub_item_table , {
text_func = function ( )
return T ( _ ( " Highlight line height: %1 \xE2 \x80 \xAF % " ) , G_reader_settings : readSetting ( " highlight_height_pct " ) or 100 )
end ,
enabled_func = function ( )
return self.view . highlight.saved_drawer == " lighten " or self.view . highlight.saved_drawer == " invert "
end ,
callback = function ( touchmenu_instance )
local spin_widget = SpinWidget : new {
value = G_reader_settings : readSetting ( " highlight_height_pct " ) or 100 ,
value_min = 0 ,
value_max = 100 ,
value_step = 1 ,
value_hold_step = 10 ,
default_value = 100 ,
unit = " % " ,
keep_shown_on_apply = true ,
title_text = _ ( " Highlight line height " ) ,
info_text = _ ( " Percentage of the text line height. " ) ,
callback = function ( spin )
local value = spin.value ~= 100 and spin.value or nil
G_reader_settings : saveSetting ( " highlight_height_pct " , value )
UIManager : setDirty ( self.dialog , " ui " )
touchmenu_instance : updateItems ( )
end ,
}
UIManager : show ( spin_widget )
end ,
} )
2024-09-14 15:21:26 +03:00
table.insert ( hl_sub_item_table , {
2022-08-03 16:51:57 +03:00
text_func = function ( )
local notemark = self.view . highlight.note_mark or " none "
for __ , v in ipairs ( note_mark ) do
if v [ 2 ] == notemark then
2024-09-14 15:21:26 +03:00
return T ( _ ( " Note marker: %1 " ) , v [ 1 ] : lower ( ) )
2022-08-03 16:51:57 +03:00
end
end
end ,
callback = function ( touchmenu_instance )
local notemark = self.view . highlight.note_mark or " none "
local radio_buttons = { }
for _ , v in ipairs ( note_mark ) do
table.insert ( radio_buttons , {
{
text = v [ 1 ] ,
checked = v [ 2 ] == notemark ,
provider = v [ 2 ] ,
} ,
} )
end
2024-05-03 09:08:57 +03:00
UIManager : show ( RadioButtonWidget : new {
2022-08-03 16:51:57 +03:00
title_text = _ ( " Note marker " ) ,
width_factor = 0.5 ,
keep_shown_on_apply = true ,
radio_buttons = radio_buttons ,
callback = function ( radio )
if radio.provider == " none " then
self.view . highlight.note_mark = nil
G_reader_settings : delSetting ( " highlight_note_marker " )
else
self.view . highlight.note_mark = radio.provider
G_reader_settings : saveSetting ( " highlight_note_marker " , radio.provider )
end
self.view : setupNoteMarkPosition ( )
UIManager : setDirty ( self.dialog , " ui " )
2024-09-14 15:21:26 +03:00
touchmenu_instance : updateItems ( )
2022-08-03 16:51:57 +03:00
end ,
} )
end ,
2024-09-07 17:56:40 +03:00
separator = true ,
2022-08-03 16:51:57 +03:00
} )
2024-09-14 15:21:26 +03:00
table.insert ( hl_sub_item_table , {
2024-09-07 17:56:40 +03:00
text = _ ( " Apply current style and color to all highlights " ) ,
callback = function ( )
2024-08-30 13:10:43 -07:00
UIManager : show ( ConfirmBox : new {
text = _ ( " Are you sure you want to update all highlights? " ) ,
icon = " texture-box " ,
ok_callback = function ( )
local count = 0
for _ , item in ipairs ( self.ui . annotation.annotations ) do
if item.drawer then
count = count + 1
item.drawer = self.view . highlight.saved_drawer
item.color = self.view . highlight.saved_color
end
end
if count > 0 then
UIManager : setDirty ( self.dialog , " ui " )
UIManager : show ( Notification : new {
2024-09-07 17:56:40 +03:00
text = T ( N_ ( " Applied style and color to 1 highlight " ,
" Applied style and color to %1 highlights " , count ) , count ) ,
2024-08-30 13:10:43 -07:00
} )
end
2024-09-07 17:56:40 +03:00
end ,
2024-08-30 13:10:43 -07:00
} )
end ,
2024-09-14 15:21:26 +03:00
separator = self.ui . paging and true ,
2024-08-30 13:10:43 -07:00
} )
2024-09-14 15:21:26 +03:00
if self.document . is_pdf then
table.insert ( hl_sub_item_table , {
text_func = function ( )
local text = self.highlight_write_into_pdf and _ ( " on " ) or _ ( " off " )
if ( not self.highlight_write_into_pdf ) == ( not G_reader_settings : isTrue ( " highlight_write_into_pdf " ) ) then
text = text .. star
end
return T ( _ ( " Write highlights into PDF: %1 " ) , text )
end ,
sub_item_table = {
{
text_func = function ( )
local text = _ ( " On " )
return G_reader_settings : isTrue ( " highlight_write_into_pdf " ) and text .. star or text
end ,
checked_func = function ( )
return self.highlight_write_into_pdf
end ,
radio = true ,
callback = function ( )
if self.document : _checkIfWritable ( ) then
self.highlight_write_into_pdf = true
if G_reader_settings : readSetting ( " document_metadata_folder " ) == " hash " then
UIManager : show ( InfoMessage : new {
text = _ ( " Warning: Book metadata location is set to hash-based storage. Writing highlights into a PDF modifies the file which may change the partial hash, resulting in its metadata (e.g., highlights and progress) being unlinked and lost. " ) ,
icon = " notice-warning " ,
} )
end
else
UIManager : show ( InfoMessage : new {
text = _ ( [ [
Highlights in this document will be saved in the settings file , but they won ' t be written in the document itself because the file is in a read-only location.
If you wish your highlights to be saved in the document , just move it to a writable directory first . ] ] ) ,
} )
end
end ,
hold_callback = function ( touchmenu_instance )
G_reader_settings : makeTrue ( " highlight_write_into_pdf " )
touchmenu_instance : updateItems ( )
end ,
} ,
{
text_func = function ( )
local text = _ ( " Off " )
return G_reader_settings : hasNot ( " highlight_write_into_pdf " ) and text .. star or text
end ,
checked_func = function ( )
return not self.highlight_write_into_pdf
end ,
radio = true ,
callback = function ( )
self.highlight_write_into_pdf = false
end ,
hold_callback = function ( touchmenu_instance )
G_reader_settings : delSetting ( " highlight_write_into_pdf " )
touchmenu_instance : updateItems ( )
end ,
} ,
{
text = _ ( " Show reminder on book opening " ) ,
checked_func = function ( )
return G_reader_settings : isTrue ( " highlight_write_into_pdf_notify " )
end ,
callback = function ( )
G_reader_settings : flipNilOrFalse ( " highlight_write_into_pdf_notify " )
end ,
separator = true ,
} ,
{
2024-11-12 21:05:16 +01:00
text = _ ( " Write all highlights into PDF file " ) ,
2024-09-14 15:21:26 +03:00
enabled_func = function ( )
return self.highlight_write_into_pdf and self.ui . annotation : getNumberOfHighlightsAndNotes ( ) > 0
end ,
callback = function ( )
UIManager : show ( ConfirmBox : new {
2024-11-12 21:05:16 +01:00
text = _ ( " Are you sure you want to write all KOReader highlights into PDF file? " ) ,
2024-09-14 15:21:26 +03:00
icon = " texture-box " ,
ok_callback = function ( )
local count = 0
for _ , item in ipairs ( self.ui . annotation.annotations ) do
if item.drawer then
count = count + 1
self : writePdfAnnotation ( " delete " , item )
self : writePdfAnnotation ( " save " , item )
if item.note then
self : writePdfAnnotation ( " content " , item , item.note )
end
end
end
UIManager : show ( Notification : new {
2024-11-12 21:05:16 +01:00
text = T ( N_ ( " 1 highlight written into PDF file " ,
" %1 highlights written into PDF file " , count ) , count ) ,
2024-09-14 15:21:26 +03:00
} )
end ,
} )
end ,
} ,
{
2024-11-12 21:06:31 +01:00
text = _ ( " Delete all highlights from PDF file " ) ,
2024-09-14 15:21:26 +03:00
enabled_func = function ( )
return self.highlight_write_into_pdf and self.ui . annotation : getNumberOfHighlightsAndNotes ( ) > 0
end ,
callback = function ( )
UIManager : show ( ConfirmBox : new {
2024-11-12 21:06:31 +01:00
text = _ ( " Are you sure you want to delete all KOReader highlights from PDF file? " ) ,
2024-09-14 15:21:26 +03:00
icon = " texture-box " ,
ok_callback = function ( )
local count = 0
for _ , item in ipairs ( self.ui . annotation.annotations ) do
if item.drawer then
count = count + 1
self : writePdfAnnotation ( " delete " , item )
end
end
UIManager : show ( Notification : new {
2024-11-12 21:06:31 +01:00
text = T ( N_ ( " 1 highlight deleted from PDF file " ,
" %1 highlights deleted from PDF file " , count ) , count ) ,
2024-09-14 15:21:26 +03:00
} )
end ,
} )
end ,
} ,
} ,
} )
end
2024-05-03 09:08:57 +03:00
if self.ui . paging then
2020-09-24 15:17:37 +02:00
menu_items.panel_zoom_options = {
text = _ ( " Panel zoom (manga/comic) " ) ,
sub_item_table = self : genPanelZoomMenu ( ) ,
}
end
2022-01-02 15:01:08 +01:00
2023-05-17 07:34:37 +03:00
-- main menu Settings
2021-09-11 12:04:48 +03:00
menu_items.long_press = {
text = _ ( " Long-press on text " ) ,
sub_item_table = {
{
text = _ ( " Dictionary on single word selection " ) ,
checked_func = function ( )
return not self.view . highlight.disabled and G_reader_settings : nilOrFalse ( " highlight_action_on_single_word " )
end ,
enabled_func = function ( )
return not self.view . highlight.disabled
end ,
callback = function ( )
G_reader_settings : flipNilOrFalse ( " highlight_action_on_single_word " )
end ,
separator = true ,
} ,
} ,
}
2023-11-19 09:52:51 +02:00
-- actions
2022-11-02 13:58:12 -04:00
for i , v in ipairs ( long_press_action ) do
2021-09-11 12:04:48 +03:00
table.insert ( menu_items.long_press . sub_item_table , {
text = v [ 1 ] ,
checked_func = function ( )
return G_reader_settings : readSetting ( " default_highlight_action " , " ask " ) == v [ 2 ]
end ,
2022-02-01 21:56:28 +02:00
radio = true ,
2021-09-11 12:04:48 +03:00
callback = function ( )
2022-11-02 13:58:12 -04:00
self : onSetHighlightAction ( i , true ) -- no notification
2021-09-11 12:04:48 +03:00
end ,
} )
end
2023-11-19 09:52:51 +02:00
-- highlight dialog position
local sub_item_table = { }
for i , v in ipairs ( highlight_dialog_position ) do
table.insert ( sub_item_table , {
text = v [ 1 ] ,
checked_func = function ( )
return G_reader_settings : readSetting ( " highlight_dialog_position " , " center " ) == v [ 2 ]
end ,
callback = function ( )
G_reader_settings : saveSetting ( " highlight_dialog_position " , v [ 2 ] )
end ,
} )
end
table.insert ( menu_items.long_press . sub_item_table , {
text_func = function ( )
local position = G_reader_settings : readSetting ( " highlight_dialog_position " , " center " )
for __ , v in ipairs ( highlight_dialog_position ) do
if v [ 2 ] == position then
return T ( _ ( " Highlight dialog position: %1 " ) , v [ 1 ] : lower ( ) )
end
end
end ,
sub_item_table = sub_item_table ,
} )
2024-05-26 14:34:29 +01:00
if Device : isTouchDevice ( ) then
-- highlight very-long-press interval
table.insert ( menu_items.long_press . sub_item_table , {
text_func = function ( )
return T ( _ ( " Highlight very-long-press interval: %1 s " ) ,
G_reader_settings : readSetting ( " highlight_long_hold_threshold_s " , 3 ) )
end ,
keep_menu_open = true ,
callback = function ( touchmenu_instance )
local items = SpinWidget : new {
title_text = _ ( " Highlight very-long-press interval " ) ,
info_text = _ ( " If a long-press is not released in this interval, it is considered a very-long-press. On document text, single word selection will not be triggered. " ) ,
width = math.floor ( self.screen_w * 0.75 ) ,
value = G_reader_settings : readSetting ( " highlight_long_hold_threshold_s " , 3 ) ,
value_min = 2.5 ,
value_max = 20 ,
value_step = 0.1 ,
value_hold_step = 0.5 ,
unit = C_ ( " Time " , " s " ) ,
precision = " %0.1f " ,
ok_text = _ ( " Set interval " ) ,
default_value = 3 ,
callback = function ( spin )
G_reader_settings : saveSetting ( " highlight_long_hold_threshold_s " , spin.value )
if touchmenu_instance then touchmenu_instance : updateItems ( ) end
end ,
}
UIManager : show ( items )
end ,
} )
end
2024-01-09 13:28:59 +01:00
table.insert ( menu_items.long_press . sub_item_table , {
text = _ ( " Auto-scroll when selection reaches a corner " ) ,
help_text = _ ( [ [
Auto - scroll to show part of the previous page when your text selection reaches the top left corner , or of the next page when it reaches the bottom right corner .
Except when in two columns mode , where this is limited to showing only the previous or next column . ] ] ) ,
2024-05-20 20:20:50 +01:00
separator = true ,
2024-01-09 13:28:59 +01:00
checked_func = function ( )
if self.ui . paging then return false end
return not self.view . highlight.disabled and G_reader_settings : nilOrTrue ( " highlight_corner_scroll " )
end ,
enabled_func = function ( )
if self.ui . paging then return false end
return not self.view . highlight.disabled
end ,
callback = function ( )
G_reader_settings : flipNilOrTrue ( " highlight_corner_scroll " )
self.allow_corner_scroll = G_reader_settings : nilOrTrue ( " highlight_corner_scroll " )
end ,
} )
2024-10-18 21:59:31 +03:00
2024-05-20 20:20:50 +01:00
-- we allow user to select the rate at which the content selection tool moves through screen
if not Device : isTouchDevice ( ) and Device : hasDPad ( ) then
table.insert ( menu_items.long_press . sub_item_table , {
text_func = function ( )
2025-01-26 18:33:31 +00:00
return T ( _ ( " Crosshairs speed for text selection: %1 " ) , G_reader_settings : readSetting ( " highlight_non_touch_factor " ) or 4 )
2024-05-20 20:20:50 +01:00
end ,
callback = function ( touchmenu_instance )
2024-06-05 21:50:22 +01:00
local curr_val = G_reader_settings : readSetting ( " highlight_non_touch_factor " ) or 4
2024-05-20 20:20:50 +01:00
local spin_widget = SpinWidget : new {
value = curr_val ,
value_min = 0.25 ,
value_max = 5 ,
precision = " %.2f " ,
value_step = 0.25 ,
default_value = 4 ,
2025-01-26 18:33:31 +00:00
title_text = _ ( " Crosshairs speed " ) ,
info_text = _ ( " Select a decimal value from 0.25 to 5. A smaller value increases the travel distance of the crosshairs per keystroke. Font size and this value are inversely correlated, meaning a smaller font size requires a larger value and vice versa. " ) ,
2024-05-20 20:20:50 +01:00
callback = function ( spin )
G_reader_settings : saveSetting ( " highlight_non_touch_factor " , spin.value )
if touchmenu_instance then touchmenu_instance : updateItems ( ) end
end
}
UIManager : show ( spin_widget )
end ,
} )
table.insert ( menu_items.long_press . sub_item_table , {
2025-01-26 18:33:31 +00:00
text = _ ( " Increase crosshairs speed on consecutive keystrokes " ) ,
2024-05-20 20:20:50 +01:00
checked_func = function ( )
return G_reader_settings : nilOrTrue ( " highlight_non_touch_spedup " )
end ,
enabled_func = function ( )
return not self.view . highlight.disabled
end ,
callback = function ( )
G_reader_settings : flipNilOrTrue ( " highlight_non_touch_spedup " )
end ,
} )
table.insert ( menu_items.long_press . sub_item_table , {
text_func = function ( )
2024-06-05 21:50:22 +01:00
local highlight_non_touch_interval = G_reader_settings : readSetting ( " highlight_non_touch_interval " ) or 1
2025-01-27 12:11:23 +01:00
return T ( N_ ( " Interval for crosshairs speed increase: 1 second " , " Interval for crosshairs speed increase: %1 seconds " , highlight_non_touch_interval ) , highlight_non_touch_interval )
2024-05-20 20:20:50 +01:00
end ,
enabled_func = function ( )
return not self.view . highlight.disabled and G_reader_settings : nilOrTrue ( " highlight_non_touch_spedup " )
end ,
callback = function ( touchmenu_instance )
2024-06-05 21:50:22 +01:00
local curr_val = G_reader_settings : readSetting ( " highlight_non_touch_interval " ) or 1
2024-05-20 20:20:50 +01:00
local spin_widget = SpinWidget : new {
value = curr_val ,
value_min = 0.1 ,
value_max = 1 ,
precision = " %.1f " ,
value_step = 0.1 ,
default_value = 1 ,
title_text = _ ( " Time interval " ) ,
2025-01-26 18:33:31 +00:00
info_text = _ ( " Select a decimal value up to 1 second. This defines the time period within which multiple keystrokes will trigger an increase in the crosshairs speed. " ) ,
2024-05-20 20:20:50 +01:00
callback = function ( spin )
G_reader_settings : saveSetting ( " highlight_non_touch_interval " , spin.value )
if touchmenu_instance then touchmenu_instance : updateItems ( ) end
end
}
UIManager : show ( spin_widget )
end ,
} )
2024-01-09 13:28:59 +01:00
2025-01-26 18:33:31 +00:00
-- long_press settings are under the taps_and_gestures menu, which is not available for non-touch devices
-- Clone long_press settings, and change its label, making it much more meaningful for non-touch device users.
2024-05-11 20:25:50 +01:00
menu_items.selection_text = menu_items.long_press
2025-01-26 18:33:31 +00:00
menu_items.selection_text . text = _ ( " Text selection tools " )
2024-05-11 20:25:50 +01:00
menu_items.long_press = nil
2022-03-12 19:16:50 +08:00
end
2023-05-17 07:34:37 +03:00
-- main menu Search
menu_items.translation_settings = Translator : genSettingsMenu ( )
menu_items.translate_current_page = {
text = _ ( " Translate current page " ) ,
callback = function ( )
self : onTranslateCurrentPage ( )
end ,
}
2014-01-18 03:05:17 +08:00
end
2020-09-24 15:17:37 +02:00
function ReaderHighlight : genPanelZoomMenu ( )
return {
{
text = _ ( " Allow panel zoom " ) ,
checked_func = function ( )
return self.panel_zoom_enabled
end ,
callback = function ( )
self : onTogglePanelZoomSetting ( )
end ,
hold_callback = function ( )
local ext = util.getFileNameSuffix ( self.ui . document.file )
2020-12-24 09:07:27 +01:00
local curr_val = G_reader_settings : getSettingForExt ( " panel_zoom_enabled " , ext )
G_reader_settings : saveSettingForExt ( " panel_zoom_enabled " , not curr_val , ext )
end ,
separator = true ,
} ,
{
text = _ ( " Fall back to text selection " ) ,
checked_func = function ( )
return self.panel_zoom_fallback_to_text_selection
end ,
callback = function ( )
self : onToggleFallbackTextSelection ( )
end ,
hold_callback = function ( )
local ext = util.getFileNameSuffix ( self.ui . document.file )
G_reader_settings : saveSettingForExt ( " panel_zoom_fallback_to_text_selection " , self.panel_zoom_fallback_to_text_selection , ext )
2020-09-24 15:17:37 +02:00
end ,
separator = true ,
} ,
}
end
2017-09-20 17:35:30 +02:00
-- Returns a unique id, that can be provided on delayed call to :clear(id)
-- to ensure current highlight has not already been cleared, and that we
-- are not going to clear a new highlight
function ReaderHighlight : getClearId ( )
The great Input/GestureDetector/TimeVal spring cleanup (a.k.a., a saner main loop) (#7415)
* ReaderDictionary: Port delay computations to TimeVal
* ReaderHighlight: Port delay computations to TimeVal
* ReaderView: Port delay computations to TimeVal
* Android: Reset gesture detection state on APP_CMD_TERM_WINDOW.
This prevents potentially being stuck in bogus gesture states when switching apps.
* GestureDetector:
* Port delay computations to TimeVal
* Fixed delay computations to handle time warps (large and negative deltas).
* Simplified timed callback handling to invalidate timers much earlier, preventing accumulating useless timers that no longer have any chance of ever detecting a gesture.
* Fixed state clearing to handle the actual effective slots, instead of hard-coding slot 0 & slot 1.
* Simplified timed callback handling in general, and added support for a timerfd backend for better performance and accuracy.
* The improved timed callback handling allows us to detect and honor (as much as possible) the three possible clock sources usable by Linux evdev events.
The only case where synthetic timestamps are used (and that only to handle timed callbacks) is limited to non-timerfd platforms where input events use
a clock source that is *NOT* MONOTONIC.
AFAICT, that's pretty much... PocketBook, and that's it?
* Input:
* Use the <linux/input.h> FFI module instead of re-declaring every constant
* Fixed (verbose) debug logging of input events to actually translate said constants properly.
* Completely reset gesture detection state on suspend. This should prevent bogus gesture detection on resume.
* Refactored the waitEvent loop to make it easier to comprehend (hopefully) and much more efficient.
Of specific note, it no longer does a crazy select spam every 100µs, instead computing and relying on sane timeouts,
as afforded by switching the UI event/input loop to the MONOTONIC time base, and the refactored timed callbacks in GestureDetector.
* reMarkable: Stopped enforcing synthetic timestamps on input events, as it should no longer be necessary.
* TimeVal:
* Refactored and simplified, especially as far as metamethods are concerned (based on <bsd/sys/time.h>).
* Added a host of new methods to query the various POSIX clock sources, and made :now default to MONOTONIC.
* Removed the debug guard in __sub, as time going backwards can be a perfectly normal occurrence.
* New methods:
* Clock sources: :realtime, :monotonic, :monotonic_coarse, :realtime_coarse, :boottime
* Utility: :tonumber, :tousecs, :tomsecs, :fromnumber, :isPositive, :isZero
* UIManager:
* Ported event loop & scheduling to TimeVal, and switched to the MONOTONIC time base.
This ensures reliable and consistent scheduling, as time is ensured never to go backwards.
* Added a :getTime() method, that returns a cached TimeVal:now(), updated at the top of every UI frame.
It's used throughout the codebase to cadge a syscall in circumstances where we are guaranteed that a syscall would return a mostly identical value,
because very few time has passed.
The only code left that does live syscalls does it because it's actually necessary for accuracy,
and the only code left that does that in a REALTIME time base is code that *actually* deals with calendar time (e.g., Statistics).
* DictQuickLookup: Port delay computations to TimeVal
* FootNoteWidget: Port delay computations to TimeVal
* HTMLBoxWidget: Port delay computations to TimeVal
* Notification: Port delay computations to TimeVal
* TextBoxWidget: Port delay computations to TimeVal
* AutoSuspend: Port to TimeVal
* AutoTurn:
* Fix it so that settings are actually honored.
* Port to TimeVal
* BackgroundRunner: Port to TimeVal
* Calibre: Port benchmarking code to TimeVal
* BookInfoManager: Removed unnecessary yield in the metadata extraction subprocess now that subprocesses get scheduled properly.
* All in all, these changes reduced the CPU cost of a single tap by a factor of ten (!), and got rid of an insane amount of weird poll/wakeup cycles that must have been hell on CPU schedulers and batteries..
2021-03-30 02:57:59 +02:00
self.clear_id = UIManager : getTime ( ) -- can act as a unique id
2017-09-20 17:35:30 +02:00
return self.clear_id
end
function ReaderHighlight : clear ( clear_id )
if clear_id then -- should be provided by delayed call to clear()
if clear_id ~= self.clear_id then
The great Input/GestureDetector/TimeVal spring cleanup (a.k.a., a saner main loop) (#7415)
* ReaderDictionary: Port delay computations to TimeVal
* ReaderHighlight: Port delay computations to TimeVal
* ReaderView: Port delay computations to TimeVal
* Android: Reset gesture detection state on APP_CMD_TERM_WINDOW.
This prevents potentially being stuck in bogus gesture states when switching apps.
* GestureDetector:
* Port delay computations to TimeVal
* Fixed delay computations to handle time warps (large and negative deltas).
* Simplified timed callback handling to invalidate timers much earlier, preventing accumulating useless timers that no longer have any chance of ever detecting a gesture.
* Fixed state clearing to handle the actual effective slots, instead of hard-coding slot 0 & slot 1.
* Simplified timed callback handling in general, and added support for a timerfd backend for better performance and accuracy.
* The improved timed callback handling allows us to detect and honor (as much as possible) the three possible clock sources usable by Linux evdev events.
The only case where synthetic timestamps are used (and that only to handle timed callbacks) is limited to non-timerfd platforms where input events use
a clock source that is *NOT* MONOTONIC.
AFAICT, that's pretty much... PocketBook, and that's it?
* Input:
* Use the <linux/input.h> FFI module instead of re-declaring every constant
* Fixed (verbose) debug logging of input events to actually translate said constants properly.
* Completely reset gesture detection state on suspend. This should prevent bogus gesture detection on resume.
* Refactored the waitEvent loop to make it easier to comprehend (hopefully) and much more efficient.
Of specific note, it no longer does a crazy select spam every 100µs, instead computing and relying on sane timeouts,
as afforded by switching the UI event/input loop to the MONOTONIC time base, and the refactored timed callbacks in GestureDetector.
* reMarkable: Stopped enforcing synthetic timestamps on input events, as it should no longer be necessary.
* TimeVal:
* Refactored and simplified, especially as far as metamethods are concerned (based on <bsd/sys/time.h>).
* Added a host of new methods to query the various POSIX clock sources, and made :now default to MONOTONIC.
* Removed the debug guard in __sub, as time going backwards can be a perfectly normal occurrence.
* New methods:
* Clock sources: :realtime, :monotonic, :monotonic_coarse, :realtime_coarse, :boottime
* Utility: :tonumber, :tousecs, :tomsecs, :fromnumber, :isPositive, :isZero
* UIManager:
* Ported event loop & scheduling to TimeVal, and switched to the MONOTONIC time base.
This ensures reliable and consistent scheduling, as time is ensured never to go backwards.
* Added a :getTime() method, that returns a cached TimeVal:now(), updated at the top of every UI frame.
It's used throughout the codebase to cadge a syscall in circumstances where we are guaranteed that a syscall would return a mostly identical value,
because very few time has passed.
The only code left that does live syscalls does it because it's actually necessary for accuracy,
and the only code left that does that in a REALTIME time base is code that *actually* deals with calendar time (e.g., Statistics).
* DictQuickLookup: Port delay computations to TimeVal
* FootNoteWidget: Port delay computations to TimeVal
* HTMLBoxWidget: Port delay computations to TimeVal
* Notification: Port delay computations to TimeVal
* TextBoxWidget: Port delay computations to TimeVal
* AutoSuspend: Port to TimeVal
* AutoTurn:
* Fix it so that settings are actually honored.
* Port to TimeVal
* BackgroundRunner: Port to TimeVal
* Calibre: Port benchmarking code to TimeVal
* BookInfoManager: Removed unnecessary yield in the metadata extraction subprocess now that subprocesses get scheduled properly.
* All in all, these changes reduced the CPU cost of a single tap by a factor of ten (!), and got rid of an insane amount of weird poll/wakeup cycles that must have been hell on CPU schedulers and batteries..
2021-03-30 02:57:59 +02:00
-- if clear_id is no longer valid, highlight has already been
2017-09-20 17:35:30 +02:00
-- cleared since this clear_id was given
return
end
end
self.clear_id = nil -- invalidate id
2021-05-19 22:57:46 +02:00
if not self.ui . document then
-- might happen if scheduled and run after document is closed
return
end
2022-12-18 18:04:00 +02:00
if self.ui . paging then
2014-11-05 12:28:11 +08:00
self.view . highlight.temp = { }
else
self.ui . document : clearSelection ( )
end
2019-02-03 10:01:58 +01:00
if self.restore_page_mode_func then
self.restore_page_mode_func ( )
self.restore_page_mode_func = nil
end
2021-10-23 21:12:56 +11:00
self.is_word_selection = false
2019-02-03 10:01:58 +01:00
self.selected_text_start_xpointer = nil
2024-05-03 09:08:57 +03:00
self.gest_pos = nil
2014-03-13 21:52:43 +08:00
if self.hold_pos then
self.hold_pos = nil
2014-07-02 17:46:17 +08:00
self.selected_text = nil
2017-06-19 00:08:57 +08:00
UIManager : setDirty ( self.dialog , " ui " )
2014-03-13 21:52:43 +08:00
return true
end
2014-08-26 20:29:53 +08:00
end
2014-11-05 12:28:11 +08:00
function ReaderHighlight : onClearHighlight ( )
self : clear ( )
return true
end
2022-11-18 21:17:25 +02:00
function ReaderHighlight : onTapSelectModeIcon ( )
if not self.select_mode then return end
UIManager : show ( ConfirmBox : new {
text = _ ( " You are currently in SELECT mode. \n To finish highlighting, long press where the highlight should end and press the HIGHLIGHT button. \n You can also exit select mode by tapping on the start of the highlight. " ) ,
2023-02-19 21:49:09 +02:00
icon = " texture-box " ,
2022-11-18 21:17:25 +02:00
ok_text = _ ( " Exit select mode " ) ,
cancel_text = _ ( " Close " ) ,
ok_callback = function ( )
self.select_mode = false
2024-05-03 09:08:57 +03:00
self : deleteHighlight ( self.highlight_idx )
end ,
2022-11-18 21:17:25 +02:00
} )
return true
end
2016-03-27 17:18:25 -07:00
function ReaderHighlight : onTap ( _ , ges )
2020-11-08 02:18:50 +01:00
-- We only actually need to clear if we have something to clear in the first place.
-- (We mainly want to avoid CRe's clearSelection,
-- which may incur a redraw as it invalidates the cache, c.f., #6854)
-- ReaderHighlight:clear can only return true if self.hold_pos was set anyway.
local cleared = self.hold_pos and self : clear ( )
-- We only care about potential taps on existing highlights, not on taps that closed a highlight menu.
2024-11-23 14:05:05 +02:00
if not cleared and ges and # self.view . highlight.visible_boxes > 0 then
local pos = self.view : screenToPageTransform ( ges.pos )
local highlights_tapped = { }
for _ , box in ipairs ( self.view . highlight.visible_boxes ) do
if inside_box ( pos , box.rect ) then
if self.select_mode then
if box.index == self.highlight_idx then
-- tap on the first fragment: abort select mode, clear highlight
self.select_mode = false
self : deleteHighlight ( box.index )
return true
2014-03-13 21:52:43 +08:00
end
2024-11-23 14:05:05 +02:00
else
table.insert ( highlights_tapped , box.index )
2014-03-13 21:52:43 +08:00
end
end
end
2024-11-23 14:05:05 +02:00
if # highlights_tapped > 0 then
return self : showChooseHighlightDialog ( highlights_tapped )
2014-03-13 21:52:43 +08:00
end
end
2013-04-24 06:59:52 +08:00
end
2024-12-05 18:53:59 +02:00
function ReaderHighlight : getHighlightVisibleBoxes ( index )
local boxes = { }
for _ , box in ipairs ( self.view . highlight.visible_boxes ) do
if box.index == index then
table.insert ( boxes , box.rect )
end
end
if next ( boxes ) ~= nil then
return boxes
end
end
2024-05-03 09:08:57 +03:00
function ReaderHighlight : updateHighlight ( index , side , direction , move_by_char )
local highlight = self.ui . annotation.annotations [ index ]
local highlight_before = util.tableDeepCopy ( highlight )
2019-02-15 18:42:27 -05:00
local highlight_beginning = highlight.pos0
local highlight_end = highlight.pos1
if side == 0 then -- we move pos0
2019-02-17 16:15:32 +01:00
local updated_highlight_beginning
2019-02-15 18:42:27 -05:00
if direction == 1 then -- move highlight to the right
if move_by_char then
updated_highlight_beginning = self.ui . document : getNextVisibleChar ( highlight_beginning )
else
updated_highlight_beginning = self.ui . document : getNextVisibleWordStart ( highlight_beginning )
end
else -- move highlight to the left
if move_by_char then
updated_highlight_beginning = self.ui . document : getPrevVisibleChar ( highlight_beginning )
else
updated_highlight_beginning = self.ui . document : getPrevVisibleWordStart ( highlight_beginning )
end
2019-02-17 16:15:32 +01:00
end
if updated_highlight_beginning then
local order = self.ui . document : compareXPointers ( updated_highlight_beginning , highlight_end )
if order and order > 0 then -- only if beginning did not go past end
2024-05-03 09:08:57 +03:00
highlight.pos0 = updated_highlight_beginning
highlight.page = updated_highlight_beginning
highlight.chapter = self.ui . toc : getTocTitleByPage ( updated_highlight_beginning )
highlight.pageno = self.document : getPageFromXPointer ( updated_highlight_beginning )
2019-02-15 18:42:27 -05:00
end
end
else -- we move pos1
2019-02-17 16:15:32 +01:00
local updated_highlight_end
2019-02-15 18:42:27 -05:00
if direction == 1 then -- move highlight to the right
if move_by_char then
updated_highlight_end = self.ui . document : getNextVisibleChar ( highlight_end )
else
updated_highlight_end = self.ui . document : getNextVisibleWordEnd ( highlight_end )
end
else -- move highlight to the left
if move_by_char then
updated_highlight_end = self.ui . document : getPrevVisibleChar ( highlight_end )
else
updated_highlight_end = self.ui . document : getPrevVisibleWordEnd ( highlight_end )
end
2019-02-17 16:15:32 +01:00
end
if updated_highlight_end then
local order = self.ui . document : compareXPointers ( highlight_beginning , updated_highlight_end )
if order and order > 0 then -- only if end did not go back past beginning
2024-05-03 09:08:57 +03:00
highlight.pos1 = updated_highlight_end
2019-02-15 18:42:27 -05:00
end
end
end
2024-05-03 09:08:57 +03:00
local new_beginning = highlight.pos0
local new_end = highlight.pos1
2019-02-15 18:42:27 -05:00
local new_text = self.ui . document : getTextFromXPointers ( new_beginning , new_end )
2024-09-06 23:06:28 +02:00
highlight.text = util.cleanupSelectedText ( new_text )
2024-05-03 09:08:57 +03:00
self.ui : handleEvent ( Event : new ( " AnnotationsModified " , { highlight , highlight_before } ) )
2020-03-20 21:58:58 +01:00
if side == 0 then
-- Ensure we show the page with the new beginning of highlight
if not self.ui . document : isXPointerInCurrentPage ( new_beginning ) then
self.ui : handleEvent ( Event : new ( " GotoXPointer " , new_beginning ) )
end
else
-- Ensure we show the page with the new end of highlight
if not self.ui . document : isXPointerInCurrentPage ( new_end ) then
if self.view . view_mode == " page " then
self.ui : handleEvent ( Event : new ( " GotoXPointer " , new_end ) )
else
-- Not easy to get the y that would show the whole line
-- containing new_end. So, we scroll so that new_end
-- is at 2/3 of the screen.
local end_y = self.ui . document : getPosFromXPointer ( new_end )
2023-11-19 09:52:51 +02:00
local top_y = end_y - math.floor ( self.screen_h * 2 / 3 )
2020-03-20 21:58:58 +01:00
self.ui . rolling : _gotoPos ( top_y )
end
end
end
2019-02-15 18:42:27 -05:00
UIManager : setDirty ( self.dialog , " ui " )
end
2023-05-28 08:05:48 +03:00
function ReaderHighlight : showChooseHighlightDialog ( highlights )
if # highlights == 1 then
2024-05-03 09:08:57 +03:00
self : showHighlightNoteOrDialog ( highlights [ 1 ] )
2023-05-28 08:05:48 +03:00
else -- overlapped highlights
local dialog
local buttons = { }
2024-05-03 09:08:57 +03:00
for i , index in ipairs ( highlights ) do
local item = self.ui . annotation.annotations [ index ]
2023-05-28 08:05:48 +03:00
buttons [ i ] = { {
2024-05-03 09:08:57 +03:00
text = ( item.note and self.ui . bookmark.display_prefix [ " note " ]
or self.ui . bookmark.display_prefix [ " highlight " ] ) .. item.text ,
2023-05-28 08:05:48 +03:00
align = " left " ,
avoid_text_truncation = false ,
font_face = " smallinfofont " ,
font_size = 22 ,
font_bold = false ,
callback = function ( )
UIManager : close ( dialog )
2024-05-03 09:08:57 +03:00
self : showHighlightNoteOrDialog ( index )
2023-05-28 08:05:48 +03:00
end ,
} }
end
dialog = ButtonDialog : new {
buttons = buttons ,
}
UIManager : show ( dialog )
2022-11-18 21:17:25 +02:00
end
2023-05-28 08:05:48 +03:00
return true
end
2024-05-03 09:08:57 +03:00
function ReaderHighlight : showHighlightNoteOrDialog ( index )
local bookmark_note = self.ui . annotation.annotations [ index ] . note
2022-01-02 20:09:53 +02:00
if bookmark_note then
local textviewer
textviewer = TextViewer : new {
title = _ ( " Note " ) ,
2023-12-14 07:50:54 +02:00
show_menu = false ,
2022-01-02 20:09:53 +02:00
text = bookmark_note ,
2023-11-19 09:52:51 +02:00
width = math.floor ( math.min ( self.screen_w , self.screen_h ) * 0.8 ) ,
height = math.floor ( math.max ( self.screen_w , self.screen_h ) * 0.4 ) ,
2024-12-05 18:53:59 +02:00
anchor = function ( )
return self : _getDialogAnchor ( textviewer , index )
end ,
2022-01-02 20:09:53 +02:00
buttons_table = {
2024-11-21 07:54:23 +02:00
{
{
text = _ ( " Delete note " ) ,
callback = function ( )
UIManager : close ( textviewer )
local annotation = self.ui . annotation.annotations [ index ]
annotation.note = nil
self.ui : handleEvent ( Event : new ( " AnnotationsModified " ,
{ annotation , nb_highlights_added = 1 , nb_notes_added = - 1 } ) )
self : writePdfAnnotation ( " content " , annotation , nil )
if self.view . highlight.note_mark then -- refresh note marker
UIManager : setDirty ( self.dialog , " ui " )
end
end ,
} ,
{
text = _ ( " Edit note " ) ,
callback = function ( )
UIManager : close ( textviewer )
self : editNote ( index )
end ,
} ,
} ,
2022-01-02 20:09:53 +02:00
{
{
2023-10-29 12:33:29 +02:00
text = _ ( " Delete highlight " ) ,
2022-01-02 20:09:53 +02:00
callback = function ( )
UIManager : close ( textviewer )
2024-05-03 09:08:57 +03:00
self : deleteHighlight ( index )
2022-01-02 20:09:53 +02:00
end ,
} ,
{
2024-11-21 07:54:23 +02:00
text = _ ( " Highlight menu " ) ,
2022-01-02 20:09:53 +02:00
callback = function ( )
UIManager : close ( textviewer )
2024-05-03 09:08:57 +03:00
self : onShowHighlightDialog ( index )
2022-01-02 20:09:53 +02:00
end ,
} ,
} ,
} ,
}
UIManager : show ( textviewer )
else
2024-05-03 09:08:57 +03:00
self : onShowHighlightDialog ( index )
2022-01-02 20:09:53 +02:00
end
end
2024-05-03 09:08:57 +03:00
function ReaderHighlight : onShowHighlightDialog ( index )
local item = self.ui . annotation.annotations [ index ]
2019-02-15 18:42:27 -05:00
local buttons = {
{
2014-03-13 21:52:43 +08:00
{
2024-11-20 20:31:20 +01:00
text = " \u{F48E} " , -- Trash can (icon to prevent confusion of Delete/Details buttons)
2019-02-15 18:42:27 -05:00
callback = function ( )
2024-05-03 09:08:57 +03:00
self : deleteHighlight ( index )
2022-11-02 13:58:12 -04:00
UIManager : close ( self.edit_highlight_dialog )
2021-03-21 13:57:18 +01:00
self.edit_highlight_dialog = nil
2019-02-15 18:42:27 -05:00
end ,
2014-03-13 21:52:43 +08:00
} ,
2019-02-15 18:42:27 -05:00
{
2021-12-04 12:51:38 +01:00
text = C_ ( " Highlight " , " Style " ) ,
2021-10-18 20:17:37 +03:00
callback = function ( )
2024-05-03 09:08:57 +03:00
self : editHighlightStyle ( index )
2021-10-18 20:17:37 +03:00
UIManager : close ( self.edit_highlight_dialog )
self.edit_highlight_dialog = nil
end ,
} ,
{
2024-09-07 17:56:40 +03:00
text = C_ ( " Highlight " , " Color " ) ,
enabled = item.drawer ~= " invert " ,
callback = function ( )
self : editHighlightColor ( index )
UIManager : close ( self.edit_highlight_dialog )
self.edit_highlight_dialog = nil
end ,
} ,
{
text = _ ( " Note " ) ,
2019-02-15 18:42:27 -05:00
callback = function ( )
2024-11-21 07:54:23 +02:00
self : editNote ( index )
2019-02-15 18:42:27 -05:00
UIManager : close ( self.edit_highlight_dialog )
2021-03-21 13:57:18 +01:00
self.edit_highlight_dialog = nil
2019-02-15 18:42:27 -05:00
end ,
} ,
2024-09-07 17:56:40 +03:00
{
text = _ ( " Details " ) ,
callback = function ( )
self.ui . bookmark : showBookmarkDetails ( index )
UIManager : close ( self.edit_highlight_dialog )
self.edit_highlight_dialog = nil
end ,
} ,
2019-10-06 23:47:53 +02:00
{
2022-05-29 22:32:16 +02:00
text = " … " ,
2019-10-06 23:47:53 +02:00
callback = function ( )
2024-05-03 09:08:57 +03:00
self.selected_text = util.tableDeepCopy ( item )
self : onShowHighlightMenu ( index )
2019-10-06 23:47:53 +02:00
UIManager : close ( self.edit_highlight_dialog )
2021-03-21 13:57:18 +01:00
self.edit_highlight_dialog = nil
2019-10-06 23:47:53 +02:00
end ,
} ,
2024-08-30 13:10:43 -07:00
} ,
2019-02-15 18:42:27 -05:00
}
2021-12-10 11:06:16 +02:00
if self.ui . rolling then
2024-05-03 09:08:57 +03:00
local enabled = not item.text_edited
2021-05-20 23:04:57 +02:00
local start_prev = " ◁▒▒ "
local start_next = " ▷▒▒ "
local end_prev = " ▒▒◁ "
local end_next = " ▒▒▷ "
2019-12-06 22:55:39 +01:00
if BD.mirroredUILayout ( ) then
2021-05-20 23:04:57 +02:00
-- BiDi will mirror the arrows, and this just works
start_prev , start_next = start_next , start_prev
end_prev , end_next = end_next , end_prev
2019-12-06 22:55:39 +01:00
end
2019-02-15 18:42:27 -05:00
table.insert ( buttons , {
{
2019-12-06 22:55:39 +01:00
text = start_prev ,
2024-03-09 17:03:43 +02:00
enabled = enabled ,
2019-02-15 18:42:27 -05:00
callback = function ( )
2024-05-03 09:08:57 +03:00
self : updateHighlight ( index , 0 , - 1 , false )
2019-02-15 18:42:27 -05:00
end ,
hold_callback = function ( )
2024-05-03 09:08:57 +03:00
self : updateHighlight ( index , 0 , - 1 , true )
2019-02-15 18:42:27 -05:00
return true
2024-09-07 17:56:40 +03:00
end ,
2019-02-15 18:42:27 -05:00
} ,
{
2019-12-06 22:55:39 +01:00
text = start_next ,
2024-03-09 17:03:43 +02:00
enabled = enabled ,
2019-02-15 18:42:27 -05:00
callback = function ( )
2024-05-03 09:08:57 +03:00
self : updateHighlight ( index , 0 , 1 , false )
2019-02-15 18:42:27 -05:00
end ,
hold_callback = function ( )
2024-05-03 09:08:57 +03:00
self : updateHighlight ( index , 0 , 1 , true )
2019-02-15 18:42:27 -05:00
return true
2024-09-07 17:56:40 +03:00
end ,
2019-02-15 18:42:27 -05:00
} ,
{
2019-12-06 22:55:39 +01:00
text = end_prev ,
2024-03-09 17:03:43 +02:00
enabled = enabled ,
2019-02-15 18:42:27 -05:00
callback = function ( )
2024-05-03 09:08:57 +03:00
self : updateHighlight ( index , 1 , - 1 , false )
2019-02-15 18:42:27 -05:00
end ,
hold_callback = function ( )
2024-05-03 09:08:57 +03:00
self : updateHighlight ( index , 1 , - 1 , true )
2024-09-07 17:56:40 +03:00
end ,
2019-02-15 18:42:27 -05:00
} ,
{
2019-12-06 22:55:39 +01:00
text = end_next ,
2024-03-09 17:03:43 +02:00
enabled = enabled ,
2019-02-15 18:42:27 -05:00
callback = function ( )
2024-05-03 09:08:57 +03:00
self : updateHighlight ( index , 1 , 1 , false )
2019-02-15 18:42:27 -05:00
end ,
hold_callback = function ( )
2024-05-03 09:08:57 +03:00
self : updateHighlight ( index , 1 , 1 , true )
2024-09-07 17:56:40 +03:00
end ,
2019-02-15 18:42:27 -05:00
}
} )
end
2024-09-07 17:56:40 +03:00
self.edit_highlight_dialog = ButtonDialog : new { -- in self for unit tests
2024-05-03 09:08:57 +03:00
buttons = buttons ,
2024-10-11 19:26:33 +03:00
anchor = function ( )
2024-12-05 18:53:59 +02:00
return self : _getDialogAnchor ( self.edit_highlight_dialog , index )
2024-10-11 19:26:33 +03:00
end ,
2014-03-13 21:52:43 +08:00
}
UIManager : show ( self.edit_highlight_dialog )
return true
2014-01-18 03:05:17 +08:00
end
2020-11-08 13:07:51 +01:00
function ReaderHighlight : addToHighlightDialog ( idx , fn_button )
-- fn_button is a function that takes the ReaderHighlight instance
-- as argument, and returns a table describing the button to be added.
self._highlight_buttons [ idx ] = fn_button
end
function ReaderHighlight : removeFromHighlightDialog ( idx )
local button = self._highlight_buttons [ idx ]
self._highlight_buttons [ idx ] = nil
return button
end
2024-05-03 09:08:57 +03:00
function ReaderHighlight : onShowHighlightMenu ( index )
2021-10-31 17:36:00 +01:00
if not self.selected_text then
return
end
2020-11-08 13:07:51 +01:00
local highlight_buttons = { { } }
local columns = 2
for idx , fn_button in ffiUtil.orderedPairs ( self._highlight_buttons ) do
2024-05-03 09:08:57 +03:00
local button = fn_button ( self , index )
2021-02-06 16:59:09 +01:00
if not button.show_in_highlight_dialog_func or button.show_in_highlight_dialog_func ( ) then
if # highlight_buttons [ # highlight_buttons ] >= columns then
table.insert ( highlight_buttons , { } )
end
table.insert ( highlight_buttons [ # highlight_buttons ] , button )
logger.dbg ( " ReaderHighlight " , idx .. " : line " .. # highlight_buttons .. " , col " .. # highlight_buttons [ # highlight_buttons ] )
2020-11-08 13:07:51 +01:00
end
2020-01-05 12:56:01 +01:00
end
2019-10-06 23:47:53 +02:00
self.highlight_dialog = ButtonDialog : new {
buttons = highlight_buttons ,
2024-10-11 19:26:33 +03:00
anchor = function ( )
2024-12-05 18:53:59 +02:00
return self : _getDialogAnchor ( self.highlight_dialog , index )
2024-10-11 19:26:33 +03:00
end ,
2019-10-06 23:47:53 +02:00
tap_close_callback = function ( ) self : handleEvent ( Event : new ( " Tap " ) ) end ,
}
Assorted bag'o tweaks & fixes (#9569)
* UIManager: Support more specialized update modes for corner-cases:
* A2, which we'll use for the VirtualKeyboards keys (they'd... inadvertently switched to UI with the highlight refactor).
* NO_MERGE variants of ui & partial (for sunxi). Use `[ui]` in ReaderHighlight's popup, because of a Sage kernel bug that could otherwise make it translucent, sometimes completely so (*sigh*).
* UIManager: Assorted code cleanups & simplifications.
* Logger & dbg: Unify logging style, and code cleanups.
* SDL: Unbreak suspend/resume outside of the emulator (fix #9567).
* NetworkMgr: Cache the network status, and allow it to be queried. (Used by AutoSuspend to avoid repeatedly poking the system when computing the standby schedule delay).
* OneTimeMigration: Don't forget about `NETWORK_PROXY` & `STARDICT_DATA_DIR` when migrating `defaults.persistent.lua` (fix #9573)
* WakeupMgr: Workaround an apparent limitation of the RTC found on i.MX5 Kobo devices, where setting a wakealarm further than UINT16_MAX seconds in the future would apparently overflow and wraparound... (fix #8039, many thanks to @yfede for the extensive deep-dive and for actually accurately pinpointing the issue!).
* Kobo: Handle standby transitions at full CPU clock speeds, in order to limit the latency hit.
* UIManager: Properly quit on reboot & exit. This ensures our exit code is preserved, as we exit on our own terms (instead of being killed by the init system). This is important on platforms where exit codes are semantically meaningful (e.g., Kobo).
* UIManager: Speaking of reboot & exit, make sure the Screensaver shows in all circumstances (e.g., autoshutdown, re: #9542)), and that there aren't any extraneous refreshes triggered. (Additionally, fix a minor regression since #9448 about tracking this very transient state on Kobo & Cervantes).
* Kindle: ID the upcoming Scribe.
* Bump base (https://github.com/koreader/koreader-base/pull/1524)
2022-10-02 03:01:49 +02:00
-- NOTE: Disable merging for this update,
-- or the buggy Sage kernel may alpha-blend it into the page (with a bogus alpha value, to boot)...
UIManager : show ( self.highlight_dialog , " [ui] " )
2019-10-06 23:47:53 +02:00
end
2024-12-05 18:53:59 +02:00
function ReaderHighlight : _getDialogAnchor ( dialog , index )
2023-11-19 09:52:51 +02:00
local position = G_reader_settings : readSetting ( " highlight_dialog_position " , " center " )
2024-10-11 19:26:33 +03:00
if position == " center " then return end
2024-12-05 18:53:59 +02:00
local padding = Size.padding . small -- vertical padding, do not stick to the highlight box or to the screen edge
2024-10-11 19:26:33 +03:00
local dialog_box = dialog : getContentSize ( )
2023-11-19 09:52:51 +02:00
local anchor_x = math.floor ( ( self.screen_w - dialog_box.w ) / 2 ) -- center by width
local anchor_y , prefers_pop_down
if position == " top " then
2024-12-05 18:53:59 +02:00
anchor_y = padding
2023-11-19 09:52:51 +02:00
prefers_pop_down = true
elseif position == " bottom " then
2024-12-05 18:53:59 +02:00
anchor_y = self.screen_h - padding
2023-11-19 09:52:51 +02:00
else -- "gesture"
2024-12-05 18:53:59 +02:00
local boxes = index and self : getHighlightVisibleBoxes ( index ) or ( self.selected_text . sboxes or self.selected_text . pboxes )
if boxes == nil then return end -- fallback to "center"
local box0 , box1 = boxes [ 1 ] , boxes [ # boxes ]
if box0.y > box1.y then
box0 , box1 = box1 , box0
2024-10-11 19:26:33 +03:00
end
2024-12-05 18:53:59 +02:00
if self.ui . paging then
local page = index and self.ui . annotation.annotations [ index ] . pos0.page or self.selected_text . pos0.page
box0 = self.view : pageToScreenTransform ( page , box0 )
box1 = self.view : pageToScreenTransform ( page , box1 )
if box0 == nil or box1 == nil then return end
2023-11-19 09:52:51 +02:00
end
2024-12-05 18:53:59 +02:00
local y0 = box0.y
local y1 = box1.y + box1.h
local dialog_box_h = dialog_box.h + 2 * padding
if y1 + dialog_box_h <= self.screen_h then -- below highlight, preferable
anchor_y = y1 + padding
2023-11-19 09:52:51 +02:00
prefers_pop_down = true
2024-12-05 18:53:59 +02:00
elseif dialog_box_h <= y0 then -- above highlight
anchor_y = y0 - padding
else -- not enough room below and above, fallback to "center"
return
2023-11-19 09:52:51 +02:00
end
end
return { x = anchor_x , y = anchor_y , h = 0 , w = 0 } , prefers_pop_down
end
2020-05-05 18:56:30 +02:00
function ReaderHighlight : _resetHoldTimer ( clear )
2024-01-09 13:29:01 +01:00
if not self.long_hold_reached_action then
self.long_hold_reached_action = function ( )
self.long_hold_reached = true
-- Have ReaderView redraw and refresh ReaderFlipping and our state icon, avoiding flashes
2024-07-28 01:19:40 +02:00
UIManager : setDirty ( self.dialog , " ui " , self.view . flipping : getRefreshRegion ( ) )
2024-01-09 13:29:01 +01:00
end
end
-- Unschedule if already set
UIManager : unschedule ( self.long_hold_reached_action )
if not clear then
-- We don't need to handle long-hold and show its icon in some configurations
-- where it would not change the behaviour from the normal-hold one (but we still
-- need to go through the checks below to clear any long_hold_reached set when in
-- the word->multiwords selection transition).
-- (It feels we don't need to care about default_highlight_action="nothing" here.)
local handle_long_hold = true
if self.is_word_selection then
-- Single word normal-hold defaults to dict lookup, and long-hold defaults to "ask".
-- If normal-hold is set to use the highlight action, and this action is still "ask",
-- no need to handle long-hold.
if G_reader_settings : isTrue ( " highlight_action_on_single_word " ) and
G_reader_settings : readSetting ( " default_highlight_action " , " ask " ) == " ask " then
handle_long_hold = false
end
else
-- Multi words selection uses default_highlight_action, and no need for long-hold
-- if it is already "ask".
if G_reader_settings : readSetting ( " default_highlight_action " , " ask " ) == " ask " then
handle_long_hold = false
end
end
if handle_long_hold then
-- (Default delay is 3 seconds as in the menu items)
UIManager : scheduleIn ( G_reader_settings : readSetting ( " highlight_long_hold_threshold_s " , 3 ) , self.long_hold_reached_action )
end
end
-- Unset flag and icon
if self.long_hold_reached then
self.long_hold_reached = false
-- Have ReaderView redraw and refresh ReaderFlipping with our state icon removed
2024-07-28 01:19:40 +02:00
UIManager : setDirty ( self.dialog , " ui " , self.view . flipping : getRefreshRegion ( ) )
2020-05-05 18:56:30 +02:00
end
end
2020-09-24 15:17:37 +02:00
function ReaderHighlight : onTogglePanelZoomSetting ( arg , ges )
2024-05-03 09:08:57 +03:00
if self.ui . rolling then return end
2020-09-24 15:17:37 +02:00
self.panel_zoom_enabled = not self.panel_zoom_enabled
end
2020-12-24 09:07:27 +01:00
function ReaderHighlight : onToggleFallbackTextSelection ( arg , ges )
2024-05-03 09:08:57 +03:00
if self.ui . rolling then return end
2020-12-24 09:07:27 +01:00
self.panel_zoom_fallback_to_text_selection = not self.panel_zoom_fallback_to_text_selection
end
2020-09-24 15:17:37 +02:00
function ReaderHighlight : onPanelZoom ( arg , ges )
self : clear ( )
local hold_pos = self.view : screenToPageTransform ( ges.pos )
if not hold_pos then return false end -- outside page boundary
local rect = self.ui . document : getPanelFromPage ( hold_pos.page , hold_pos )
if not rect then return false end -- panel not found, return
Document: Do not cache panel-zoom tiles to disk and fix their caching and rendering (#12303)
* Use a dedicated cache hash for partial tiles from panel-zoom
* Never dump them to disk, as it confuses DocCache's crappy heuristics that rewinds the cache to skip over the hinted page to try to dump the on-screen page to disk.
* Apply the zoom factor in the exact same way as any other page rect (i.e., floor coordinates, ceil dimensions), and make sure said rect is actually a Geom so it doesn't break the cache hash, which relies on Geom's custom tostring method for rects. Said scaling method *also* belongs to the Geom class anyway.
* Handle such pre-scaled rects properly in renderPage, so as not to apply the zoom factor to the full page, which would attempt to create a gigantic buffer.
* And now that the rect is rendered properly in an appropriately-sized buffer, use the rendered tile as-is, no need to blit it to another (potentially way too large because of the above issue) blank BB.
* The zoom factor is now computed for a scale to best-fit (honoring `imageviewer_rotate_auto_for_best_fit`), ensuring the best efficiency (ImageViewer won't have to re-scale).
* Cache: Reduce the maximum item size to 50% of the cache, instead of 75%.
* Warn about the legacy ReaderRotation module, as it turned out to be horribly broken. The whole machinery (which is spread over *a lot* of various codepaths) is left as-is, peppered with notes & fixmes hinting at the problem. Thankfully, that's not how we actually handle rotation, so it was probably hardly ever used (which possibly explains why nobody ever noticed it breaking, and that nugget possibly dates back to the inception of the kpv -> ko refactor!). (#12309)
2024-08-08 04:52:24 +02:00
local image , rotate = self.ui . document : drawPagePart ( hold_pos.page , rect , 0 )
2020-09-24 15:17:37 +02:00
if image then
local ImageViewer = require ( " ui/widget/imageviewer " )
local imgviewer = ImageViewer : new {
image = image ,
Document: Do not cache panel-zoom tiles to disk and fix their caching and rendering (#12303)
* Use a dedicated cache hash for partial tiles from panel-zoom
* Never dump them to disk, as it confuses DocCache's crappy heuristics that rewinds the cache to skip over the hinted page to try to dump the on-screen page to disk.
* Apply the zoom factor in the exact same way as any other page rect (i.e., floor coordinates, ceil dimensions), and make sure said rect is actually a Geom so it doesn't break the cache hash, which relies on Geom's custom tostring method for rects. Said scaling method *also* belongs to the Geom class anyway.
* Handle such pre-scaled rects properly in renderPage, so as not to apply the zoom factor to the full page, which would attempt to create a gigantic buffer.
* And now that the rect is rendered properly in an appropriately-sized buffer, use the rendered tile as-is, no need to blit it to another (potentially way too large because of the above issue) blank BB.
* The zoom factor is now computed for a scale to best-fit (honoring `imageviewer_rotate_auto_for_best_fit`), ensuring the best efficiency (ImageViewer won't have to re-scale).
* Cache: Reduce the maximum item size to 50% of the cache, instead of 75%.
* Warn about the legacy ReaderRotation module, as it turned out to be horribly broken. The whole machinery (which is spread over *a lot* of various codepaths) is left as-is, peppered with notes & fixmes hinting at the problem. Thankfully, that's not how we actually handle rotation, so it was probably hardly ever used (which possibly explains why nobody ever noticed it breaking, and that nugget possibly dates back to the inception of the kpv -> ko refactor!). (#12309)
2024-08-08 04:52:24 +02:00
image_disposable = false , -- It's a TileCache item
2020-09-24 15:17:37 +02:00
with_title_bar = false ,
fullscreen = true ,
Document: Do not cache panel-zoom tiles to disk and fix their caching and rendering (#12303)
* Use a dedicated cache hash for partial tiles from panel-zoom
* Never dump them to disk, as it confuses DocCache's crappy heuristics that rewinds the cache to skip over the hinted page to try to dump the on-screen page to disk.
* Apply the zoom factor in the exact same way as any other page rect (i.e., floor coordinates, ceil dimensions), and make sure said rect is actually a Geom so it doesn't break the cache hash, which relies on Geom's custom tostring method for rects. Said scaling method *also* belongs to the Geom class anyway.
* Handle such pre-scaled rects properly in renderPage, so as not to apply the zoom factor to the full page, which would attempt to create a gigantic buffer.
* And now that the rect is rendered properly in an appropriately-sized buffer, use the rendered tile as-is, no need to blit it to another (potentially way too large because of the above issue) blank BB.
* The zoom factor is now computed for a scale to best-fit (honoring `imageviewer_rotate_auto_for_best_fit`), ensuring the best efficiency (ImageViewer won't have to re-scale).
* Cache: Reduce the maximum item size to 50% of the cache, instead of 75%.
* Warn about the legacy ReaderRotation module, as it turned out to be horribly broken. The whole machinery (which is spread over *a lot* of various codepaths) is left as-is, peppered with notes & fixmes hinting at the problem. Thankfully, that's not how we actually handle rotation, so it was probably hardly ever used (which possibly explains why nobody ever noticed it breaking, and that nugget possibly dates back to the inception of the kpv -> ko refactor!). (#12309)
2024-08-08 04:52:24 +02:00
rotated = rotate ,
2020-09-24 15:17:37 +02:00
}
UIManager : show ( imgviewer )
2020-12-24 09:07:27 +01:00
return true
2020-09-24 15:17:37 +02:00
end
2020-12-24 09:07:27 +01:00
return false
2020-09-24 15:17:37 +02:00
end
2017-01-15 21:51:43 +01:00
function ReaderHighlight : onHold ( arg , ges )
2024-05-03 09:08:57 +03:00
if self.ui . paging and self.panel_zoom_enabled then
2020-12-24 09:07:27 +01:00
local res = self : onPanelZoom ( arg , ges )
if res or not self.panel_zoom_fallback_to_text_selection then
return res
end
2020-09-24 15:17:37 +02:00
end
2017-09-20 17:35:30 +02:00
self : clear ( ) -- clear previous highlight (delayed clear may not have done it yet)
2014-03-13 21:52:43 +08:00
self.hold_pos = self.view : screenToPageTransform ( ges.pos )
2016-12-29 00:10:38 -08:00
logger.dbg ( " hold position in page " , self.hold_pos )
2014-03-13 21:52:43 +08:00
if not self.hold_pos then
2016-12-29 00:10:38 -08:00
logger.dbg ( " not inside page area " )
2020-01-24 20:05:21 +01:00
return false
2014-03-13 21:52:43 +08:00
end
2024-10-11 19:26:33 +03:00
self.gest_pos = { self.hold_pos , self.hold_pos }
2013-10-12 23:07:13 +08:00
2024-01-09 13:28:59 +01:00
self.allow_hold_pan_corner_scroll = false -- reset this, don't allow that yet
2017-01-15 21:51:43 +01:00
-- check if we were holding on an image
2018-04-21 22:03:04 +02:00
-- we provide want_frames=true, so we get a list of images for
-- animated GIFs (supported by ImageViewer)
2022-09-11 19:55:28 +02:00
-- We provide accept_cre_scalable_image=true to get, if the image is a SVG image,
-- a function that ImageViewer can use to get a perfect bb at any scale factor.
local image = self.ui . document : getImageFromPosition ( self.hold_pos , true , true )
2017-01-15 21:51:43 +01:00
if image then
logger.dbg ( " hold on image " )
local ImageViewer = require ( " ui/widget/imageviewer " )
local imgviewer = ImageViewer : new {
image = image ,
-- title_text = _("Document embedded image"),
-- No title, more room for image
with_title_bar = false ,
fullscreen = true ,
}
UIManager : show ( imgviewer )
2022-03-12 19:16:50 +08:00
self : onStopHighlightIndicator ( )
2017-01-15 21:51:43 +01:00
return true
end
-- otherwise, we must be holding on text
2021-09-11 12:04:48 +03:00
if self.view . highlight.disabled then return false end -- Long-press action "Do nothing" checked
2014-03-13 21:52:43 +08:00
local ok , word = pcall ( self.ui . document.getWordFromPosition , self.ui . document , self.hold_pos )
if ok and word then
2016-12-29 00:10:38 -08:00
logger.dbg ( " selected word: " , word )
2021-10-23 21:12:56 +11:00
-- Convert "word selection" table to "text selection" table because we
2021-10-23 21:13:09 +11:00
-- use text selections throughout readerhighlight in order to allow the
-- highlight to be corrected by language-specific plugins more easily.
2021-10-23 21:12:56 +11:00
self.is_word_selection = true
self.selected_text = {
text = word.word or " " ,
pos0 = word.pos0 or word.pos ,
pos1 = word.pos1 or word.pos ,
sboxes = word.sbox and { word.sbox } ,
pboxes = word.pbox and { word.pbox } ,
}
2017-09-10 14:26:49 +02:00
local link = self.ui . link : getLinkFromGes ( ges )
2017-09-09 18:30:00 +02:00
self.selected_link = nil
if link then
logger.dbg ( " link: " , link )
self.selected_link = link
end
2021-10-23 21:13:09 +11:00
2021-10-25 17:45:26 +11:00
if self.ui . languagesupport and self.ui . languagesupport : hasActiveLanguagePlugins ( ) then
2021-10-23 21:13:09 +11:00
-- If this is a language where pan-less word selection needs some
-- extra work above and beyond what the document engine gives us
-- from getWordFromPosition, call the relevant language-specific
-- plugin.
local new_selected_text = self.ui . languagesupport : improveWordSelection ( self.selected_text )
if new_selected_text then
self.selected_text = new_selected_text
end
end
2022-12-18 18:04:00 +02:00
if self.ui . paging then
2021-10-23 21:12:56 +11:00
self.view . highlight.temp [ self.hold_pos . page ] = self.selected_text . sboxes
2021-01-09 21:59:29 +01:00
-- Unfortunately, getWordFromPosition() may not return good coordinates,
-- so refresh the whole page
UIManager : setDirty ( self.dialog , " ui " )
else
2021-10-23 21:12:56 +11:00
-- With crengine, getWordFromPosition() does return good coordinates.
UIManager : setDirty ( self.dialog , " ui " , Geom.boundingBox ( self.selected_text . sboxes ) )
2014-03-13 21:52:43 +08:00
end
2020-05-05 18:56:30 +02:00
self : _resetHoldTimer ( )
2019-03-13 13:10:32 +01:00
if word.pos0 then
-- Remember original highlight start position, so we can show
-- a marker when back from across-pages text selection, which
-- is handled in onHoldPan()
self.selected_text_start_xpointer = word.pos0
end
2020-01-24 20:05:21 +01:00
return true
2014-03-13 21:52:43 +08:00
end
2020-01-24 20:05:21 +01:00
return false
2013-06-15 23:13:19 +08:00
end
2016-03-27 17:18:25 -07:00
function ReaderHighlight : onHoldPan ( _ , ges )
2021-12-10 11:06:16 +02:00
if self.view . highlight.disabled then return false end -- Long-press action "Do nothing" checked
2014-03-13 21:52:43 +08:00
if self.hold_pos == nil then
2016-12-29 00:10:38 -08:00
logger.dbg ( " no previous hold position " )
2020-05-05 18:56:30 +02:00
self : _resetHoldTimer ( true )
2014-03-13 21:52:43 +08:00
return true
end
2014-07-02 16:38:09 +08:00
local page_area = self.view : getScreenPageArea ( self.hold_pos . page )
if ges.pos : notIntersectWith ( page_area ) then
2016-12-29 00:10:38 -08:00
logger.dbg ( " not inside page area " , ges , page_area )
2020-05-05 18:56:30 +02:00
self : _resetHoldTimer ( )
2014-07-02 16:38:09 +08:00
return true
end
2014-03-13 21:52:43 +08:00
self.holdpan_pos = self.view : screenToPageTransform ( ges.pos )
2016-12-29 00:10:38 -08:00
logger.dbg ( " holdpan position in page " , self.holdpan_pos )
2019-02-03 10:01:58 +01:00
2024-01-09 13:28:59 +01:00
if self.ui . rolling and self.allow_corner_scroll and self.selected_text_start_xpointer then
2019-02-03 10:01:58 +01:00
-- With CreDocuments, allow text selection across multiple pages
-- by (temporarily) switching to scroll mode when panning to the
-- top left or bottom right corners.
2020-01-06 21:09:00 +01:00
local mirrored_reading = BD.mirroredUILayout ( )
2021-11-21 19:33:51 +02:00
if self.view . inverse_reading_order then
2020-01-06 21:09:00 +01:00
mirrored_reading = not mirrored_reading
end
local is_in_prev_page_corner , is_in_next_page_corner
if mirrored_reading then
2019-12-06 22:55:39 +01:00
-- Note: this might not be really usable, as crengine native selection
-- is not adapted to RTL text
2020-01-06 21:09:00 +01:00
-- top right corner
2023-11-19 09:52:51 +02:00
is_in_prev_page_corner = self.holdpan_pos . y < 1 / 8 * self.screen_h
and self.holdpan_pos . x > 7 / 8 * self.screen_w
2020-01-06 21:09:00 +01:00
-- bottom left corner
2023-11-19 09:52:51 +02:00
is_in_next_page_corner = self.holdpan_pos . y > 7 / 8 * self.screen_h
and self.holdpan_pos . x < 1 / 8 * self.screen_w
2020-01-06 21:09:00 +01:00
else -- default in LTR UI with no inverse_reading_order
-- top left corner
2023-11-19 09:52:51 +02:00
is_in_prev_page_corner = self.holdpan_pos . y < 1 / 8 * self.screen_h
and self.holdpan_pos . x < 1 / 8 * self.screen_w
2020-01-06 21:09:00 +01:00
-- bottom right corner
2023-11-19 09:52:51 +02:00
is_in_next_page_corner = self.holdpan_pos . y > 7 / 8 * self.screen_h
and self.holdpan_pos . x > 7 / 8 * self.screen_w
2019-12-06 22:55:39 +01:00
end
2024-01-09 13:28:59 +01:00
if not self.allow_hold_pan_corner_scroll then
if not is_in_prev_page_corner and not is_in_next_page_corner then
-- We expect the user to come from a non-corner zone into a corner
-- to enable this; this allows normal highlighting without scrolling
-- if the selection is started in the corner: the user will have to
-- move out from and go back in to trigger a scroll.
self.allow_hold_pan_corner_scroll = true
end
elseif is_in_prev_page_corner or is_in_next_page_corner then
2020-05-05 18:56:30 +02:00
self : _resetHoldTimer ( )
2019-02-03 10:01:58 +01:00
if self.was_in_some_corner then
-- Do nothing, wait for the user to move his finger out of that corner
return true
end
self.was_in_some_corner = true
2019-03-13 13:10:32 +01:00
if self.ui . document : getVisiblePageCount ( ) == 1 then -- single page mode
-- We'll adjust hold_pos.y after the mode switch and the scroll
-- so it's accurate in the new screen coordinates
local orig_y = self.ui . document : getScreenPositionFromXPointer ( self.selected_text_start_xpointer )
if self.view . view_mode ~= " scroll " then
-- Switch from page mode to scroll mode
local restore_page_mode_xpointer = self.ui . document : getXPointer ( ) -- top of current page
self.restore_page_mode_func = function ( )
self.ui : handleEvent ( Event : new ( " SetViewMode " , " page " ) )
self.ui . rolling : onGotoXPointer ( restore_page_mode_xpointer , self.selected_text_start_xpointer )
end
self.ui : handleEvent ( Event : new ( " SetViewMode " , " scroll " ) )
end
-- (using rolling:onGotoViewRel(1/3) has some strange side effects)
2023-11-19 09:52:51 +02:00
local scroll_distance = math.floor ( self.screen_h * 1 / 3 )
2020-01-06 21:09:00 +01:00
local move_y = is_in_next_page_corner and scroll_distance or - scroll_distance
2019-03-13 13:10:32 +01:00
self.ui . rolling : _gotoPos ( self.ui . document : getCurrentPos ( ) + move_y )
local new_y = self.ui . document : getScreenPositionFromXPointer ( self.selected_text_start_xpointer )
self.hold_pos . y = self.hold_pos . y - orig_y + new_y
UIManager : setDirty ( self.dialog , " ui " )
return true
else -- two pages mode
-- We don't switch to scroll mode: we just turn 1 page to
-- allow continuing the selection.
-- Unlike in 1-page mode, we have a limitation here: we can't adjust
-- the selection to further than current page and prev/next one.
-- So don't handle another corner if we already handled one:
2020-01-06 21:09:00 +01:00
-- Note that this feature won't work well with the RTL UI or
-- if inverse_reading_order as crengine currently always displays
-- the first page on the left and the second on the right in
-- dual page mode.
2019-03-13 13:10:32 +01:00
if self.restore_page_mode_func then
return true
end
-- Also, we are not able to move hold_pos.x out of screen,
-- so if we started on the right page, ignore top left corner,
-- and if we started on the left page, ignore bottom right corner.
2023-11-19 09:52:51 +02:00
local screen_half_width = math.floor ( self.screen_w * 0.5 )
2020-01-06 21:09:00 +01:00
if self.hold_pos . x >= screen_half_width and is_in_prev_page_corner then
2019-03-13 13:10:32 +01:00
return true
2020-01-06 21:09:00 +01:00
elseif self.hold_pos . x <= screen_half_width and is_in_next_page_corner then
2019-03-13 13:10:32 +01:00
return true
end
2021-01-29 01:02:19 +01:00
-- To be able to browse half-page when 2 visible pages as 1 page number,
-- we must work with internal page numbers
local cur_page = self.ui . document : getCurrentPage ( true )
2019-02-03 10:01:58 +01:00
local restore_page_mode_xpointer = self.ui . document : getXPointer ( ) -- top of current page
2021-01-29 01:02:19 +01:00
self.ui . document.no_page_sync = true -- avoid CreDocument:drawCurrentViewByPage() to resync page
2019-02-03 10:01:58 +01:00
self.restore_page_mode_func = function ( )
2021-01-29 01:02:19 +01:00
self.ui . document.no_page_sync = nil
2019-02-03 10:01:58 +01:00
self.ui . rolling : onGotoXPointer ( restore_page_mode_xpointer , self.selected_text_start_xpointer )
end
2020-01-06 21:09:00 +01:00
if is_in_next_page_corner then -- bottom right corner in LTR UI
2021-01-29 01:02:19 +01:00
self.ui . rolling : _gotoPage ( cur_page + 1 , true , true ) -- no odd left page enforcement
2019-03-13 13:10:32 +01:00
self.hold_pos . x = self.hold_pos . x - screen_half_width
2020-01-06 21:09:00 +01:00
else -- top left corner in RTL UI
2021-01-29 01:02:19 +01:00
self.ui . rolling : _gotoPage ( cur_page - 1 , true , true ) -- no odd left page enforcement
2019-03-13 13:10:32 +01:00
self.hold_pos . x = self.hold_pos . x + screen_half_width
end
UIManager : setDirty ( self.dialog , " ui " )
return true
2019-02-03 10:01:58 +01:00
end
else
self.was_in_some_corner = nil
end
end
2014-11-30 00:12:00 +00:00
local old_text = self.selected_text and self.selected_text . text
2014-03-13 21:52:43 +08:00
self.selected_text = self.ui . document : getTextFromPositions ( self.hold_pos , self.holdpan_pos )
2024-10-11 19:26:33 +03:00
self.gest_pos = { self.hold_pos , self.holdpan_pos }
2021-10-23 21:12:56 +11:00
self.is_word_selection = false
2019-02-03 10:01:58 +01:00
if self.selected_text and self.selected_text . pos0 then
if not self.selected_text_start_xpointer then
2019-03-13 13:10:32 +01:00
-- This should have been set in onHold(), where we would get
-- a precise pos0 on the first word selected.
-- Do it here too in case onHold() missed it, but it could be
-- less precise (getTextFromPositions() does order pos0 and pos1,
-- so it's not certain pos0 is where we started from; we get
-- the ones from the first pan, and if it is not small enough
-- and spans quite some height, the marker could point away
-- from the start position)
2019-02-03 10:01:58 +01:00
self.selected_text_start_xpointer = self.selected_text . pos0
end
end
2014-11-30 00:12:00 +00:00
if self.selected_text and old_text and old_text == self.selected_text . text then
-- no modification
return
end
2020-05-05 18:56:30 +02:00
self : _resetHoldTimer ( ) -- selection updated
2016-12-29 00:10:38 -08:00
logger.dbg ( " selected text: " , self.selected_text )
2024-12-05 18:53:59 +02:00
if self.ui . paging and self.selected_text then
2014-03-13 21:52:43 +08:00
self.view . highlight.temp [ self.hold_pos . page ] = self.selected_text . sboxes
end
2014-12-03 14:06:43 +08:00
UIManager : setDirty ( self.dialog , " ui " )
2013-06-15 23:13:19 +08:00
end
2019-08-22 17:11:47 +02:00
local info_message_ocr_text = _ ( [ [
No OCR results or no language data .
2018-03-05 16:38:04 +01:00
2024-09-07 16:23:56 +02:00
KOReader has a built - in OCR engine for recognizing words in scanned PDF and DjVu documents . In order to use OCR in scanned pages , you need to install tesseract trained data for your document language .
2018-03-05 16:38:04 +01:00
2024-06-01 00:25:59 +02:00
You can download language data files for Tesseract version 5.3 .4 from https : // tesseract - ocr.github . io / tessdoc / Data - Files
2018-03-05 16:38:04 +01:00
2024-06-01 00:25:59 +02:00
Copy the language data files ( e.g . , eng.traineddata for English and spa.traineddata for Spanish ) into koreader / data / tessdata ] ] )
2018-03-05 16:38:04 +01:00
2024-12-05 18:53:59 +02:00
function ReaderHighlight : lookupDictWord ( )
2021-10-23 21:12:56 +11:00
-- convert sboxes to word boxes
local word_boxes = { }
2024-12-05 18:53:59 +02:00
for i , sbox in ipairs ( self.selected_text . sboxes ) do
2021-10-23 21:12:56 +11:00
word_boxes [ i ] = self.view : pageToScreenTransform ( self.hold_pos . page , sbox )
end
2014-03-13 21:52:43 +08:00
-- if we extracted text directly
2024-12-05 18:53:59 +02:00
if # self.selected_text . text > 0 then
self.ui . dictionary : onLookupWord ( self.selected_text . text , false , word_boxes , self , self.selected_link )
2014-03-13 21:52:43 +08:00
-- or we will do OCR
2024-12-05 18:53:59 +02:00
elseif self.selected_text . sboxes then
local text = self.ui . document : getOCRText ( self.hold_pos . page , self.selected_text . sboxes )
2021-10-23 21:12:56 +11:00
if not text then
-- getOCRText is not implemented in some document backends, but
-- getOCRWord is implemented everywhere. As such, fall back to
-- getOCRWord.
text = " "
2024-12-05 18:53:59 +02:00
for _ , sbox in ipairs ( self.selected_text . sboxes ) do
2021-10-23 21:12:56 +11:00
local word = self.ui . document : getOCRWord ( self.hold_pos . page , { sbox = sbox } )
logger.dbg ( " OCRed word: " , word )
2021-10-23 16:29:00 +02:00
--- @fixme This might produce incorrect results on RTL text.
2024-12-05 18:53:59 +02:00
text = text .. ( word or " " )
2021-10-23 21:12:56 +11:00
end
end
logger.dbg ( " OCRed text: " , text )
if text and text ~= " " then
2024-12-05 18:53:59 +02:00
self.ui . dictionary : onLookupWord ( text , false , word_boxes , self , self.selected_link )
2018-03-05 16:38:04 +01:00
else
UIManager : show ( InfoMessage : new {
text = info_message_ocr_text ,
} )
end
2014-03-13 21:52:43 +08:00
end
2013-07-20 02:49:03 +08:00
end
2022-06-13 03:34:17 +08:00
function ReaderHighlight : getSelectedWordContext ( nb_words )
2022-10-25 18:23:18 +08:00
if not self.selected_text then return end
local ok , prev_context , next_context = pcall ( self.ui . document.getSelectedWordContext , self.ui . document ,
2024-08-04 23:27:30 +02:00
self.selected_text . text , nb_words , self.selected_text . pos0 , self.selected_text . pos1 , true )
2022-10-25 18:23:18 +08:00
if ok then
return prev_context , next_context
2022-06-13 03:34:17 +08:00
end
end
2021-09-01 14:17:35 +02:00
function ReaderHighlight : viewSelectionHTML ( debug_view , no_css_files_buttons )
2018-10-08 18:58:43 +02:00
if self.selected_text and self.selected_text . pos0 and self.selected_text . pos1 then
2023-03-05 13:54:25 +01:00
local ViewHtml = require ( " ui/viewhtml " )
ViewHtml : viewSelectionHTML ( self.ui . document , self.selected_text )
2018-10-08 18:58:43 +02:00
end
end
2024-05-03 09:08:57 +03:00
function ReaderHighlight : translate ( index )
2022-06-11 11:08:40 +02:00
if self.ui . rolling then
-- Extend the selected text to include any punctuation at start or end,
-- which may give a better translation with the added context.
2024-05-03 09:08:57 +03:00
local extended_text = self.ui . document : extendXPointersToSentenceSegment ( self.selected_text . pos0 , self.selected_text . pos1 )
2022-06-11 11:08:40 +02:00
if extended_text then
2024-05-03 09:08:57 +03:00
self.selected_text = extended_text
2022-06-11 11:08:40 +02:00
end
end
2024-05-03 09:08:57 +03:00
if # self.selected_text . text > 0 then
self : onTranslateText ( self.selected_text . text , index )
2014-03-13 21:52:43 +08:00
-- or we will do OCR
2021-05-16 19:54:46 +02:00
elseif self.hold_pos then
2024-05-03 09:08:57 +03:00
local text = self.ui . document : getOCRText ( self.hold_pos . page , self.selected_text )
2016-12-29 00:10:38 -08:00
logger.dbg ( " OCRed text: " , text )
2018-03-05 16:38:04 +01:00
if text and text ~= " " then
2018-12-16 18:02:38 +01:00
self : onTranslateText ( text )
2018-03-05 16:38:04 +01:00
else
UIManager : show ( InfoMessage : new {
text = info_message_ocr_text ,
} )
end
2014-03-13 21:52:43 +08:00
end
2013-07-20 02:49:03 +08:00
end
2024-05-03 09:08:57 +03:00
function ReaderHighlight : onTranslateText ( text , index )
Translator : showTranslation ( text , true , nil , nil , true , index )
2023-05-17 07:34:37 +03:00
end
function ReaderHighlight : onTranslateCurrentPage ( )
local x0 , y0 , x1 , y1 , page , is_reflow
if self.ui . rolling then
x0 = 0
y0 = 0
2023-11-19 09:52:51 +02:00
x1 = self.screen_w
y1 = self.screen_h
2023-05-17 07:34:37 +03:00
else
page = self.ui : getCurrentPage ( )
is_reflow = self.ui . document.configurable . text_wrap
self.ui . document.configurable . text_wrap = 0
local page_boxes = self.ui . document : getTextBoxes ( page )
if page_boxes and page_boxes [ 1 ] [ 1 ] . word then
x0 = page_boxes [ 1 ] [ 1 ] . x0
y0 = page_boxes [ 1 ] [ 1 ] . y0
x1 = page_boxes [ # page_boxes ] [ # page_boxes [ # page_boxes ] ] . x1
y1 = page_boxes [ # page_boxes ] [ # page_boxes [ # page_boxes ] ] . y1
end
end
local res = x0 and self.ui . document : getTextFromPositions ( { x = x0 , y = y0 , page = page } , { x = x1 , y = y1 } , true )
if self.ui . paging then
self.ui . document.configurable . text_wrap = is_reflow
end
if res and res.text then
2023-08-30 07:53:59 +03:00
Translator : showTranslation ( res.text , false , self.ui . doc_props.language )
2023-05-17 07:34:37 +03:00
end
2018-12-16 18:02:38 +01:00
end
2014-08-11 21:49:42 +08:00
function ReaderHighlight : onHoldRelease ( )
2022-01-08 16:58:32 +01:00
if self.clear_id then
-- Something has requested a clear id and is about to clear
-- the highlight: it may be a onHoldClose() that handled
-- "hold" and was closed, and can't handle "hold_release":
-- ignore this "hold_release" event.
return true
end
2024-01-09 13:29:01 +01:00
local long_final_hold = self.long_hold_reached
self : _resetHoldTimer ( true ) -- clear state
2021-11-21 19:31:10 +02:00
local default_highlight_action = G_reader_settings : readSetting ( " default_highlight_action " , " ask " )
if self.select_mode then -- extended highlighting, ending fragment
if self.selected_text then
2022-12-02 20:22:27 +02:00
self.select_mode = false
self : extendSelection ( )
if default_highlight_action == " select " then
self : saveHighlight ( true )
self : clear ( )
2021-11-21 19:31:10 +02:00
else
2022-12-02 20:22:27 +02:00
self : onShowHighlightMenu ( )
2021-11-21 19:31:10 +02:00
end
end
return true
end
2021-11-21 18:41:07 +01:00
2021-10-23 21:12:56 +11:00
if self.is_word_selection then -- single-word selection
2020-05-05 18:56:30 +02:00
if long_final_hold or G_reader_settings : isTrue ( " highlight_action_on_single_word " ) then
2021-10-23 21:12:56 +11:00
self.is_word_selection = false
2017-09-10 20:35:27 +02:00
end
end
2019-01-17 22:12:38 +01:00
2021-11-06 18:11:06 +11:00
if self.selected_text then
if self.is_word_selection then
2024-12-05 18:53:59 +02:00
self : lookupDictWord ( )
2021-11-06 18:11:06 +11:00
else
if long_final_hold or default_highlight_action == " ask " then
-- bypass default action and show popup if long final hold
self : onShowHighlightMenu ( )
elseif default_highlight_action == " highlight " then
2022-06-11 11:08:40 +02:00
self : saveHighlight ( true )
2021-11-06 18:11:06 +11:00
self : onClose ( )
2021-11-21 19:31:10 +02:00
elseif default_highlight_action == " select " then
self : startSelection ( )
self : onClose ( )
2022-11-02 13:58:12 -04:00
elseif default_highlight_action == " note " then
self : addNote ( )
self : onClose ( )
2021-11-06 18:11:06 +11:00
elseif default_highlight_action == " translate " then
2024-05-03 09:08:57 +03:00
self : translate ( )
2021-11-06 18:11:06 +11:00
elseif default_highlight_action == " wikipedia " then
self : lookupWikipedia ( )
self : onClose ( )
elseif default_highlight_action == " dictionary " then
2024-12-05 18:53:59 +02:00
self : lookupDict ( )
2021-11-06 18:11:06 +11:00
self : onClose ( )
elseif default_highlight_action == " search " then
self : onHighlightSearch ( )
-- No self:onClose() to not remove the selected text
-- which will have been the first search result
end
2018-12-16 18:02:38 +01:00
end
2014-03-13 21:52:43 +08:00
end
return true
2013-04-24 06:59:52 +08:00
end
2022-11-02 13:58:12 -04:00
function ReaderHighlight : getHighlightActions ( ) -- for Dispatcher
local action_nums , action_texts = { } , { }
for i , v in ipairs ( long_press_action ) do
table.insert ( action_nums , i )
table.insert ( action_texts , v [ 1 ] )
end
return action_nums , action_texts
end
function ReaderHighlight : onSetHighlightAction ( action_num , no_notification )
local v = long_press_action [ action_num ]
G_reader_settings : saveSetting ( " default_highlight_action " , v [ 2 ] )
self.view . highlight.disabled = v [ 2 ] == " nothing "
if not no_notification then -- fired with a gesture
UIManager : show ( Notification : new {
text = T ( _ ( " Default highlight action changed to '%1'. " ) , v [ 1 ] ) ,
} )
end
return true
end
2019-03-14 15:33:04 +01:00
function ReaderHighlight : onCycleHighlightAction ( )
2021-09-11 12:04:48 +03:00
local current_action = G_reader_settings : readSetting ( " default_highlight_action " , " ask " )
local next_action_num
for i , v in ipairs ( long_press_action ) do
if v [ 2 ] == current_action then
next_action_num = i + 1
break
end
end
if next_action_num > # long_press_action then
next_action_num = 1
2019-03-14 15:33:04 +01:00
end
2022-11-02 13:58:12 -04:00
self : onSetHighlightAction ( next_action_num )
2019-04-18 06:12:38 -04:00
return true
end
2024-08-31 19:28:48 +03:00
function ReaderHighlight . getHighlightStyles ( )
return highlight_style
end
2024-05-16 21:57:04 +03:00
function ReaderHighlight : getHighlightStyleString ( style ) -- for bookmark list
for _ , v in ipairs ( highlight_style ) do
if v [ 2 ] == style then
return v [ 1 ]
end
end
end
2019-04-18 06:12:38 -04:00
function ReaderHighlight : onCycleHighlightStyle ( )
2021-10-18 20:17:37 +03:00
local current_style = self.view . highlight.saved_drawer
local next_style_num
for i , v in ipairs ( highlight_style ) do
if v [ 2 ] == current_style then
next_style_num = i + 1
break
end
end
if next_style_num > # highlight_style then
next_style_num = 1
end
self.view . highlight.saved_drawer = highlight_style [ next_style_num ] [ 2 ]
2019-04-18 06:12:38 -04:00
UIManager : show ( Notification : new {
2021-10-18 20:17:37 +03:00
text = T ( _ ( " Default highlight style changed to '%1'. " ) , highlight_style [ next_style_num ] [ 1 ] ) ,
2019-04-18 06:12:38 -04:00
} )
return true
2019-03-14 15:33:04 +01:00
end
2014-11-05 12:28:11 +08:00
function ReaderHighlight : highlightFromHoldPos ( )
2014-08-11 21:49:42 +08:00
if self.hold_pos then
if not self.selected_text then
self.selected_text = self.ui . document : getTextFromPositions ( self.hold_pos , self.hold_pos )
2021-10-25 17:45:26 +11:00
if self.ui . languagesupport and self.ui . languagesupport : hasActiveLanguagePlugins ( ) then
2021-10-23 21:13:09 +11:00
-- Match language-specific expansion you'd get from self:onHold().
local new_selected_text = self.ui . languagesupport : improveWordSelection ( self.selected_text )
if new_selected_text then
self.selected_text = new_selected_text
end
end
2016-12-29 00:10:38 -08:00
logger.dbg ( " selected text: " , self.selected_text )
2014-08-11 21:49:42 +08:00
end
end
end
2024-05-03 09:08:57 +03:00
function ReaderHighlight : saveHighlight ( extend_to_sentence )
logger.dbg ( " save highlight " )
2015-03-12 18:50:57 +08:00
if self.hold_pos and not self.selected_text then
self : highlightFromHoldPos ( )
end
if self.selected_text and self.selected_text . pos0 and self.selected_text . pos1 then
2024-05-03 09:08:57 +03:00
local pg_or_xp
if self.ui . rolling then
if extend_to_sentence then
local extended_text = self.ui . document : extendXPointersToSentenceSegment ( self.selected_text . pos0 , self.selected_text . pos1 )
if extended_text then
self.selected_text = extended_text
end
2022-06-11 11:08:40 +02:00
end
2024-05-03 09:08:57 +03:00
pg_or_xp = self.selected_text . pos0
else
pg_or_xp = self.selected_text . pos0.page
2022-06-11 11:08:40 +02:00
end
2024-05-03 09:08:57 +03:00
local item = {
page = self.ui . paging and self.selected_text . pos0.page or self.selected_text . pos0 ,
2014-11-27 21:59:27 +08:00
pos0 = self.selected_text . pos0 ,
pos1 = self.selected_text . pos1 ,
2024-09-06 23:06:28 +02:00
text = util.cleanupSelectedText ( self.selected_text . text ) ,
2014-11-27 21:59:27 +08:00
drawer = self.view . highlight.saved_drawer ,
2024-08-30 13:10:43 -07:00
color = self.view . highlight.saved_color ,
2024-05-03 09:08:57 +03:00
chapter = self.ui . toc : getTocTitleByPage ( pg_or_xp ) ,
2014-11-27 21:59:27 +08:00
}
2024-05-03 09:08:57 +03:00
if self.ui . paging then
item.pboxes = self.selected_text . pboxes
item.ext = self.selected_text . ext
self : writePdfAnnotation ( " save " , item )
2015-03-12 18:50:57 +08:00
end
2024-05-03 09:08:57 +03:00
local index = self.ui . annotation : addItem ( item )
2024-09-07 17:56:40 +03:00
self.view . footer : maybeUpdateFooter ( )
2025-02-07 08:30:21 +02:00
self.ui : handleEvent ( Event : new ( " AnnotationsModified " , { item , nb_highlights_added = 1 , index_modified = index } ) )
2024-05-03 09:08:57 +03:00
return index
2014-03-13 21:52:43 +08:00
end
2013-06-15 23:13:19 +08:00
end
2024-05-03 09:08:57 +03:00
function ReaderHighlight : writePdfAnnotation ( action , item , content )
2024-09-14 15:21:26 +03:00
if self.ui . rolling or not self.highlight_write_into_pdf then
2022-12-02 20:22:27 +02:00
return
end
logger.dbg ( " write to pdf document " , action , item )
2022-12-12 00:47:34 +01:00
local function doAction ( action_ , page_ , item_ , content_ )
if action_ == " save " then
2024-09-14 15:21:26 +03:00
self.document : saveHighlight ( page_ , item_ )
2022-12-12 00:47:34 +01:00
elseif action_ == " delete " then
2024-09-14 15:21:26 +03:00
self.document : deleteHighlight ( page_ , item_ )
2022-12-12 00:47:34 +01:00
elseif action_ == " content " then
2024-09-14 15:21:26 +03:00
self.document : updateHighlightContents ( page_ , item_ , content_ )
2022-12-02 20:22:27 +02:00
end
end
if item.pos0 . page == item.pos1 . page then -- single-page highlight
2024-09-14 15:21:26 +03:00
doAction ( action , item.pos0 . page , item , content )
2022-12-02 20:22:27 +02:00
else -- multi-page highlight
for hl_page = item.pos0 . page , item.pos1 . page do
local hl_part = self : getSavedExtendedHighlightPage ( item , hl_page )
2024-09-14 15:21:26 +03:00
doAction ( action , hl_page , hl_part , content )
2022-12-02 20:22:27 +02:00
if action == " save " then -- update pboxes from quadpoints
item.ext [ hl_page ] . pboxes = hl_part.pboxes
end
end
end
2014-02-02 00:16:51 +08:00
end
2014-08-20 14:41:45 +08:00
function ReaderHighlight : lookupWikipedia ( )
if self.selected_text then
2024-09-06 23:06:28 +02:00
self.ui : handleEvent ( Event : new ( " LookupWikipedia " , util.cleanupSelectedText ( self.selected_text . text ) ) )
2014-08-20 14:41:45 +08:00
end
end
2014-11-05 12:28:11 +08:00
function ReaderHighlight : onHighlightSearch ( )
2016-12-29 00:10:38 -08:00
logger.dbg ( " search highlight " )
2021-03-21 13:57:18 +01:00
-- First, if our dialog is still shown, close it.
if self.highlight_dialog then
UIManager : close ( self.highlight_dialog )
self.highlight_dialog = nil
end
2014-11-05 12:28:11 +08:00
self : highlightFromHoldPos ( )
if self.selected_text then
2024-09-06 23:06:28 +02:00
local text = util.stripPunctuation ( util.cleanupSelectedText ( self.selected_text . text ) )
2024-03-10 07:57:50 +02:00
self.ui . search : searchText ( text )
2014-11-05 12:28:11 +08:00
end
end
2024-12-05 18:53:59 +02:00
function ReaderHighlight : lookupDict ( index )
2016-12-29 00:10:38 -08:00
logger.dbg ( " dictionary lookup highlight " )
2015-06-15 18:11:42 +08:00
self : highlightFromHoldPos ( )
if self.selected_text then
2024-12-05 18:53:59 +02:00
local boxes = index and self : getHighlightVisibleBoxes ( index ) or ( self.selected_text . sboxes or self.selected_text . pboxes )
local word_boxes
if boxes ~= nil then
word_boxes = { }
for i , box in ipairs ( boxes ) do
word_boxes [ i ] = self.view : pageToScreenTransform ( self.selected_text . pos0.page , box )
end
end
self.ui . dictionary : onLookupWord ( util.cleanupSelectedText ( self.selected_text . text ) , false , word_boxes )
2015-06-15 18:11:42 +08:00
end
end
2024-05-03 09:08:57 +03:00
function ReaderHighlight : deleteHighlight ( index )
logger.dbg ( " delete highlight " , index )
local item = self.ui . annotation.annotations [ index ]
self : writePdfAnnotation ( " delete " , item )
self.ui . bookmark : removeItemByIndex ( index )
2022-01-16 21:54:08 +02:00
UIManager : setDirty ( self.dialog , " ui " )
2013-06-15 23:13:19 +08:00
end
2024-05-03 09:08:57 +03:00
function ReaderHighlight : addNote ( text )
local index = self : saveHighlight ( true )
if text then -- called from Translator to save translation to note
self : clear ( )
end
2024-11-21 07:54:23 +02:00
self : editNote ( index , true , text )
2024-05-03 09:08:57 +03:00
end
2024-11-21 07:54:23 +02:00
function ReaderHighlight : editNote ( index , is_new_note , text )
2024-05-16 21:57:04 +03:00
local note_updated_callback = function ( )
if self.view . highlight.note_mark then -- refresh note marker
UIManager : setDirty ( self.dialog , " ui " )
end
end
self.ui . bookmark : setBookmarkNote ( index , is_new_note , text , note_updated_callback )
2013-06-15 23:13:19 +08:00
end
2013-10-18 22:38:07 +02:00
2024-05-03 09:08:57 +03:00
function ReaderHighlight : editHighlightStyle ( index )
local item = self.ui . annotation.annotations [ index ]
2023-06-08 08:28:43 +03:00
local apply_drawer = function ( drawer )
2024-05-03 09:08:57 +03:00
self : writePdfAnnotation ( " delete " , item )
2023-06-08 08:28:43 +03:00
item.drawer = drawer
if self.ui . paging then
2024-05-03 09:08:57 +03:00
self : writePdfAnnotation ( " save " , item )
if item.note then
self : writePdfAnnotation ( " content " , item , item.note )
2023-06-08 08:28:43 +03:00
end
end
UIManager : setDirty ( self.dialog , " ui " )
2024-05-03 09:08:57 +03:00
self.ui : handleEvent ( Event : new ( " AnnotationsModified " , { item } ) )
2023-06-08 08:28:43 +03:00
end
2024-08-30 13:10:43 -07:00
self : showHighlightStyleDialog ( apply_drawer , item.drawer , index )
2023-06-08 08:28:43 +03:00
end
2024-08-30 13:10:43 -07:00
function ReaderHighlight : editHighlightColor ( index )
local item = self.ui . annotation.annotations [ index ]
local apply_color = function ( color )
self : writePdfAnnotation ( " delete " , item )
item.color = color
if self.ui . paging then
self : writePdfAnnotation ( " save " , item )
if item.note then
self : writePdfAnnotation ( " content " , item , item.note )
end
end
UIManager : setDirty ( self.dialog , " ui " )
self.ui : handleEvent ( Event : new ( " AnnotationsModified " , { item } ) )
end
2024-09-07 17:56:40 +03:00
self : showHighlightColorDialog ( apply_color , item )
2024-08-30 13:10:43 -07:00
end
2024-09-07 17:56:40 +03:00
function ReaderHighlight : showHighlightStyleDialog ( caller_callback , item_drawer )
2023-06-08 08:28:43 +03:00
local default_drawer , keep_shown_on_apply
2024-05-03 09:08:57 +03:00
if item_drawer then -- called from ReaderHighlight:editHighlightStyle()
2024-09-07 17:56:40 +03:00
default_drawer = self.view . highlight.saved_drawer
2023-06-08 08:28:43 +03:00
keep_shown_on_apply = true
end
2021-10-18 20:17:37 +03:00
local radio_buttons = { }
for _ , v in ipairs ( highlight_style ) do
table.insert ( radio_buttons , {
{
2022-01-25 22:39:03 +02:00
text = v [ 1 ] ,
2023-06-08 08:28:43 +03:00
checked = item_drawer == v [ 2 ] ,
2022-01-25 22:39:03 +02:00
provider = v [ 2 ] ,
2021-10-18 20:17:37 +03:00
} ,
} )
end
2024-09-07 17:56:40 +03:00
UIManager : show ( RadioButtonWidget : new {
2021-10-18 20:17:37 +03:00
title_text = _ ( " Highlight style " ) ,
width_factor = 0.5 ,
2023-06-08 08:28:43 +03:00
keep_shown_on_apply = keep_shown_on_apply ,
2021-10-18 20:17:37 +03:00
radio_buttons = radio_buttons ,
2023-06-08 08:28:43 +03:00
default_provider = default_drawer ,
2021-10-18 20:17:37 +03:00
callback = function ( radio )
2023-06-08 08:28:43 +03:00
caller_callback ( radio.provider )
2021-10-18 20:17:37 +03:00
end ,
2024-09-07 17:56:40 +03:00
} )
2024-08-30 13:10:43 -07:00
end
2024-09-07 17:56:40 +03:00
function ReaderHighlight : showHighlightColorDialog ( caller_callback , item )
local default_color , curr_color , keep_shown_on_apply
if item then -- called from ReaderHighlight:editHighlightColor()
default_color = self.view . highlight.saved_color
curr_color = item.color or default_color
2024-08-30 13:10:43 -07:00
keep_shown_on_apply = true
2024-09-07 17:56:40 +03:00
else
default_color = G_reader_settings : readSetting ( " highlight_color " ) or self._fallback_color
curr_color = self.view . highlight.saved_color
2024-08-30 13:10:43 -07:00
end
local radio_buttons = { }
for _ , v in ipairs ( self.highlight_colors ) do
table.insert ( radio_buttons , {
{
text = v [ 1 ] ,
2024-09-07 17:56:40 +03:00
checked = curr_color == v [ 2 ] ,
bgcolor = BlitBuffer.colorFromName ( v [ 2 ] )
or BlitBuffer.Color8 ( bit.bxor ( 0xFF * self.view . highlight.lighten_factor , 0xFF ) ) ,
2024-08-30 13:10:43 -07:00
provider = v [ 2 ] ,
} ,
} )
end
UIManager : show ( RadioButtonWidget : new {
title_text = _ ( " Highlight color " ) ,
width_factor = 0.5 ,
keep_shown_on_apply = keep_shown_on_apply ,
radio_buttons = radio_buttons ,
default_provider = default_color ,
callback = function ( radio )
caller_callback ( radio.provider )
end ,
2024-09-07 17:56:40 +03:00
-- This ensures the waveform mode will be upgraded to a Kaleido wfm on compatible devices
2024-08-30 13:10:43 -07:00
colorful = true ,
dithered = true ,
2021-10-18 20:17:37 +03:00
} )
end
2021-11-21 19:31:10 +02:00
function ReaderHighlight : startSelection ( )
2024-05-03 09:08:57 +03:00
self.highlight_idx = self : saveHighlight ( )
2021-11-21 19:31:10 +02:00
self.select_mode = true
end
function ReaderHighlight : extendSelection ( )
-- item1 - starting fragment (saved), item2 - ending fragment (currently selected)
-- new extended highlight includes item1, item2 and the text between them
2024-05-03 09:08:57 +03:00
local item1 = self.ui . annotation.annotations [ self.highlight_idx ]
2021-11-21 19:31:10 +02:00
local item2_pos0 , item2_pos1 = self.selected_text . pos0 , self.selected_text . pos1
-- getting starting and ending positions, text and pboxes of extended highlight
2022-12-02 20:22:27 +02:00
local new_pos0 , new_pos1 , new_text , new_pboxes , ext
if self.ui . paging then
local cur_page = self.hold_pos . page
2022-01-06 21:54:33 +02:00
local is_reflow = self.ui . document.configurable . text_wrap
2021-11-21 19:31:10 +02:00
-- pos0 and pos1 are not in order within highlights, hence sorting all
local function comparePositions ( pos1 , pos2 )
2022-01-06 21:54:33 +02:00
return self.ui . document : comparePositions ( pos1 , pos2 ) == 1
2021-11-21 19:31:10 +02:00
end
local positions = { item1.pos0 , item1.pos1 , item2_pos0 , item2_pos1 }
self.ui . document.configurable . text_wrap = 0 -- native positions
table.sort ( positions , comparePositions )
new_pos0 = positions [ 1 ]
new_pos1 = positions [ 4 ]
2022-12-02 20:22:27 +02:00
local temp_pos0 , temp_pos1
if new_pos0.page == new_pos1.page then -- single-page highlight
local text_boxes = self.ui . document : getTextFromPositions ( new_pos0 , new_pos1 )
new_text = text_boxes.text
new_pboxes = text_boxes.pboxes
temp_pos0 , temp_pos1 = new_pos0 , new_pos1
else -- multi-page highlight
new_text = " "
ext = { }
for page = new_pos0.page , new_pos1.page do
local item = self : getExtendedHighlightPage ( new_pos0 , new_pos1 , page )
new_text = new_text .. item.text
ext [ page ] = { -- for every page of multi-page highlight
pos0 = item.pos0 ,
pos1 = item.pos1 ,
pboxes = item.pboxes ,
}
if page == cur_page then
temp_pos0 , temp_pos1 = item.pos0 , item.pos1
end
end
end
2022-01-06 21:54:33 +02:00
self.ui . document.configurable . text_wrap = is_reflow -- restore reflow
2021-11-21 19:31:10 +02:00
-- draw
2022-12-02 20:22:27 +02:00
self.view . highlight.temp [ cur_page ] = self.ui . document : getPageBoxesFromPositions ( cur_page , temp_pos0 , temp_pos1 )
2021-11-21 19:31:10 +02:00
else
-- pos0 and pos1 are in order within highlights
new_pos0 = self.ui . document : compareXPointers ( item1.pos0 , item2_pos0 ) == 1 and item1.pos0 or item2_pos0
new_pos1 = self.ui . document : compareXPointers ( item1.pos1 , item2_pos1 ) == 1 and item2_pos1 or item1.pos1
2024-12-05 18:53:59 +02:00
new_pboxes = self.document : getScreenBoxesFromPositions ( new_pos0 , new_pos1 )
2021-11-21 19:31:10 +02:00
-- true to draw
new_text = self.ui . document : getTextFromXPointers ( new_pos0 , new_pos1 , true )
end
2024-05-03 09:08:57 +03:00
self : deleteHighlight ( self.highlight_idx ) -- starting fragment
2021-11-21 19:31:10 +02:00
self.selected_text = {
text = new_text ,
pos0 = new_pos0 ,
pos1 = new_pos1 ,
pboxes = new_pboxes ,
2022-12-02 20:22:27 +02:00
ext = ext ,
2021-11-21 19:31:10 +02:00
}
UIManager : setDirty ( self.dialog , " ui " )
end
2022-12-02 20:22:27 +02:00
-- Calculates positions, text, pboxes of one page of selected multi-page highlight
-- (For pdf documents only, reflow mode must be off)
function ReaderHighlight : getExtendedHighlightPage ( pos0 , pos1 , cur_page )
local item = { }
for page = pos0.page , pos1.page do
if page == cur_page then
local page_boxes = self.ui . document : getTextBoxes ( page )
if page == pos0.page then
-- first page (from the start of highlight to the end of the page)
2024-05-03 09:08:57 +03:00
item.pos0 = {
x = pos0.x ,
y = pos0.y ,
}
2022-12-02 20:22:27 +02:00
item.pos1 = {
x = page_boxes [ # page_boxes ] [ # page_boxes [ # page_boxes ] ] . x1 ,
y = page_boxes [ # page_boxes ] [ # page_boxes [ # page_boxes ] ] . y1 ,
}
elseif page ~= pos1.page then
-- middle pages (full pages)
item.pos0 = {
x = page_boxes [ 1 ] [ 1 ] . x0 ,
y = page_boxes [ 1 ] [ 1 ] . y0 ,
}
item.pos1 = {
x = page_boxes [ # page_boxes ] [ # page_boxes [ # page_boxes ] ] . x1 ,
y = page_boxes [ # page_boxes ] [ # page_boxes [ # page_boxes ] ] . y1 ,
}
else
-- last page (from the start of the page to the end of highlight)
item.pos0 = {
x = page_boxes [ 1 ] [ 1 ] . x0 ,
y = page_boxes [ 1 ] [ 1 ] . y0 ,
}
2024-05-03 09:08:57 +03:00
item.pos1 = {
x = pos1.x ,
y = pos1.y ,
}
2022-12-02 20:22:27 +02:00
end
item.pos0 . page = page
item.pos1 . page = page
local text_boxes = self.ui . document : getTextFromPositions ( item.pos0 , item.pos1 )
item.text = text_boxes.text
item.pboxes = text_boxes.pboxes
end
end
return item
end
2024-05-03 09:08:57 +03:00
-- Returns the list of highlights in page.
-- The list includes full single-page highlights and parts of multi-page highlights.
2022-12-02 20:22:27 +02:00
-- (For pdf documents only)
2024-05-03 09:08:57 +03:00
function ReaderHighlight : getPageSavedHighlights ( page )
2024-05-09 20:53:53 +03:00
local idx_offset
2024-05-03 09:08:57 +03:00
local highlights = { }
for index , highlight in ipairs ( self.ui . annotation.annotations ) do
if highlight.drawer and highlight.pos0 . page <= page and page <= highlight.pos1 . page then
2024-05-09 20:53:53 +03:00
if idx_offset == nil then
idx_offset = index - 1
end
2024-05-03 09:08:57 +03:00
if highlight.ext then -- multi-page highlight
local item = self : getSavedExtendedHighlightPage ( highlight , page , index )
table.insert ( highlights , item )
else
table.insert ( highlights , highlight )
2022-12-02 20:22:27 +02:00
end
end
end
2024-05-09 20:53:53 +03:00
return highlights , idx_offset
2024-05-03 09:08:57 +03:00
end
-- Returns one page of saved multi-page highlight
-- (For pdf documents only)
function ReaderHighlight : getSavedExtendedHighlightPage ( highlight , page , index )
local item = {
datetime = highlight.datetime ,
drawer = highlight.drawer ,
2024-10-18 21:59:31 +03:00
color = highlight.color or self.view . highlight.saved_color ,
2024-05-03 09:08:57 +03:00
text = highlight.text ,
2024-05-09 20:53:53 +03:00
note = highlight.note ,
2024-05-03 09:08:57 +03:00
page = highlight.page ,
pos0 = highlight.ext [ page ] . pos0 ,
pos1 = highlight.ext [ page ] . pos1 ,
pboxes = highlight.ext [ page ] . pboxes ,
parent = index ,
}
item.pos0 . zoom = highlight.pos0 . zoom
2022-12-02 20:22:27 +02:00
item.pos0 . rotation = highlight.pos0 . rotation
2024-05-03 09:08:57 +03:00
item.pos1 . zoom = highlight.pos0 . zoom
2022-12-02 20:22:27 +02:00
item.pos1 . rotation = highlight.pos0 . rotation
return item
end
2014-01-18 03:05:17 +08:00
function ReaderHighlight : onReadSettings ( config )
2021-09-02 23:46:27 +03:00
self.view . highlight.saved_drawer = config : readSetting ( " highlight_drawer " )
or G_reader_settings : readSetting ( " highlight_drawing_style " ) or self.view . highlight.saved_drawer
2024-08-30 13:10:43 -07:00
self.view . highlight.saved_color = config : readSetting ( " highlight_color " )
or G_reader_settings : readSetting ( " highlight_color " ) or self.view . highlight.saved_color
2024-05-03 09:08:57 +03:00
self.view . highlight.disabled = G_reader_settings : readSetting ( " default_highlight_action " ) == " nothing "
2020-09-24 15:17:37 +02:00
2024-01-09 13:28:59 +01:00
self.allow_corner_scroll = G_reader_settings : nilOrTrue ( " highlight_corner_scroll " )
2020-09-24 15:17:37 +02:00
-- panel zoom settings isn't supported in EPUB
2024-05-03 09:08:57 +03:00
if self.ui . paging then
2024-09-14 15:21:26 +03:00
if self.document . is_pdf and self.document : _checkIfWritable ( ) then
if config : has ( " highlight_write_into_pdf " ) then
self.highlight_write_into_pdf = config : isTrue ( " highlight_write_into_pdf " ) -- true or false
else
self.highlight_write_into_pdf = G_reader_settings : readSetting ( " highlight_write_into_pdf " ) -- true or nil
end
end
2020-12-24 09:07:27 +01:00
local ext = util.getFileNameSuffix ( self.ui . document.file )
G_reader_settings : initializeExtSettings ( " panel_zoom_enabled " , { cbz = true , cbt = true } )
G_reader_settings : initializeExtSettings ( " panel_zoom_fallback_to_text_selection " , { pdf = true } )
2021-03-06 22:44:18 +01:00
if config : has ( " panel_zoom_enabled " ) then
self.panel_zoom_enabled = config : isTrue ( " panel_zoom_enabled " )
else
2020-12-24 09:07:27 +01:00
self.panel_zoom_enabled = G_reader_settings : getSettingForExt ( " panel_zoom_enabled " , ext ) or false
end
2021-03-06 22:44:18 +01:00
if config : has ( " panel_zoom_fallback_to_text_selection " ) then
self.panel_zoom_fallback_to_text_selection = config : isTrue ( " panel_zoom_fallback_to_text_selection " )
else
2020-12-24 09:07:27 +01:00
self.panel_zoom_fallback_to_text_selection = G_reader_settings : getSettingForExt ( " panel_zoom_fallback_to_text_selection " , ext ) or false
2020-09-24 15:17:37 +02:00
end
end
2014-01-18 03:05:17 +08:00
end
2020-08-06 00:13:26 +02:00
function ReaderHighlight : onUpdateHoldPanRate ( )
self : setupTouchZones ( )
end
2014-01-18 03:05:17 +08:00
function ReaderHighlight : onSaveSettings ( )
2014-03-13 21:52:43 +08:00
self.ui . doc_settings : saveSetting ( " highlight_drawer " , self.view . highlight.saved_drawer )
2024-08-30 13:10:43 -07:00
self.ui . doc_settings : saveSetting ( " highlight_color " , self.view . highlight.saved_color )
2024-09-14 15:21:26 +03:00
self.ui . doc_settings : saveSetting ( " highlight_write_into_pdf " , self.highlight_write_into_pdf )
2020-09-24 15:17:37 +02:00
self.ui . doc_settings : saveSetting ( " panel_zoom_enabled " , self.panel_zoom_enabled )
2014-01-18 03:05:17 +08:00
end
2014-08-20 14:41:45 +08:00
function ReaderHighlight : onClose ( )
2024-01-15 02:34:58 +01:00
if self.highlight_dialog then
UIManager : close ( self.highlight_dialog )
self.highlight_dialog = nil
end
2014-08-20 14:41:45 +08:00
-- clear highlighted text
2014-08-26 20:29:53 +08:00
self : clear ( )
2014-08-20 14:41:45 +08:00
end
2024-05-03 09:08:57 +03:00
-- dpad/keys support
2022-03-12 19:16:50 +08:00
function ReaderHighlight : onHighlightPress ( )
2025-01-28 15:52:27 +00:00
if not self._current_indicator_pos then return false end
if self._start_indicator_highlight then
self : onHoldRelease ( nil , self : _createHighlightGesture ( " hold_release " ) )
self : onStopHighlightIndicator ( )
2022-03-12 19:16:50 +08:00
return true
end
2025-01-28 15:52:27 +00:00
-- Attempt to open an existing highlight
if self : onTap ( nil , self : _createHighlightGesture ( " tap " ) ) then
self : onStopHighlightIndicator ( true ) -- need_clear_selection=true
return true
end
-- no existing highlight at current indicator position: start hold
self._start_indicator_highlight = true
self : onHold ( nil , self : _createHighlightGesture ( " hold " ) )
if not ( self.ui . rolling and self.selected_text and self.selected_text . sboxes and # self.selected_text . sboxes > 0 ) then
return true
end
-- With crengine, selected_text.sboxes have good coordinates, so we'll borrow them.
local pos = self.selected_text . sboxes [ 1 ]
local margins = self.ui . document.configurable . h_page_margins [ 1 ] + self.ui . document.configurable . h_page_margins [ 2 ]
local two_column_mode = self.ui . document.configurable . visible_pages == 2
local effective_width = two_column_mode and ( self.screen_w - margins ) / 2 or self.screen_w - margins
-- When words are split (and hyphenated) due to line breaks, they create selection boxes that are almost as wide as the
-- effective_width, so we need to check if that is the case, in order to handle those cases properly. We cannot precisely
-- and easily recognise hyphenated words in the front end, so a heuristic approach is used, it goes in two steps.
-- Step one: check if our box is a 'big boy'. We must allow some room for unknown variables like publisher-embedded padding, etc.
local is_word_split = pos.w > 0.7 * effective_width
-- Step two: weed out false positives (i.e long words) by comparing words found at different box coordinates.
if is_word_split then
-- In the case of a split (and hyphenated) word, we should get distinct words at different coordinates inside the box,
-- false positives on the other hand, should return the same word at different coordinates.
local word_at_pos1 = self.ui . document : getWordFromPosition ( {
x = BD.mirroredUILayout ( ) and pos.x + pos.w or pos.x ,
y = pos.y + pos.h * 1 / 4 -- puts us at a potential line 1 of 2
} )
local word_at_pos2 = self.ui . document : getWordFromPosition ( {
x = BD.mirroredUILayout ( ) and pos.x or pos.x + pos.w ,
y = pos.y + pos.h * 3 / 4 -- puts us at a potential line 2 of 2
} )
local does_word_at_pos1_match = word_at_pos1 and word_at_pos1.word == self.selected_text . text
local does_word_at_pos2_match = word_at_pos2 and word_at_pos2.word == self.selected_text . text
-- If all 3 words are a match, then we're likely not a split word, just a very long one, something worthy of floccinaucinihilipilification.
if does_word_at_pos1_match and does_word_at_pos2_match then
is_word_split = false -- check mate
else -- We're reasonably sure the word was split (and hyphenated). Re-select the original word to ensure the correct word is highlighted.
self.ui . document : getWordFromPosition ( {
x = BD.mirroredUILayout ( ) and pos.x + pos.w or pos.x ,
y = pos.y + pos.h * 3 / 4
} )
end
end
-- helper function to update crosshairs positioning and self.hold_pos
local function updatePositions ( hold_x , hold_y , indicator_x , indicator_y )
self.hold_pos = self.view : screenToPageTransform ( { x = hold_x , y = hold_y } )
UIManager : setDirty ( self.dialog , " ui " , self._current_indicator_pos )
self._current_indicator_pos . x = indicator_x
self._current_indicator_pos . y = indicator_y
end
-- Determine positions based on word type and layout.
if is_word_split then
if BD.mirroredUILayout ( ) then -- RTL
updatePositions (
pos.x + pos.w , -- rightmost point
pos.y + pos.h * 3 / 4 , -- adjusted vertical position
pos.x + pos.w ,
pos.y + pos.h * 3 / 4 - self._current_indicator_pos . h / 2
)
else
updatePositions (
pos.x , -- leftmost point
pos.y + pos.h * 3 / 4 , -- adjusted vertical position
pos.x ,
pos.y + pos.h * 3 / 4 - self._current_indicator_pos . h / 2
)
end
else
updatePositions (
-- set hold_pos to center of selected_text to make center selection more stable, not JITted at edge
pos.x + pos.w / 2 , -- center of word horizontally
pos.y + pos.h / 2 , -- center of word vertically
pos.x + pos.w / 2 - self._current_indicator_pos . w / 2 ,
pos.y + pos.h / 2 - self._current_indicator_pos . h / 2
)
end
UIManager : setDirty ( self.dialog , " ui " , self._current_indicator_pos )
return true
2022-03-12 19:16:50 +08:00
end
function ReaderHighlight : onStartHighlightIndicator ( )
2024-05-20 20:20:50 +01:00
-- disable long-press icon (poke-ball), as it is triggered constantly due to NT devices needing a workaround for text selection to work.
self.long_hold_reached_action = function ( ) end
2022-03-12 19:16:50 +08:00
if self.view . visible_area and not self._current_indicator_pos then
-- set start position to centor of page
local rect = self._previous_indicator_pos
if not rect then
rect = Geom : new ( )
rect.x = self.view . visible_area.w / 2
rect.y = self.view . visible_area.h / 2
rect.w = Size.item . height_default
rect.h = rect.w
end
self._current_indicator_pos = rect
self.view . highlight.indicator = rect
UIManager : setDirty ( self.dialog , " ui " , rect )
return true
end
return false
end
function ReaderHighlight : onStopHighlightIndicator ( need_clear_selection )
if self._current_indicator_pos then
local rect = self._current_indicator_pos
self._previous_indicator_pos = rect
self._start_indicator_highlight = false
self._current_indicator_pos = nil
self.view . highlight.indicator = nil
UIManager : setDirty ( self.dialog , " ui " , rect )
if need_clear_selection then
self : clear ( )
end
return true
end
return false
end
function ReaderHighlight : onMoveHighlightIndicator ( args )
if self.view . visible_area and self._current_indicator_pos then
local dx , dy , quick_move = unpack ( args )
2022-10-10 22:21:27 +02:00
local quick_move_distance_dx = self.view . visible_area.w * ( 1 / 5 ) -- quick move distance: fifth of visible_area
local quick_move_distance_dy = self.view . visible_area.h * ( 1 / 5 )
2024-05-20 20:20:50 +01:00
-- single move distance, user adjustable, default value (4) capable to move on word with small font size and narrow line height
2024-06-05 21:50:22 +01:00
local move_distance = Size.item . height_default / ( G_reader_settings : readSetting ( " highlight_non_touch_factor " ) or 4 )
2022-03-12 19:16:50 +08:00
local rect = self._current_indicator_pos : copy ( )
2022-04-14 14:59:36 +08:00
if quick_move then
rect.x = rect.x + quick_move_distance_dx * dx
rect.y = rect.y + quick_move_distance_dy * dy
else
2022-05-05 21:00:22 +02:00
local now = time : now ( )
2022-04-14 14:59:36 +08:00
if dx == self._last_indicator_move_args . dx and dy == self._last_indicator_move_args . dy then
local diff = now - self._last_indicator_move_args . time
2024-05-20 20:20:50 +01:00
-- if user presses same arrow key within 1 second (default, user adjustable), speed up
2022-04-14 14:59:36 +08:00
-- double press: 4 single move distances, usually move to next word or line
-- triple press: 16 single distances, usually skip several words or lines
2024-05-20 20:20:50 +01:00
-- quadruple press: 64 single distances, almost move to screen edge
if G_reader_settings : nilOrTrue ( " highlight_non_touch_spedup " ) then
-- user selects whether to use 'constant' or [this] 'sped up' rate (speed-up on by default)
2024-06-05 21:50:22 +01:00
local t_inter = G_reader_settings : readSetting ( " highlight_non_touch_interval " ) or 1
if diff < time.s ( t_inter ) then
2024-05-20 20:20:50 +01:00
move_distance = self._last_indicator_move_args . distance * 4
end
2022-04-14 14:59:36 +08:00
end
end
rect.x = rect.x + move_distance * dx
rect.y = rect.y + move_distance * dy
self._last_indicator_move_args . distance = move_distance
self._last_indicator_move_args . dx = dx
self._last_indicator_move_args . dy = dy
self._last_indicator_move_args . time = now
end
2022-03-12 19:16:50 +08:00
if rect.x < 0 then
rect.x = 0
end
if rect.x + rect.w > self.view . visible_area.w then
rect.x = self.view . visible_area.w - rect.w
end
if rect.y < 0 then
rect.y = 0
end
if rect.y + rect.h > self.view . visible_area.h then
rect.y = self.view . visible_area.h - rect.h
end
UIManager : setDirty ( self.dialog , " ui " , self._current_indicator_pos )
self._current_indicator_pos = rect
self.view . highlight.indicator = rect
UIManager : setDirty ( self.dialog , " ui " , rect )
if self._start_indicator_highlight then
self : onHoldPan ( nil , self : _createHighlightGesture ( " hold_pan " ) )
end
return true
end
return false
end
function ReaderHighlight : _createHighlightGesture ( gesture )
local point = self._current_indicator_pos : copy ( )
point.x = point.x + point.w / 2
point.y = point.y + point.h / 2
point.w = 0
point.h = 0
return {
ges = gesture ,
pos = point ,
2022-05-05 21:00:22 +02:00
time = time.realtime ( ) ,
2022-03-12 19:16:50 +08:00
}
end
2013-10-18 22:38:07 +02:00
return ReaderHighlight