aboutsummaryrefslogtreecommitdiff
path: root/plugins/dragdropselected.lua
blob: 3c6583bf7744be0cc4d6bc240b459fc75a04ca6d (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
166
167
168
169
170
171
172
173
174
175
176
177
-- mod-version:3
--[[
  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()