aboutsummaryrefslogtreecommitdiff
path: root/plugins/dragdropselected.lua
blob: 6bdb101d8c91f78e789cd9f369e03f5aecf7b27c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
-- mod-version:2 -- lite-xl 2.0
--[[
    dragdropselected.lua
    provides basic drag and drop of selected text (in same document)
    version: 20200627_133351
    originally by SwissalpS

    TODO: use OS drag and drop events
    TODO: change mouse cursor when duplicating
    TODO: add dragging image
--]]
local DocView = require "core.docview"
local core = require "core"
local keymap = require "core.keymap"
local style = require "core.style"

-- helper function for on_mouse_pressed to determine if mouse down is in selection
-- iLine is line number where mouse down happened
-- iCol is column where mouse down happened
-- iSelLine1 is line number where selection starts
-- iSelCol1 is column where selection starts
-- iSelLine2 is line number where selection ends
-- iSelCol2 is column where selection ends
local function isInSelection(iLine, iCol, iSelLine1, iSelCol1, iSelLine2, iSelCol2)
    if iLine < iSelLine1 then return false end
    if iLine > iSelLine2 then return false end
    if (iLine == iSelLine1) and (iCol < iSelCol1) then return false end
    if (iLine == iSelLine2) and (iCol > iSelCol2) then return false end
    return true
end -- isInSelection

-- distance between two points
local function distance(x1, y1, x2, y2)
    return math.sqrt(math.pow(x2-x1, 2)+math.pow(y2-y1, 2))
end

local min_drag = style.code_font:get_width(" ")

-- override DocView:on_mouse_moved
local on_mouse_moved = DocView.on_mouse_moved
function DocView:on_mouse_moved(x, y, ...)

    local sCursor = nil

    -- make sure we only act if previously on_mouse_pressed was in selection
    if self.bClickedIntoSelection and
       ( -- we are already dragging or we moved enough to start dragging
         not self.drag_start_loc or
         distance(self.drag_start_loc[1],self.drag_start_loc[2], x, y) > min_drag
       ) then
        self.drag_start_loc = nil

        -- show that we are dragging something
        sCursor = 'hand'

        -- calculate line and column for current mouse position
        local iLine, iCol = self:resolve_screen_position(x, y)
        local iSelLine1 = self.dragged_selection[1]
        local iSelCol1  = self.dragged_selection[2]
        local iSelLine2 = self.dragged_selection[3]
        local iSelCol2  = self.dragged_selection[4]
        self.doc:set_selection(iSelLine1, iSelCol1, iSelLine2, iSelCol2)
        if not isInSelection(iLine, iCol, iSelLine1, iSelCol1, iSelLine2, iSelCol2) then
            -- show cursor only if outside selection
            self.doc:add_selection(iLine, iCol)
        end
        -- update scroll position
        self:scroll_to_line(iLine, true)
    end -- if previously clicked into selection

    -- hand off to 'old' on_mouse_moved()
    on_mouse_moved(self, x, y, ...)
    -- override cursor as needed
    if sCursor then self.cursor = sCursor end

end -- DocView:on_mouse_moved

-- override DocView:on_mouse_pressed
local on_mouse_pressed = DocView.on_mouse_pressed
function DocView:on_mouse_pressed(button, x, y, clicks)
    local caught = DocView.super.on_mouse_pressed(self, button, x, y, clicks)
    if caught then
        return caught
    end
    -- no need to proceed if not left button or has no selection
    if ('left' ~= button)
      or (not self.doc:has_selection())
      or (1 < clicks) then
        return on_mouse_pressed(self, button, x, y, clicks)
    end
    -- convert pixel coordinates to line and column coordinates
    local iLine, iCol = self:resolve_screen_position(x, y)
    -- get selection coordinates
    local iSelLine1, iSelCol1, iSelLine2, iSelCol2 = self.doc:get_selection(true)
    -- set flag for on_mouse_released and on_mouse_moved() methods to detect dragging
    self.bClickedIntoSelection = isInSelection(iLine, iCol, iSelLine1, iSelCol1,
                                               iSelLine2, iSelCol2)
    if self.bClickedIntoSelection then
        self.drag_start_loc = { x, y }
        -- stash selection for inserting later
        self.sDraggedText = self.doc:get_text(self.doc:get_selection())
        self.dragged_selection = { iSelLine1, iSelCol1, iSelLine2, iSelCol2 }
    else
        self.bClickedIntoSelection = nil
        self.dragged_selection = nil
        -- let 'old' on_mouse_pressed() do whatever it needs to do
        on_mouse_pressed(self, button, x, y, clicks)
    end
end -- DocView:on_mouse_pressed

-- override DocView:on_mouse_released()
local on_mouse_released = DocView.on_mouse_released
function DocView:on_mouse_released(button, x, y)
    local iLine, iCol = self:resolve_screen_position(x, y)
    if self.bClickedIntoSelection then
        local iSelLine1, iSelCol1, iSelLine2, iSelCol2 = table.unpack(self.dragged_selection)
        if not self.drag_start_loc
           and not isInSelection(iLine, iCol, iSelLine1, iSelCol1, iSelLine2, iSelCol2) then
            -- insert stashed selected text at current position
            if iLine < iSelLine1 or (iLine == iSelLine1 and iCol < iSelCol1) then
                -- delete first
                self.doc:set_selection(iSelLine1, iSelCol1, iSelLine2, iSelCol2)
                if not keymap.modkeys['ctrl'] then
                    self.doc:delete_to(0)
                end
                self.doc:set_selection(iLine, iCol)
                self.doc:text_input(self.sDraggedText)
            else
                -- insert first
                self.doc:set_selection(iLine, iCol)
                self.doc:text_input(self.sDraggedText)
                self.doc:set_selection(iSelLine1, iSelCol1, iSelLine2, iSelCol2)
                if not keymap.modkeys['ctrl'] then
                    self.doc:delete_to(0)
                end
                self.doc:set_selection(iLine, iCol)
            end
        elseif self.drag_start_loc then
            -- deselect only if the drag never happened
            self.doc:set_selection(iLine, iCol)
        end
        -- unset stash and flag(s) TODO:
        self.sDraggedText = ''
        self.bClickedIntoSelection = nil
    end

    -- hand over to old handler
    on_mouse_released(self, button, x, y)

end -- DocView:on_mouse_released

-- override DocView:draw_caret()
local draw_caret = DocView.draw_caret
function DocView:draw_caret(x, y)
    if self.bClickedIntoSelection then
        local iLine, iCol = self:resolve_screen_position(x, y)
        -- don't show carets inside selections
        if isInSelection(iLine, iCol,
                         self.dragged_selection[1], self.dragged_selection[2],
                         self.dragged_selection[3], self.dragged_selection[4]) then
            return
        end
    end
    draw_caret(self, x, y)
end -- DocView:draw_caret()