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