diff options
| author | Jan Djärv | 2004-02-03 16:55:30 +0000 |
|---|---|---|
| committer | Jan Djärv | 2004-02-03 16:55:30 +0000 |
| commit | 133aad747dac3476722b7c75ef1578e2e905d8f9 (patch) | |
| tree | c55c2e9a67043a4037c0d53dd66fb218d7a4f3f5 | |
| parent | 69eff41f7eea76db81a9e4a441d7daf747a3cf68 (diff) | |
| download | emacs-133aad747dac3476722b7c75ef1578e2e905d8f9.tar.gz emacs-133aad747dac3476722b7c75ef1578e2e905d8f9.zip | |
* x-dnd.el: New file for drag and drop.
* term/x-win.el: require x-dnd, set after-make-frame-functions
to x-dnd-init-frame, let x-dnd-handle-drag-n-drop-event handle
drag-n-drop event.
* dired.el (dired-dnd-test-function, dired-dnd-popup-notice)
(dired-dnd-do-ask-action, dired-dnd-handle-local-file)
(dired-dnd-handle-file): New functions for drag and drop support.
(dired-mode): Initialize drag and drop if x-dnd present.
| -rw-r--r-- | lisp/ChangeLog | 13 | ||||
| -rw-r--r-- | lisp/dired.el | 98 | ||||
| -rw-r--r-- | lisp/term/x-win.el | 6 | ||||
| -rw-r--r-- | lisp/x-dnd.el | 605 |
4 files changed, 721 insertions, 1 deletions
diff --git a/lisp/ChangeLog b/lisp/ChangeLog index 8e5ad91126e..95cd73fcc3f 100644 --- a/lisp/ChangeLog +++ b/lisp/ChangeLog | |||
| @@ -1,3 +1,16 @@ | |||
| 1 | 2004-02-03 Jan Dj,Ad(Brv <jan.h.d@swipnet.se> | ||
| 2 | |||
| 3 | * x-dnd.el: New file for drag and drop. | ||
| 4 | |||
| 5 | * term/x-win.el: require x-dnd, set after-make-frame-functions | ||
| 6 | to x-dnd-init-frame, let x-dnd-handle-drag-n-drop-event handle | ||
| 7 | drag-n-drop event. | ||
| 8 | |||
| 9 | * dired.el (dired-dnd-test-function, dired-dnd-popup-notice) | ||
| 10 | (dired-dnd-do-ask-action, dired-dnd-handle-local-file) | ||
| 11 | (dired-dnd-handle-file): New functions for drag and drop support. | ||
| 12 | (dired-mode): Initialize drag and drop if x-dnd present. | ||
| 13 | |||
| 1 | 2004-02-02 Benjamin Rutt <brutt@bloomington.in.us> | 14 | 2004-02-02 Benjamin Rutt <brutt@bloomington.in.us> |
| 2 | 15 | ||
| 3 | * diff-mode.el (diff-mode-shared-map): Bind q to `quit-window'. | 16 | * diff-mode.el (diff-mode-shared-map): Bind q to `quit-window'. |
diff --git a/lisp/dired.el b/lisp/dired.el index c15134e3bc9..c3511baea47 100644 --- a/lisp/dired.el +++ b/lisp/dired.el | |||
| @@ -1292,7 +1292,16 @@ Keybindings: | |||
| 1292 | (or switches dired-listing-switches)) | 1292 | (or switches dired-listing-switches)) |
| 1293 | (set (make-local-variable 'font-lock-defaults) '(dired-font-lock-keywords t)) | 1293 | (set (make-local-variable 'font-lock-defaults) '(dired-font-lock-keywords t)) |
| 1294 | (dired-sort-other dired-actual-switches t) | 1294 | (dired-sort-other dired-actual-switches t) |
| 1295 | (run-hooks 'dired-mode-hook)) | 1295 | (run-hooks 'dired-mode-hook) |
| 1296 | (when (featurep 'x-dnd) | ||
| 1297 | (make-variable-buffer-local 'x-dnd-test-function) | ||
| 1298 | (make-variable-buffer-local 'x-dnd-protocol-alist) | ||
| 1299 | (setq x-dnd-test-function 'dired-dnd-test-function) | ||
| 1300 | (setq x-dnd-protocol-alist | ||
| 1301 | (append '(("^file:///" . dired-dnd-handle-local-file) | ||
| 1302 | ("^file://" . dired-dnd-handle-file) | ||
| 1303 | ("^file:" . dired-dnd-handle-local-file)) | ||
| 1304 | x-dnd-protocol-alist)))) | ||
| 1296 | 1305 | ||
| 1297 | ;; Idiosyncratic dired commands that don't deal with marks. | 1306 | ;; Idiosyncratic dired commands that don't deal with marks. |
| 1298 | 1307 | ||
| @@ -3131,6 +3140,93 @@ true then the type of the file linked to by FILE is printed instead." | |||
| 3131 | 3140 | ||
| 3132 | (autoload 'dired-query "dired-aux") | 3141 | (autoload 'dired-query "dired-aux") |
| 3133 | 3142 | ||
| 3143 | |||
| 3144 | ;;;; Drag and drop support | ||
| 3145 | |||
| 3146 | (defun dired-dnd-test-function (window action types) | ||
| 3147 | "The test function for drag and drop into dired buffers. | ||
| 3148 | WINDOW is where the mouse is when this function is called. It may be a frame | ||
| 3149 | if the mouse is over the menu bar, scroll bar or tool bar. | ||
| 3150 | ACTION is the suggested action from the source, and TYPES are the | ||
| 3151 | types the drop data can have. This function only accepts drops with | ||
| 3152 | types in `x-dnd-known-types'. It returns the action suggested by the source." | ||
| 3153 | (let ((type (x-dnd-choose-type types))) | ||
| 3154 | (if type | ||
| 3155 | (cons action type) | ||
| 3156 | nil))) | ||
| 3157 | |||
| 3158 | (defun dired-dnd-popup-notice () | ||
| 3159 | (x-popup-dialog | ||
| 3160 | t | ||
| 3161 | '("Recursive copies not enabled.\nSee variable dired-recursive-copies." | ||
| 3162 | ("Ok" . nil)))) | ||
| 3163 | |||
| 3164 | |||
| 3165 | (defun dired-dnd-do-ask-action (uri) | ||
| 3166 | ;; No need to get actions and descriptions from the source, | ||
| 3167 | ;; we only have three actions anyway. | ||
| 3168 | (let ((action (x-popup-menu | ||
| 3169 | t | ||
| 3170 | (list "What action?" | ||
| 3171 | (cons "" | ||
| 3172 | '(("Copy here" . copy) | ||
| 3173 | ("Move here" . move) | ||
| 3174 | ("Link here" . link) | ||
| 3175 | "--" | ||
| 3176 | ("Cancel" . nil))))))) | ||
| 3177 | (if action | ||
| 3178 | (dired-dnd-handle-local-file uri action) | ||
| 3179 | nil))) | ||
| 3180 | |||
| 3181 | (defun dired-dnd-handle-local-file (uri action) | ||
| 3182 | "Copy, move or link a file to the dired directory. | ||
| 3183 | URI is the file to handle, ACTION is one of copy, move, link or ask. | ||
| 3184 | Ask means pop up a menu for the user to select one of copy, move or link." | ||
| 3185 | (require 'dired-aux) | ||
| 3186 | (let* ((from (x-dnd-get-local-file-name uri t)) | ||
| 3187 | (to (if from (concat (dired-current-directory) | ||
| 3188 | (file-name-nondirectory from)) | ||
| 3189 | nil))) | ||
| 3190 | (if from | ||
| 3191 | (cond ((or (eq action 'copy) | ||
| 3192 | (eq action 'private)) ; Treat private as copy. | ||
| 3193 | |||
| 3194 | ;; If copying a directory and dired-recursive-copies is nil, | ||
| 3195 | ;; dired-copy-file silently fails. Pop up a notice. | ||
| 3196 | (if (and (file-directory-p from) | ||
| 3197 | (not dired-recursive-copies)) | ||
| 3198 | (dired-dnd-popup-notice) | ||
| 3199 | (progn | ||
| 3200 | (dired-copy-file from to 1) | ||
| 3201 | (dired-relist-entry to) | ||
| 3202 | action))) | ||
| 3203 | |||
| 3204 | ((eq action 'move) | ||
| 3205 | (dired-rename-file from to 1) | ||
| 3206 | (dired-relist-entry to) | ||
| 3207 | action) | ||
| 3208 | |||
| 3209 | ((eq action 'link) | ||
| 3210 | (make-symbolic-link from to 1) | ||
| 3211 | (dired-relist-entry to) | ||
| 3212 | action) | ||
| 3213 | |||
| 3214 | ((eq action 'ask) | ||
| 3215 | (dired-dnd-do-ask-action uri)) | ||
| 3216 | |||
| 3217 | (t nil))))) | ||
| 3218 | |||
| 3219 | (defun dired-dnd-handle-file (uri action) | ||
| 3220 | "Copy, move or link a file to the dired directory if it is a local file. | ||
| 3221 | URI is the file to handle. If the hostname in the URI isn't local, do nothing. | ||
| 3222 | ACTION is one of copy, move, link or ask. | ||
| 3223 | Ask means pop up a menu for the user to select one of copy, move or link." | ||
| 3224 | (let ((local-file (x-dnd-get-local-file-uri uri))) | ||
| 3225 | (if local-file (dired-dnd-handle-local-file local-file action) | ||
| 3226 | nil))) | ||
| 3227 | |||
| 3228 | |||
| 3229 | |||
| 3134 | (if (eq system-type 'vax-vms) | 3230 | (if (eq system-type 'vax-vms) |
| 3135 | (load "dired-vms")) | 3231 | (load "dired-vms")) |
| 3136 | 3232 | ||
diff --git a/lisp/term/x-win.el b/lisp/term/x-win.el index 0e6d9be3f7d..09657fb848f 100644 --- a/lisp/term/x-win.el +++ b/lisp/term/x-win.el | |||
| @@ -76,6 +76,7 @@ | |||
| 76 | (require 'select) | 76 | (require 'select) |
| 77 | (require 'menu-bar) | 77 | (require 'menu-bar) |
| 78 | (require 'fontset) | 78 | (require 'fontset) |
| 79 | (require 'x-dnd) | ||
| 79 | 80 | ||
| 80 | (defvar x-invocation-args) | 81 | (defvar x-invocation-args) |
| 81 | 82 | ||
| @@ -2452,6 +2453,7 @@ order until succeed.") | |||
| 2452 | ;; Turn on support for mouse wheels. | 2453 | ;; Turn on support for mouse wheels. |
| 2453 | (mouse-wheel-mode 1) | 2454 | (mouse-wheel-mode 1) |
| 2454 | 2455 | ||
| 2456 | |||
| 2455 | ;; Enable CLIPBOARD copy/paste through menu bar commands. | 2457 | ;; Enable CLIPBOARD copy/paste through menu bar commands. |
| 2456 | (menu-bar-enable-clipboard) | 2458 | (menu-bar-enable-clipboard) |
| 2457 | 2459 | ||
| @@ -2469,5 +2471,9 @@ order until succeed.") | |||
| 2469 | (cons "Paste" (cons "Paste text from clipboard or kill ring" | 2471 | (cons "Paste" (cons "Paste text from clipboard or kill ring" |
| 2470 | 'x-clipboard-yank))) | 2472 | 'x-clipboard-yank))) |
| 2471 | 2473 | ||
| 2474 | ;; Initiate drag and drop | ||
| 2475 | (add-hook 'after-make-frame-functions 'x-dnd-init-frame) | ||
| 2476 | (global-set-key [drag-n-drop] 'x-dnd-handle-drag-n-drop-event) | ||
| 2477 | |||
| 2472 | ;;; arch-tag: f1501302-db8b-4d95-88e3-116697d89f78 | 2478 | ;;; arch-tag: f1501302-db8b-4d95-88e3-116697d89f78 |
| 2473 | ;;; x-win.el ends here | 2479 | ;;; x-win.el ends here |
diff --git a/lisp/x-dnd.el b/lisp/x-dnd.el new file mode 100644 index 00000000000..85a691dea1d --- /dev/null +++ b/lisp/x-dnd.el | |||
| @@ -0,0 +1,605 @@ | |||
| 1 | |||
| 2 | ;;; x-dnd.el --- drag and drop support for X. | ||
| 3 | |||
| 4 | ;; Copyright (C) 2004 | ||
| 5 | ;; Free Software Foundation, Inc. | ||
| 6 | |||
| 7 | ;; Author: Jan Dj,Ad(Brv <jan.h.d@swipnet.se> | ||
| 8 | ;; Maintainer: FSF | ||
| 9 | ;; Keywords: window, drag, drop | ||
| 10 | |||
| 11 | ;; This file is part of GNU Emacs. | ||
| 12 | |||
| 13 | ;; GNU Emacs is free software; you can redistribute it and/or modify | ||
| 14 | ;; it under the terms of the GNU General Public License as published by | ||
| 15 | ;; the Free Software Foundation; either version 2, or (at your option) | ||
| 16 | ;; any later version. | ||
| 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 | ||
| 24 | ;; along with GNU Emacs; see the file COPYING. If not, write to the | ||
| 25 | ;; Free Software Foundation, Inc., 59 Temple Place - Suite 330, | ||
| 26 | ;; Boston, MA 02111-1307, USA. | ||
| 27 | |||
| 28 | ;;; Commentary: | ||
| 29 | |||
| 30 | ;; This file provides the drop part only. Currently supported protocols | ||
| 31 | ;; are XDND and the old KDE 1.x protocol. | ||
| 32 | |||
| 33 | ;;; Code: | ||
| 34 | |||
| 35 | ;;; Customizable variables | ||
| 36 | |||
| 37 | |||
| 38 | (defcustom x-dnd-test-function 'x-dnd-default-test-function | ||
| 39 | "The function drag and drop uses to determine if to accept or reject a drop. | ||
| 40 | The function takes three arguments, WINDOW ACTION and TYPES. | ||
| 41 | WINDOW is where the mouse is when the function is called. WINDOW may be a | ||
| 42 | frame if the mouse isn't over a real window (i.e. menu bar, tool bar or | ||
| 43 | scroll bar). ACTION is the suggested action from the drag and drop source, | ||
| 44 | one of the symbols move, copy link or ask. TYPES is a list of available types | ||
| 45 | for the drop. | ||
| 46 | |||
| 47 | The function shall return nil to reject the drop or a cons with two values, | ||
| 48 | the wanted action as car and the wanted type as cdr. The wanted action | ||
| 49 | can be copy, move, link, ask or private. | ||
| 50 | The default value for this variable is `x-dnd-default-test-function'." | ||
| 51 | :type 'symbol | ||
| 52 | :group 'x) | ||
| 53 | |||
| 54 | (defcustom x-dnd-protocol-alist | ||
| 55 | '( | ||
| 56 | ("^file:///" . x-dnd-open-local-file) ; XDND format. | ||
| 57 | ("^file://" . x-dnd-open-file) ; URL with host | ||
| 58 | ("^file:" . x-dnd-open-local-file) ; Old KDE, Motif, Sun | ||
| 59 | ) | ||
| 60 | |||
| 61 | "The functions to call for different protocols when a drop is made. | ||
| 62 | This variable is used by `x-dnd-handle-uri-list' and `x-dnd-handle-moz-url'. | ||
| 63 | The list contains of (REGEXP . FUNCTION) pairs. | ||
| 64 | The functions shall take two arguments, URL, which is the URL dropped and | ||
| 65 | ACTION which is the action to be performed for the drop (move, copy, link, | ||
| 66 | private or ask). | ||
| 67 | If no match is found here, and the value of `browse-url-browser-function' | ||
| 68 | is a pair of (REGEXP . FUNCTION), those regexps are tried for a match. | ||
| 69 | Insertion of text is not handeled by these functions, see `x-dnd-types-alist' | ||
| 70 | for that. | ||
| 71 | The function shall return the action done (move, copy, link or private) | ||
| 72 | if some action was made, or nil if the URL is ignored." | ||
| 73 | :type 'alist | ||
| 74 | :group 'x) | ||
| 75 | |||
| 76 | |||
| 77 | (defcustom x-dnd-types-alist | ||
| 78 | '( | ||
| 79 | ("text/uri-list" . x-dnd-handle-uri-list) | ||
| 80 | ("text/x-moz-url" . x-dnd-handle-moz-url) | ||
| 81 | ("FILE_NAME" . x-dnd-handle-uri-list) | ||
| 82 | ("_NETSCAPE_URL" . x-dnd-handle-uri-list) | ||
| 83 | ("UTF8_STRING" . x-dnd-insert-utf8-text) | ||
| 84 | ("text/plain;charset=UTF-8" . x-dnd-insert-utf8-text) | ||
| 85 | ("text/plain;charset=utf-8" . x-dnd-insert-utf8-text) | ||
| 86 | ("text/unicode" . x-dnd-insert-utf16-text) | ||
| 87 | ("text/plain" . x-dnd-insert-text) | ||
| 88 | ("STRING" . x-dnd-insert-text) | ||
| 89 | ("TEXT" . x-dnd-insert-text) | ||
| 90 | ) | ||
| 91 | "Which function to call to handle a drop of that type. | ||
| 92 | If the type for the drop is not present, or the function is nil, | ||
| 93 | the drop is rejected. The function takes three arguments, WINDOW, ACTION | ||
| 94 | and DATA. WINDOW is where the drop occured, ACTION is the action for | ||
| 95 | this drop (copy, move, link, private or ask) as determined by a previous | ||
| 96 | call to `x-dnd-test-function'. DATA is the drop data. | ||
| 97 | The function shall return the action used (copy, move, link or private) if drop | ||
| 98 | is successful, nil if not." | ||
| 99 | :type 'alist | ||
| 100 | :group 'x) | ||
| 101 | |||
| 102 | (defcustom x-dnd-open-file-other-window nil | ||
| 103 | "If non-nil, always use find-file-other-window to open dropped files." | ||
| 104 | :type 'boolean | ||
| 105 | :group 'x) | ||
| 106 | |||
| 107 | ;; Internal variables | ||
| 108 | |||
| 109 | (defvar x-dnd-known-types | ||
| 110 | '("text/uri-list" | ||
| 111 | "text/x-moz-url" | ||
| 112 | "FILE_NAME" | ||
| 113 | "_NETSCAPE_URL" | ||
| 114 | "UTF8_STRING" | ||
| 115 | "text/plain;charset=UTF-8" | ||
| 116 | "text/plain;charset=utf-8" | ||
| 117 | "text/unicode" | ||
| 118 | "text/plain" | ||
| 119 | "STRING" | ||
| 120 | "TEXT" | ||
| 121 | ) | ||
| 122 | "The types accepted by default for dropped data. | ||
| 123 | The types are chosen in the order they appear in the list.") | ||
| 124 | |||
| 125 | (defvar x-dnd-current-state nil | ||
| 126 | "The current state for a drop. | ||
| 127 | This is an alist with one entry for each display. The value for each display | ||
| 128 | is a vector that contains the state for drag and drop for that display. | ||
| 129 | Elements in the vector are: | ||
| 130 | Last buffer drag was in, | ||
| 131 | last window drag was in, | ||
| 132 | types available for drop, | ||
| 133 | the action suggested by the source, | ||
| 134 | the type we want for the drop, | ||
| 135 | the action we want for the drop.") | ||
| 136 | |||
| 137 | (defvar x-dnd-empty-state [nil nil nil nil nil nil]) | ||
| 138 | |||
| 139 | |||
| 140 | |||
| 141 | (defun x-dnd-init-frame (&optional frame) | ||
| 142 | "Setup drag and drop for FRAME (i.e. create appropriate properties)." | ||
| 143 | (x-dnd-init-xdnd-for-frame frame)) | ||
| 144 | |||
| 145 | (defun x-dnd-get-state-cons-for-frame (frame-or-window) | ||
| 146 | "Return the entry in x-dnd-current-state for a frame or window." | ||
| 147 | (let* ((frame (if (framep frame-or-window) frame-or-window | ||
| 148 | (window-frame frame-or-window))) | ||
| 149 | (display (frame-parameter frame 'display))) | ||
| 150 | (if (not (assoc display x-dnd-current-state)) | ||
| 151 | (push (cons display x-dnd-empty-state) x-dnd-current-state)) | ||
| 152 | (assoc display x-dnd-current-state))) | ||
| 153 | |||
| 154 | (defun x-dnd-get-state-for-frame (frame-or-window) | ||
| 155 | "Return the state in x-dnd-current-state for a frame or window." | ||
| 156 | (cdr (x-dnd-get-state-cons-for-frame frame-or-window))) | ||
| 157 | |||
| 158 | (defun x-dnd-default-test-function (window action types) | ||
| 159 | "The default test function for drag and drop. | ||
| 160 | WINDOW is where the mouse is when this function is called. It may be a frame | ||
| 161 | if the mouse is over the menu bar, scroll bar or tool bar. | ||
| 162 | ACTION is the suggested action from the source, and TYPES are the | ||
| 163 | types the drop data can have. This function only accepts drops with | ||
| 164 | types in `x-dnd-known-types'. It always returns the action private." | ||
| 165 | (let ((type (x-dnd-choose-type types))) | ||
| 166 | (when type (cons 'private type)))) | ||
| 167 | |||
| 168 | |||
| 169 | (defun x-dnd-current-type (frame-or-window) | ||
| 170 | "Return the type we want the DND data to be in for the current drop. | ||
| 171 | FRAME-OR-WINDOW is the frame or window that the mouse is over." | ||
| 172 | (aref (x-dnd-get-state-for-frame frame-or-window) 4)) | ||
| 173 | |||
| 174 | (defun x-dnd-forget-drop (frame-or-window) | ||
| 175 | "Remove all state for the last drop. | ||
| 176 | FRAME-OR-WINDOW is the frame or window that the mouse is over." | ||
| 177 | (setcdr (x-dnd-get-state-cons-for-frame frame-or-window) x-dnd-empty-state)) | ||
| 178 | |||
| 179 | (defun x-dnd-maybe-call-test-function (window action) | ||
| 180 | "Call `x-dnd-test-function' if something has changed. | ||
| 181 | WINDOW is the window the mouse is over. ACTION is the suggested | ||
| 182 | action from the source. If nothing has changed, return the last | ||
| 183 | action and type we got from `x-dnd-test-function'." | ||
| 184 | (let ((buffer (when (and (windowp window) (window-live-p window)) | ||
| 185 | (window-buffer window))) | ||
| 186 | (current-state (x-dnd-get-state-for-frame window))) | ||
| 187 | (when (or (not (equal buffer (aref current-state 0))) | ||
| 188 | (not (equal window (aref current-state 1))) | ||
| 189 | (not (equal action (aref current-state 3)))) | ||
| 190 | (save-excursion | ||
| 191 | (when buffer (set-buffer buffer)) | ||
| 192 | (let* ((action-type (funcall x-dnd-test-function | ||
| 193 | window | ||
| 194 | action | ||
| 195 | (aref current-state 2))) | ||
| 196 | (handler (cdr (assoc (cdr action-type) x-dnd-types-alist)))) | ||
| 197 | ;; Ignore action-type if we have no handler. | ||
| 198 | (setq current-state | ||
| 199 | (x-dnd-save-state window | ||
| 200 | action | ||
| 201 | (when handler action-type))))))) | ||
| 202 | (let ((current-state (x-dnd-get-state-for-frame window))) | ||
| 203 | (cons (aref current-state 5) | ||
| 204 | (aref current-state 4)))) | ||
| 205 | |||
| 206 | (defun x-dnd-save-state (window action action-type &optional types) | ||
| 207 | "Save the state of the current drag and drop. | ||
| 208 | WINDOW is the window the mouse is over. ACTION is the action suggested | ||
| 209 | by the source. ACTION-TYPE is the result of calling `x-dnd-test-function'. | ||
| 210 | If given, TYPES are the types for the drop data that the source supports." | ||
| 211 | (let ((current-state (x-dnd-get-state-for-frame window))) | ||
| 212 | (aset current-state 5 (car action-type)) | ||
| 213 | (aset current-state 4 (cdr action-type)) | ||
| 214 | (aset current-state 3 action) | ||
| 215 | (if types (aset current-state 2 types)) | ||
| 216 | (aset current-state 1 window) | ||
| 217 | (aset current-state 0 (if (and (windowp window) | ||
| 218 | (window-live-p window)) | ||
| 219 | (window-buffer window) nil)) | ||
| 220 | (setcdr (x-dnd-get-state-cons-for-frame window) current-state))) | ||
| 221 | |||
| 222 | |||
| 223 | (defun x-dnd-test-and-save-state (window action types) | ||
| 224 | "Test if drop shall be accepted, and save the state for future reference. | ||
| 225 | ACTION is the suggested action by the source. | ||
| 226 | TYPES is a list of types the source supports." | ||
| 227 | (x-dnd-save-state window | ||
| 228 | action | ||
| 229 | (x-dnd-maybe-call-test-function window action) | ||
| 230 | types)) | ||
| 231 | |||
| 232 | (defun x-dnd-handle-one-url (window action arg) | ||
| 233 | "Handle one dropped url by calling the appropriate handler. | ||
| 234 | The handler is first localted by looking at `x-dnd-protocol-alist'. | ||
| 235 | If no match is found here, and the value of `browse-url-browser-function' | ||
| 236 | is a pair of (REGEXP . FUNCTION), those regexps are tried for a match. | ||
| 237 | If no match is found, just call `x-dnd-insert-text,A4(B. | ||
| 238 | WINDOW is where the drop happend, ACTION is the action for the drop, | ||
| 239 | ARG is the URL that has been dropped. | ||
| 240 | Returns ACTION." | ||
| 241 | (require 'browse-url) | ||
| 242 | (let* ((uri (replace-regexp-in-string | ||
| 243 | "%[A-Z0-9][A-Z0-9]" | ||
| 244 | (lambda (arg) | ||
| 245 | (format "%c" (string-to-number (substring arg 1) 16))) | ||
| 246 | arg)) | ||
| 247 | ret) | ||
| 248 | (or | ||
| 249 | (catch 'done | ||
| 250 | (dolist (bf x-dnd-protocol-alist) | ||
| 251 | (when (string-match (car bf) uri) | ||
| 252 | (setq ret (funcall (cdr bf) uri action)) | ||
| 253 | (throw 'done t))) | ||
| 254 | nil) | ||
| 255 | (when (not (functionp browse-url-browser-function)) | ||
| 256 | (catch 'done | ||
| 257 | (dolist (bf browse-url-browser-function) | ||
| 258 | (when (string-match (car bf) uri) | ||
| 259 | (setq ret 'private) | ||
| 260 | (funcall (cdr bf) uri action) | ||
| 261 | (throw 'done t))) | ||
| 262 | nil)) | ||
| 263 | (x-dnd-insert-text window action uri)) | ||
| 264 | ret)) | ||
| 265 | |||
| 266 | |||
| 267 | (defun x-dnd-get-local-file-uri (uri) | ||
| 268 | "Return an uri converted to file:/// syntax if uri is a local file. | ||
| 269 | Return nil if URI is not a local file." | ||
| 270 | |||
| 271 | ;; The hostname may be our hostname, in that case, convert to a local | ||
| 272 | ;; file. Otherwise return nil. TODO: How about an IP-address as hostname? | ||
| 273 | (let ((hostname (when (string-match "^file://\\([^/]*\\)" uri) | ||
| 274 | (downcase (match-string 1 uri)))) | ||
| 275 | (system-name-no-dot | ||
| 276 | (downcase (if (string-match "^[^\\.]+" system-name) | ||
| 277 | (match-string 0 system-name) | ||
| 278 | system-name)))) | ||
| 279 | (when (and hostname | ||
| 280 | (or (string-equal "localhost" hostname) | ||
| 281 | (string-equal (downcase system-name) hostname) | ||
| 282 | (string-equal system-name-no-dot hostname))) | ||
| 283 | (concat "file://" (substring uri (+ 7 (length hostname))))))) | ||
| 284 | |||
| 285 | (defun x-dnd-get-local-file-name (uri &optional must-exist) | ||
| 286 | "Return file name converted from file:/// or file: syntax. | ||
| 287 | URI is the uri for the file. If MUST-EXIST is given and non-nil, | ||
| 288 | only return non-nil if the file exists. | ||
| 289 | Return nil if URI is not a local file." | ||
| 290 | (let ((f (cond ((string-match "^file:///" uri) ; XDND format. | ||
| 291 | (substring uri (1- (match-end 0)))) | ||
| 292 | ((string-match "^file:" uri) ; Old KDE, Motif, Sun | ||
| 293 | (substring uri (match-end 0))) | ||
| 294 | nil))) | ||
| 295 | (when (and f must-exist) | ||
| 296 | (let* ((decoded-f (decode-coding-string | ||
| 297 | f | ||
| 298 | (or file-name-coding-system | ||
| 299 | default-file-name-coding-system))) | ||
| 300 | (try-f (if (file-readable-p decoded-f) decoded-f f))) | ||
| 301 | (when (file-readable-p try-f) try-f))))) | ||
| 302 | |||
| 303 | |||
| 304 | (defun x-dnd-open-local-file (uri action) | ||
| 305 | "Open a local file. | ||
| 306 | The file is opened in the current window, or a new window if | ||
| 307 | `x-dnd-open-file-other-window' is set. URI is the url for the file, | ||
| 308 | and must have the format file:file-name or file:///file-name. | ||
| 309 | The last / in file:/// is part of the file name. ACTION is ignored." | ||
| 310 | |||
| 311 | (let* ((f (x-dnd-get-local-file-name uri t))) | ||
| 312 | (when f | ||
| 313 | (if (file-readable-p f) | ||
| 314 | (progn | ||
| 315 | (if x-dnd-open-file-other-window | ||
| 316 | (find-file-other-window f) | ||
| 317 | (find-file f)) | ||
| 318 | 'private) | ||
| 319 | (error "Can not read %s (%s)" f uri))))) | ||
| 320 | |||
| 321 | (defun x-dnd-open-file (uri action) | ||
| 322 | "Open a local or remote file. | ||
| 323 | The file is opened in the current window, or a new window if | ||
| 324 | `x-dnd-open-file-other-window' is set. URI is the url for the file, | ||
| 325 | and must have the format file://hostname/file-name. ACTION is ignored. | ||
| 326 | The last / in file://hostname/ is part of the file name." | ||
| 327 | |||
| 328 | ;; The hostname may be our hostname, in that case, convert to a local | ||
| 329 | ;; file. Otherwise return nil. | ||
| 330 | (let ((local-file (x-dnd-get-local-file-uri uri))) | ||
| 331 | (when local-file (x-dnd-open-local-file local-file action)))) | ||
| 332 | |||
| 333 | |||
| 334 | (defun x-dnd-handle-moz-url (window action data) | ||
| 335 | "Handle one item of type text/x-moz-url. | ||
| 336 | WINDOW is the window where the drop happened. ACTION is ignored. | ||
| 337 | DATA is the moz-url, which is formatted as two strings separated by \r\n. | ||
| 338 | The first string is the URL, the second string is the title of that URL. | ||
| 339 | DATA is encoded in utf-16. Decode the URL and call `x-dnd-handle-uri-list'." | ||
| 340 | (let* ((string (decode-coding-string data 'utf-16le)) ;; ALWAYS LE??? | ||
| 341 | (strings (split-string string "[\r\n]" t)) | ||
| 342 | ;; Can one drop more than one moz-url ?? Assume not. | ||
| 343 | (url (car strings)) | ||
| 344 | (title (car (cdr strings)))) | ||
| 345 | (x-dnd-handle-uri-list window action url))) | ||
| 346 | |||
| 347 | (defun x-dnd-insert-utf8-text (window action text) | ||
| 348 | "Decode the UTF-8 text and insert it at point. | ||
| 349 | TEXT is the text as a string, WINDOW is the window where the drop happened." | ||
| 350 | (x-dnd-insert-text window action (decode-coding-string text 'utf-8))) | ||
| 351 | |||
| 352 | (defun x-dnd-insert-utf16-text (window action text) | ||
| 353 | "Decode the UTF-16 text and insert it at point. | ||
| 354 | TEXT is the text as a string, WINDOW is the window where the drop happened." | ||
| 355 | (x-dnd-insert-text window action (decode-coding-string text 'utf-16le))) | ||
| 356 | |||
| 357 | (defun x-dnd-insert-text (window action text) | ||
| 358 | "Insert text at point or push to the kill ring if buffer is read only. | ||
| 359 | TEXT is the text as a string, WINDOW is the window where the drop happened." | ||
| 360 | (if (or buffer-read-only | ||
| 361 | (not (windowp window))) | ||
| 362 | (progn | ||
| 363 | (kill-new text) | ||
| 364 | (message | ||
| 365 | (substitute-command-keys | ||
| 366 | "The dropped text can be accessed with \\[yank]"))) | ||
| 367 | (insert text)) | ||
| 368 | action) | ||
| 369 | |||
| 370 | (defun x-dnd-handle-uri-list (window action string) | ||
| 371 | "Split an uri-list into separate URIs and call `x-dnd-handle-one-url'. | ||
| 372 | WINDOW is the window where the drop happened. | ||
| 373 | STRING is the uri-list as a string. The URIs are separated by \r\n." | ||
| 374 | (let ((uri-list (split-string string "[\0\r\n]" t)) | ||
| 375 | retval) | ||
| 376 | (dolist (bf uri-list) | ||
| 377 | ;; If one URL is handeled, treat as if the whole drop succeeded. | ||
| 378 | (let ((did-action (x-dnd-handle-one-url window action bf))) | ||
| 379 | (when did-action (setq retval did-action)))) | ||
| 380 | retval)) | ||
| 381 | |||
| 382 | |||
| 383 | (defun x-dnd-choose-type (types &optional known-types) | ||
| 384 | "Choose which type we want to receive for the drop. | ||
| 385 | TYPES are the types the source of the drop offers, a vector of type names | ||
| 386 | as strings or symbols. Select among the types in `x-dnd-known-types' or | ||
| 387 | KNOWN-TYPES if given, and return that type name. | ||
| 388 | If no suitable type is found, return nil." | ||
| 389 | (let* ((known-list (or known-types x-dnd-known-types)) | ||
| 390 | (first-known-type (car known-list)) | ||
| 391 | (types-array types) | ||
| 392 | (found (when first-known-type | ||
| 393 | (catch 'done | ||
| 394 | (dotimes (i (length types-array)) | ||
| 395 | (let* ((type (aref types-array i)) | ||
| 396 | (typename (if (symbolp type) | ||
| 397 | (symbol-name type) type))) | ||
| 398 | (when (equal first-known-type typename) | ||
| 399 | (throw 'done first-known-type)))) | ||
| 400 | nil)))) | ||
| 401 | |||
| 402 | (if (and (not found) (cdr known-list)) | ||
| 403 | (x-dnd-choose-type types (cdr known-list)) | ||
| 404 | found))) | ||
| 405 | |||
| 406 | (defun x-dnd-drop-data (event frame window data type) | ||
| 407 | "Drop one data item onto a frame. | ||
| 408 | EVENT is the client message for the drop, FRAME is the frame the drop occurred | ||
| 409 | on. WINDOW is the window of FRAME where the drop happened. DATA is the data | ||
| 410 | received from the source, and type is the type for DATA, see | ||
| 411 | `x-dnd-types-alist'). | ||
| 412 | |||
| 413 | Returns the action used (move, copy, link, private) if drop was successful, | ||
| 414 | nil if not." | ||
| 415 | (let* ((type-info (assoc type x-dnd-types-alist)) | ||
| 416 | (handler (cdr type-info)) | ||
| 417 | (state (x-dnd-get-state-for-frame frame)) | ||
| 418 | (action (aref state 5)) | ||
| 419 | (w (posn-window (event-start event)))) | ||
| 420 | (when handler | ||
| 421 | (if (and (windowp w) (window-live-p w)) | ||
| 422 | ;; If dropping in a window, open files in that window rather | ||
| 423 | ;; than in a new widow. | ||
| 424 | (let ((x-dnd-open-file-other-window nil)) | ||
| 425 | (goto-char (posn-point (event-start event))) | ||
| 426 | (funcall handler window action data)) | ||
| 427 | (let ((x-dnd-open-file-other-window t)) ;; Dropping on non-window. | ||
| 428 | (select-frame frame) | ||
| 429 | (funcall handler window action data)))))) | ||
| 430 | |||
| 431 | (defun x-dnd-handle-drag-n-drop-event (event) | ||
| 432 | "Receive drag and drop events (X client messages). | ||
| 433 | Currently XDND and old KDE 1.x protocols are recognized. | ||
| 434 | TODO: Add Motif and OpenWindows." | ||
| 435 | (interactive "e") | ||
| 436 | (let* ((client-message (car (cdr (cdr event)))) | ||
| 437 | (window (posn-window (event-start event))) | ||
| 438 | (message-atom (aref client-message 0)) | ||
| 439 | (frame (aref client-message 1)) | ||
| 440 | (format (aref client-message 2)) | ||
| 441 | (data (aref client-message 3))) | ||
| 442 | |||
| 443 | (cond ((equal "DndProtocol" message-atom) ;; Old KDE 1.x. | ||
| 444 | (x-dnd-handle-old-kde event frame window message-atom format data)) | ||
| 445 | |||
| 446 | ((and (> (length message-atom) 4) ;; XDND protocol. | ||
| 447 | (equal "Xdnd" (substring message-atom 0 4))) | ||
| 448 | (x-dnd-handle-xdnd event frame window message-atom format data)) | ||
| 449 | |||
| 450 | (t (error "Unknown DND atom: %s" message-atom))))) | ||
| 451 | |||
| 452 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; | ||
| 453 | ;;; Old KDE protocol. Only dropping of files. | ||
| 454 | |||
| 455 | (defun x-dnd-handle-old-kde (event frame window message format data) | ||
| 456 | "Open the files in a KDE 1.x drop." | ||
| 457 | (let ((values (x-window-property "DndSelection" frame nil 0 t))) | ||
| 458 | (x-dnd-handle-uri-list window 'private | ||
| 459 | (replace-regexp-in-string "\0$" "" values)))) | ||
| 460 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; | ||
| 461 | |||
| 462 | |||
| 463 | |||
| 464 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; | ||
| 465 | ;;; XDND protocol. | ||
| 466 | |||
| 467 | (defvar x-dnd-xdnd-to-action | ||
| 468 | '(("XdndActionPrivate" . private) | ||
| 469 | ("XdndActionCopy" . copy) | ||
| 470 | ("XdndActionMove" . move) | ||
| 471 | ("XdndActionLink" . link) | ||
| 472 | ("XdndActionAsk" . ask)) | ||
| 473 | "Mapping from XDND action types to lisp symbols.") | ||
| 474 | |||
| 475 | (defun x-dnd-init-xdnd-for-frame (frame) | ||
| 476 | "Set the XdndAware for FRAME to indicate that we do XDND." | ||
| 477 | (x-change-window-property "XdndAware" | ||
| 478 | '(5) ;; The version of XDND we support. | ||
| 479 | frame "ATOM" 32 t)) | ||
| 480 | |||
| 481 | (defun x-dnd-get-drop-width-height (frame w accept) | ||
| 482 | "Return the widht/height to be sent in a XDndStatus message. | ||
| 483 | FRAME is the frame and W is the window where the drop happened. | ||
| 484 | If ACCEPT is nil return 0 (empty rectangle), | ||
| 485 | otherwise if W is a window, return its widht/height, | ||
| 486 | otherwise return the frame width/height." | ||
| 487 | (if accept | ||
| 488 | (if (windowp w) ;; w is not a window if dropping on the menu bar, | ||
| 489 | ;; scroll bar or tool bar. | ||
| 490 | (let ((edges (window-inside-pixel-edges w))) | ||
| 491 | (cons | ||
| 492 | (- (nth 2 edges) (nth 0 edges)) ;; right - left | ||
| 493 | (- (nth 3 edges) (nth 1 edges)))) ;; bottom - top | ||
| 494 | (cons (frame-pixel-width frame) | ||
| 495 | (frame-pixel-height frame))) | ||
| 496 | 0)) | ||
| 497 | |||
| 498 | (defun x-dnd-get-drop-x-y (frame w) | ||
| 499 | "Return the x/y coordinates to be sent in a XDndStatus message. | ||
| 500 | Coordinates are required to be absolute. | ||
| 501 | FRAME is the frame and W is the window where the drop happened. | ||
| 502 | If W is a window, return its absolute corrdinates, | ||
| 503 | otherwise return the frame coordinates." | ||
| 504 | (let* ((frame-left (frame-parameter frame 'left)) | ||
| 505 | ;; If the frame is outside the display, frame-left looks like | ||
| 506 | ;; '(0 -16). Extract the -16. | ||
| 507 | (frame-real-left (if (consp frame-left) (car (cdr frame-left)) | ||
| 508 | frame-left)) | ||
| 509 | (frame-top (frame-parameter frame 'top)) | ||
| 510 | (frame-real-top (if (consp frame-top) (car (cdr frame-top)) | ||
| 511 | frame-top))) | ||
| 512 | (if (windowp w) | ||
| 513 | (let ((edges (window-inside-pixel-edges w))) | ||
| 514 | (cons | ||
| 515 | (+ frame-real-left (nth 0 edges)) | ||
| 516 | (+ frame-real-top (nth 1 edges)))) | ||
| 517 | (cons frame-real-left frame-real-top)))) | ||
| 518 | |||
| 519 | (defun x-dnd-handle-xdnd (event frame window message format data) | ||
| 520 | "Receive one XDND event (client message) and send the appropriate reply. | ||
| 521 | EVENT is the client message. FRAME is where the mouse is now. | ||
| 522 | WINDOW is the window within FRAME where the mouse is now. | ||
| 523 | FORMAT is 32 (not used). MESSAGE is the data part of an XClientMessageEvent." | ||
| 524 | (cond ((equal "XdndEnter" message) | ||
| 525 | (let ((version (ash (car (aref data 1)) -8)) | ||
| 526 | (more-than-3 (cdr (aref data 1))) | ||
| 527 | (dnd-source (aref data 0))) | ||
| 528 | (x-dnd-save-state | ||
| 529 | window nil nil | ||
| 530 | (if (> more-than-3 0) | ||
| 531 | (x-window-property "XdndTypeList" | ||
| 532 | frame "AnyPropertyType" | ||
| 533 | dnd-source nil t) | ||
| 534 | (vector (x-get-atom-name (aref data 2)) | ||
| 535 | (x-get-atom-name (aref data 3)) | ||
| 536 | (x-get-atom-name (aref data 4))))))) | ||
| 537 | |||
| 538 | ((equal "XdndPosition" message) | ||
| 539 | (let* ((x (car (aref data 2))) | ||
| 540 | (y (cdr (aref data 2))) | ||
| 541 | (action (x-get-atom-name (aref data 4))) | ||
| 542 | (dnd-source (aref data 0)) | ||
| 543 | (dnd-time (aref data 3)) | ||
| 544 | (action-type (x-dnd-maybe-call-test-function | ||
| 545 | window | ||
| 546 | (cdr (assoc action x-dnd-xdnd-to-action)))) | ||
| 547 | (reply-action (car (rassoc (car action-type) | ||
| 548 | x-dnd-xdnd-to-action))) | ||
| 549 | (accept ;; 1 = accept, 0 = reject | ||
| 550 | (if (and reply-action action-type) 1 0)) | ||
| 551 | (list-to-send | ||
| 552 | (list (string-to-number | ||
| 553 | (frame-parameter frame 'outer-window-id)) | ||
| 554 | accept ;; 1 = Accept, 0 = reject. | ||
| 555 | (x-dnd-get-drop-x-y frame window) | ||
| 556 | (x-dnd-get-drop-width-height | ||
| 557 | frame window (eq accept 1)) | ||
| 558 | (or reply-action 0) | ||
| 559 | ))) | ||
| 560 | (x-send-client-message | ||
| 561 | frame dnd-source frame "XdndStatus" 32 list-to-send) | ||
| 562 | )) | ||
| 563 | |||
| 564 | ((equal "XdndLeave" message) | ||
| 565 | (x-dnd-forget-drop window)) | ||
| 566 | |||
| 567 | ((equal "XdndDrop" message) | ||
| 568 | (if (windowp window) (select-window window)) | ||
| 569 | (let* ((dnd-source (aref data 0)) | ||
| 570 | (value (and (x-dnd-current-type window) | ||
| 571 | ;; Get selection with target DELETE if move. | ||
| 572 | (x-get-selection-internal | ||
| 573 | 'XdndSelection | ||
| 574 | (intern (x-dnd-current-type window))))) | ||
| 575 | success action ret-action) | ||
| 576 | |||
| 577 | (setq action (if value | ||
| 578 | (condition-case info | ||
| 579 | (x-dnd-drop-data event frame window value | ||
| 580 | (x-dnd-current-type window)) | ||
| 581 | (error | ||
| 582 | (message "Error: %s" info) | ||
| 583 | nil)))) | ||
| 584 | |||
| 585 | (setq success (if action 1 0)) | ||
| 586 | (setq ret-action | ||
| 587 | (if (eq success 1) | ||
| 588 | (or (car (rassoc action x-dnd-xdnd-to-action)) | ||
| 589 | "XdndActionPrivate") | ||
| 590 | 0)) | ||
| 591 | |||
| 592 | (x-send-client-message | ||
| 593 | frame dnd-source frame "XdndFinished" 32 | ||
| 594 | (list (string-to-number (frame-parameter frame 'outer-window-id)) | ||
| 595 | success ;; 1 = Success, 0 = Error | ||
| 596 | (if success "XdndActionPrivate" 0) | ||
| 597 | )) | ||
| 598 | (x-dnd-forget-drop window))) | ||
| 599 | |||
| 600 | (t (error "Unknown XDND message %s %s" message data)))) | ||
| 601 | |||
| 602 | (provide 'x-dnd) | ||
| 603 | |||
| 604 | ;;; arch-tag: ??? | ||
| 605 | ;;; x-dnd.el ends here | ||