Convert consecutive FSF copyright years to ranges.
[bpt/emacs.git] / lisp / dnd.el
CommitLineData
e9dc55ba 1;;; dnd.el --- drag and drop support. -*- coding: utf-8 -*-
6018020d 2
73b0cd50 3;; Copyright (C) 2005-2011
e9dc55ba 4;; Free Software Foundation, Inc.
6018020d 5
e9dc55ba 6;; Author: Jan Djärv <jan.h.d@swipnet.se>
6018020d
JD
7;; Maintainer: FSF
8;; Keywords: window, drag, drop
bd78fa1d 9;; Package: emacs
6018020d
JD
10
11;; This file is part of GNU Emacs.
12
eb3fa2cf 13;; GNU Emacs is free software: you can redistribute it and/or modify
6018020d 14;; it under the terms of the GNU General Public License as published by
eb3fa2cf
GM
15;; the Free Software Foundation, either version 3 of the License, or
16;; (at your option) any later version.
6018020d
JD
17
18;; GNU Emacs is distributed in the hope that it will be useful,
19;; but WITHOUT ANY WARRANTY; without even the implied warranty of
20;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21;; GNU General Public License for more details.
22
23;; You should have received a copy of the GNU General Public License
eb3fa2cf 24;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>.
6018020d
JD
25
26;;; Commentary:
27
28;; This file provides the generic handling of the drop part only.
29;; Different DND backends (X11, W32, etc.) that handle the platform
30;; specific DND parts call the functions here to do final delivery of
31;; a drop.
32
33;;; Code:
34
35;;; Customizable variables
36
37
01147a50 38;;;###autoload
6018020d 39(defcustom dnd-protocol-alist
1e8780b1
DN
40 `((,(purecopy "^file:///") . dnd-open-local-file) ; XDND format.
41 (,(purecopy "^file://") . dnd-open-file) ; URL with host
42 (,(purecopy "^file:") . dnd-open-local-file) ; Old KDE, Motif, Sun
43 (,(purecopy "^\\(https?\\|ftp\\|file\\|nfs\\)://") . dnd-open-file)
c79b0f8f 44 )
6018020d
JD
45
46 "The functions to call for different protocols when a drop is made.
47This variable is used by `dnd-handle-one-url' and `dnd-handle-file-name'.
48The list contains of (REGEXP . FUNCTION) pairs.
49The functions shall take two arguments, URL, which is the URL dropped and
50ACTION which is the action to be performed for the drop (move, copy, link,
51private or ask).
52If no match is found here, and the value of `browse-url-browser-function'
53is a pair of (REGEXP . FUNCTION), those regexps are tried for a match.
54If no match is found, the URL is inserted as text by calling `dnd-insert-text'.
55The function shall return the action done (move, copy, link or private)
56if some action was made, or nil if the URL is ignored."
57 :version "22.1"
1ed8284d 58 :type '(repeat (cons (regexp) (function)))
6018020d
JD
59 :group 'dnd)
60
61
c79b0f8f
JD
62(defcustom dnd-open-remote-file-function
63 (if (eq system-type 'windows-nt)
8e9e7fa1 64 'dnd-open-local-file
c79b0f8f
JD
65 'dnd-open-remote-url)
66 "The function to call when opening a file on a remote machine.
67The function will be called with two arguments; URI and ACTION. See
68`dnd-open-file' for details.
69If nil, then dragging remote files into Emacs will result in an error.
8e9e7fa1
JR
70Predefined functions are `dnd-open-local-file' and `dnd-open-remote-url'.
71`dnd-open-local-file' attempts to open a remote file using its UNC name and
72is the default on MS-Windows. `dnd-open-remote-url' uses `url-handler-mode'
73and is the default except for MS-Windows."
c79b0f8f
JD
74 :version "22.1"
75 :type 'function
76 :group 'dnd)
77
6018020d
JD
78
79(defcustom dnd-open-file-other-window nil
80 "If non-nil, always use find-file-other-window to open dropped files."
81 :version "22.1"
82 :type 'boolean
83 :group 'dnd)
84
85
86;; Functions
87
01aa8c41 88(defun dnd-handle-one-url (window action url)
6018020d
JD
89 "Handle one dropped url by calling the appropriate handler.
90The handler is first located by looking at `dnd-protocol-alist'.
91If no match is found here, and the value of `browse-url-browser-function'
92is a pair of (REGEXP . FUNCTION), those regexps are tried for a match.
93If no match is found, just call `dnd-insert-text'.
a3545af4 94WINDOW is where the drop happened, ACTION is the action for the drop,
01aa8c41 95URL is what has been dropped.
6018020d
JD
96Returns ACTION."
97 (require 'browse-url)
01aa8c41 98 (let (ret)
6018020d
JD
99 (or
100 (catch 'done
101 (dolist (bf dnd-protocol-alist)
01aa8c41
YM
102 (when (string-match (car bf) url)
103 (setq ret (funcall (cdr bf) url action))
6018020d
JD
104 (throw 'done t)))
105 nil)
106 (when (not (functionp browse-url-browser-function))
107 (catch 'done
108 (dolist (bf browse-url-browser-function)
01aa8c41 109 (when (string-match (car bf) url)
6018020d 110 (setq ret 'private)
01aa8c41 111 (funcall (cdr bf) url action)
6018020d
JD
112 (throw 'done t)))
113 nil))
114 (progn
01aa8c41 115 (dnd-insert-text window action url)
6018020d
JD
116 (setq ret 'private)))
117 ret))
118
119
120(defun dnd-get-local-file-uri (uri)
121 "Return an uri converted to file:/// syntax if uri is a local file.
122Return nil if URI is not a local file."
123
124 ;; The hostname may be our hostname, in that case, convert to a local
125 ;; file. Otherwise return nil. TODO: How about an IP-address as hostname?
126 (let ((hostname (when (string-match "^file://\\([^/]*\\)" uri)
127 (downcase (match-string 1 uri))))
128 (system-name-no-dot
129 (downcase (if (string-match "^[^\\.]+" system-name)
130 (match-string 0 system-name)
131 system-name))))
132 (when (and hostname
133 (or (string-equal "localhost" hostname)
134 (string-equal (downcase system-name) hostname)
135 (string-equal system-name-no-dot hostname)))
136 (concat "file://" (substring uri (+ 7 (length hostname)))))))
137
adc0b793
JD
138(defsubst dnd-unescape-uri (uri)
139 (replace-regexp-in-string
140 "%[A-Fa-f0-9][A-Fa-f0-9]"
141 (lambda (arg)
142 (let ((str (make-string 1 0)))
143 (aset str 0 (string-to-number (substring arg 1) 16))
144 str))
145 uri t t))
146
147;; http://lists.gnu.org/archive/html/emacs-devel/2006-05/msg01060.html
6018020d
JD
148(defun dnd-get-local-file-name (uri &optional must-exist)
149 "Return file name converted from file:/// or file: syntax.
150URI is the uri for the file. If MUST-EXIST is given and non-nil,
151only return non-nil if the file exists.
152Return nil if URI is not a local file."
153 (let ((f (cond ((string-match "^file:///" uri) ; XDND format.
154 (substring uri (1- (match-end 0))))
155 ((string-match "^file:" uri) ; Old KDE, Motif, Sun
156 (substring uri (match-end 0))))))
adc0b793
JD
157 (and f (setq f (decode-coding-string (dnd-unescape-uri f)
158 (or file-name-coding-system
159 default-file-name-coding-system))))
160 (when (and f must-exist (not (file-readable-p f)))
161 (setq f nil))
83b8ea28 162 f))
6018020d
JD
163
164(defun dnd-open-local-file (uri action)
165 "Open a local file.
166The file is opened in the current window, or a new window if
167`dnd-open-file-other-window' is set. URI is the url for the file,
168and must have the format file:file-name or file:///file-name.
8e9e7fa1
JR
169The last / in file:/// is part of the file name. If the system
170natively supports unc file names, then remote urls of the form
171file://server-name/file-name will also be handled by this function.
172An alternative for systems that do not support unc file names is
173`dnd-open-remote-url'. ACTION is ignored."
6018020d
JD
174
175 (let* ((f (dnd-get-local-file-name uri t)))
176 (if (and f (file-readable-p f))
177 (progn
178 (if dnd-open-file-other-window
179 (find-file-other-window f)
180 (find-file f))
181 'private)
182 (error "Can not read %s" uri))))
183
c79b0f8f
JD
184(defun dnd-open-remote-url (uri action)
185 "Open a remote file with `find-file' and `url-handler-mode'.
186Turns `url-handler-mode' on if not on before. The file is opened in the
187current window, or a new window if `dnd-open-file-other-window' is set.
188URI is the url for the file. ACTION is ignored."
189 (progn
1df4d4a8 190 (require 'url-handlers)
c79b0f8f
JD
191 (or url-handler-mode (url-handler-mode))
192 (if dnd-open-file-other-window
193 (find-file-other-window uri)
194 (find-file uri))
195 'private))
196
197
6018020d
JD
198(defun dnd-open-file (uri action)
199 "Open a local or remote file.
200The file is opened in the current window, or a new window if
201`dnd-open-file-other-window' is set. URI is the url for the file,
202and must have the format file://hostname/file-name. ACTION is ignored.
203The last / in file://hostname/ is part of the file name."
204
205 ;; The hostname may be our hostname, in that case, convert to a local
206 ;; file. Otherwise return nil.
207 (let ((local-file (dnd-get-local-file-uri uri)))
208 (if local-file (dnd-open-local-file local-file action)
c79b0f8f
JD
209 (if dnd-open-remote-file-function
210 (funcall dnd-open-remote-file-function uri action)
211 (error "Remote files not supported")))))
6018020d
JD
212
213
214(defun dnd-insert-text (window action text)
215 "Insert text at point or push to the kill ring if buffer is read only.
216TEXT is the text as a string, WINDOW is the window where the drop happened."
217 (if (or buffer-read-only
218 (not (windowp window)))
219 (progn
220 (kill-new text)
8a26c165 221 (message "%s"
6018020d
JD
222 (substitute-command-keys
223 "The dropped text can be accessed with \\[yank]")))
224 (insert text))
225 action)
226
227
228(provide 'dnd)
229
230;;; dnd.el ends here