aboutsummaryrefslogtreecommitdiff
-- 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((x2-x1)^2+(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()