| 1 | ;;; image-dired.el --- use dired to browse and manipulate your images |
| 2 | ;; |
| 3 | ;; Copyright (C) 2005-2013 Free Software Foundation, Inc. |
| 4 | ;; |
| 5 | ;; Version: 0.4.11 |
| 6 | ;; Keywords: multimedia |
| 7 | ;; Author: Mathias Dahl <mathias.rem0veth1s.dahl@gmail.com> |
| 8 | |
| 9 | ;; This file is part of GNU Emacs. |
| 10 | |
| 11 | ;; GNU Emacs is free software: you can redistribute it and/or modify |
| 12 | ;; it under the terms of the GNU General Public License as published by |
| 13 | ;; the Free Software Foundation, either version 3 of the License, or |
| 14 | ;; (at your option) any later version. |
| 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 |
| 22 | ;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. |
| 23 | |
| 24 | ;;; Commentary: |
| 25 | ;; |
| 26 | ;; BACKGROUND |
| 27 | ;; ========== |
| 28 | ;; |
| 29 | ;; I needed a program to browse, organize and tag my pictures. I got |
| 30 | ;; tired of the old gallery program I used as it did not allow |
| 31 | ;; multi-file operations easily. Also, it put things out of my |
| 32 | ;; control. Image viewing programs I tested did not allow multi-file |
| 33 | ;; operations or did not do what I wanted it to. |
| 34 | ;; |
| 35 | ;; So, I got the idea to use the wonderful functionality of Emacs and |
| 36 | ;; `dired' to do it. It would allow me to do almost anything I wanted, |
| 37 | ;; which is basically just to browse all my pictures in an easy way, |
| 38 | ;; letting me manipulate and tag them in various ways. `dired' already |
| 39 | ;; provide all the file handling and navigation facilities; I only |
| 40 | ;; needed to add some functions to display the images. |
| 41 | ;; |
| 42 | ;; I briefly tried out thumbs.el, and although it seemed more |
| 43 | ;; powerful than this package, it did not work the way I wanted to. It |
| 44 | ;; was too slow to created thumbnails of all files in a directory (I |
| 45 | ;; currently keep all my 2000+ images in the same directory) and |
| 46 | ;; browsing the thumbnail buffer was slow too. image-dired.el will not |
| 47 | ;; create thumbnails until they are needed and the browsing is done |
| 48 | ;; quickly and easily in dired. I copied a great deal of ideas and |
| 49 | ;; code from there though... :) |
| 50 | ;; |
| 51 | ;; `image-dired' stores the thumbnail files in `image-dired-dir' |
| 52 | ;; using the file name format ORIGNAME.thumb.ORIGEXT. For example |
| 53 | ;; ~/.emacs.d/image-dired/myimage01.thumb.jpg. The "database" is for |
| 54 | ;; now just a plain text file with the following format: |
| 55 | ;; |
| 56 | ;; file-name-non-directory;comment:comment-text;tag1;tag2;tag3;...;tagN |
| 57 | ;; |
| 58 | ;; |
| 59 | ;; PREREQUISITES |
| 60 | ;; ============= |
| 61 | ;; |
| 62 | ;; * The ImageMagick package. Currently, `convert' and `mogrify' are |
| 63 | ;; used. Find it here: http://www.imagemagick.org. |
| 64 | ;; |
| 65 | ;; * For non-lossy rotation of JPEG images, the JpegTRAN program is |
| 66 | ;; needed. |
| 67 | ;; |
| 68 | ;; * For `image-dired-get-exif-data' and `image-dired-write-exif-data' to work, |
| 69 | ;; the command line tool `exiftool' is needed. It can be found here: |
| 70 | ;; http://www.sno.phy.queensu.ca/~phil/exiftool/. These two functions |
| 71 | ;; are, among other things, used for writing comments to image files |
| 72 | ;; using `image-dired-thumbnail-set-image-description' and to create |
| 73 | ;; "unique" file names using `image-dired-get-exif-file-name' (used by |
| 74 | ;; `image-dired-copy-with-exif-file-name'). |
| 75 | ;; |
| 76 | ;; |
| 77 | ;; USAGE |
| 78 | ;; ===== |
| 79 | ;; |
| 80 | ;; This information has been moved to the manual. Type `C-h r' to open |
| 81 | ;; the Emacs manual and go to the node Thumbnails by typing `g |
| 82 | ;; Thumbnails RET'. |
| 83 | ;; |
| 84 | ;; Quickstart: M-x image-dired RET DIRNAME RET |
| 85 | ;; |
| 86 | ;; where DIRNAME is a directory containing image files. |
| 87 | ;; |
| 88 | ;; LIMITATIONS |
| 89 | ;; =========== |
| 90 | ;; |
| 91 | ;; * Supports all image formats that Emacs and convert supports, but |
| 92 | ;; the thumbnails are hard-coded to JPEG format. |
| 93 | ;; |
| 94 | ;; * WARNING: The "database" format used might be changed so keep a |
| 95 | ;; backup of `image-dired-db-file' when testing new versions. |
| 96 | ;; |
| 97 | ;; |
| 98 | ;; TODO |
| 99 | ;; ==== |
| 100 | ;; |
| 101 | ;; * Support gallery creation when using per-directory thumbnail |
| 102 | ;; storage. |
| 103 | ;; |
| 104 | ;; * Some sort of auto-rotate function based on rotate info in the |
| 105 | ;; EXIF data. |
| 106 | ;; |
| 107 | ;; * Check if exiftool exist before trying to call it to give a better |
| 108 | ;; error message. |
| 109 | ;; |
| 110 | ;; * Investigate if it is possible to also write the tags to the image |
| 111 | ;; files. |
| 112 | ;; |
| 113 | ;; * From thumbs.el: Add an option for clean-up/max-size functionality |
| 114 | ;; for thumbnail directory. |
| 115 | ;; |
| 116 | ;; * From thumbs.el: Add setroot function. |
| 117 | ;; |
| 118 | ;; * From thumbs.el: Add image resizing, if useful (image-dired's automatic |
| 119 | ;; "image fit" might be enough) |
| 120 | ;; |
| 121 | ;; * From thumbs.el: Add the "modify" commands (emboss, negate, |
| 122 | ;; monochrome etc). |
| 123 | ;; |
| 124 | ;; * Asynchronous creation of thumbnails. |
| 125 | ;; |
| 126 | ;; * Add `image-dired-display-thumbs-ring' and functions to cycle that. Find |
| 127 | ;; out which is best, saving old batch just before inserting new, or |
| 128 | ;; saving the current batch in the ring when inserting it. Adding it |
| 129 | ;; probably needs rewriting `image-dired-display-thumbs' to be more general. |
| 130 | ;; |
| 131 | ;; * Find some way of toggling on and off really nice keybindings in |
| 132 | ;; dired (for example, using C-n or <down> instead of C-S-n). Richard |
| 133 | ;; suggested that we could keep C-t as prefix for image-dired commands |
| 134 | ;; as it is currently not used in dired. He also suggested that |
| 135 | ;; `dired-next-line' and `dired-previous-line' figure out if |
| 136 | ;; image-dired is enabled in the current buffer and, if it is, call |
| 137 | ;; `image-dired-dired-next-line' and |
| 138 | ;; `image-dired-dired-previous-line', respectively. Update: This is |
| 139 | ;; partly done; some bindings have now been added to dired. |
| 140 | ;; |
| 141 | ;; * Enhanced gallery creation with basic CSS-support and pagination |
| 142 | ;; of tag pages with many pictures. |
| 143 | ;; |
| 144 | ;; * Rewrite `image-dired-modify-mark-on-thumb-original-file' to be |
| 145 | ;; less ugly. |
| 146 | ;; |
| 147 | ;; * In some way keep track of buffers and windows and stuff so that |
| 148 | ;; it works as the user expects. |
| 149 | ;; |
| 150 | ;; * More/better documentation |
| 151 | ;; |
| 152 | ;; |
| 153 | ;;; Code: |
| 154 | |
| 155 | (require 'dired) |
| 156 | (require 'format-spec) |
| 157 | (require 'widget) |
| 158 | |
| 159 | (eval-when-compile |
| 160 | (require 'cl-lib) |
| 161 | (require 'wid-edit)) |
| 162 | |
| 163 | (defgroup image-dired nil |
| 164 | "Use dired to browse your images as thumbnails, and more." |
| 165 | :prefix "image-dired-" |
| 166 | :group 'multimedia) |
| 167 | |
| 168 | (defcustom image-dired-dir (locate-user-emacs-file "image-dired/") |
| 169 | "Directory where thumbnail images are stored." |
| 170 | :type 'string |
| 171 | :group 'image-dired) |
| 172 | |
| 173 | (defcustom image-dired-thumbnail-storage 'use-image-dired-dir |
| 174 | "How to store image-dired's thumbnail files. |
| 175 | Image-Dired can store thumbnail files in one of two ways and this is |
| 176 | controlled by this variable. \"Use image-dired dir\" means that the |
| 177 | thumbnails are stored in a central directory. \"Per directory\" |
| 178 | means that each thumbnail is stored in a subdirectory called |
| 179 | \".image-dired\" in the same directory where the image file is. |
| 180 | \"Thumbnail Managing Standard\" means that the thumbnails are |
| 181 | stored and generated according to the Thumbnail Managing Standard |
| 182 | that allows sharing of thumbnails across different programs." |
| 183 | :type '(choice :tag "How to store thumbnail files" |
| 184 | (const :tag "Thumbnail Managing Standard" standard) |
| 185 | (const :tag "Use image-dired-dir" use-image-dired-dir) |
| 186 | (const :tag "Per-directory" per-directory)) |
| 187 | :group 'image-dired) |
| 188 | |
| 189 | (defcustom image-dired-db-file |
| 190 | (expand-file-name ".image-dired_db" image-dired-dir) |
| 191 | "Database file where file names and their associated tags are stored." |
| 192 | :type 'string |
| 193 | :group 'image-dired) |
| 194 | |
| 195 | (defcustom image-dired-temp-image-file |
| 196 | (expand-file-name ".image-dired_temp" image-dired-dir) |
| 197 | "Name of temporary image file used by various commands." |
| 198 | :type 'string |
| 199 | :group 'image-dired) |
| 200 | |
| 201 | (defcustom image-dired-gallery-dir |
| 202 | (expand-file-name ".image-dired_gallery" image-dired-dir) |
| 203 | "Directory to store generated gallery html pages. |
| 204 | This path needs to be \"shared\" to the public so that it can access |
| 205 | the index.html page that image-dired creates." |
| 206 | :type 'string |
| 207 | :group 'image-dired) |
| 208 | |
| 209 | (defcustom image-dired-gallery-image-root-url |
| 210 | "http://your.own.server/image-diredpics" |
| 211 | "URL where the full size images are to be found. |
| 212 | Note that this path has to be configured in your web server. Image-Dired |
| 213 | expects to find pictures in this directory." |
| 214 | :type 'string |
| 215 | :group 'image-dired) |
| 216 | |
| 217 | (defcustom image-dired-gallery-thumb-image-root-url |
| 218 | "http://your.own.server/image-diredthumbs" |
| 219 | "URL where the thumbnail images are to be found. |
| 220 | Note that this path has to be configured in your web server. Image-Dired |
| 221 | expects to find pictures in this directory." |
| 222 | :type 'string |
| 223 | :group 'image-dired) |
| 224 | |
| 225 | (defcustom image-dired-cmd-create-thumbnail-program |
| 226 | "convert" |
| 227 | "Executable used to create thumbnail. |
| 228 | Used together with `image-dired-cmd-create-thumbnail-options'." |
| 229 | :type 'string |
| 230 | :group 'image-dired) |
| 231 | |
| 232 | (defcustom image-dired-cmd-create-thumbnail-options |
| 233 | "%p -size %wx%h \"%f\" -resize \"%wx%h>\" -strip jpeg:\"%t\"" |
| 234 | "Format of command used to create thumbnail image. |
| 235 | Available options are %p which is replaced by |
| 236 | `image-dired-cmd-create-thumbnail-program', %w which is replaced by |
| 237 | `image-dired-thumb-width', %h which is replaced by `image-dired-thumb-height', |
| 238 | %f which is replaced by the file name of the original image and %t |
| 239 | which is replaced by the file name of the thumbnail file." |
| 240 | :type 'string |
| 241 | :group 'image-dired) |
| 242 | |
| 243 | (defcustom image-dired-cmd-create-temp-image-program |
| 244 | "convert" |
| 245 | "Executable used to create temporary image. |
| 246 | Used together with `image-dired-cmd-create-temp-image-options'." |
| 247 | :type 'string |
| 248 | :group 'image-dired) |
| 249 | |
| 250 | (defcustom image-dired-cmd-create-temp-image-options |
| 251 | "%p -size %wx%h \"%f\" -resize \"%wx%h>\" -strip jpeg:\"%t\"" |
| 252 | "Format of command used to create temporary image for display window. |
| 253 | Available options are %p which is replaced by |
| 254 | `image-dired-cmd-create-temp-image-program', %w and %h which is replaced by |
| 255 | the calculated max size for width and height in the image display window, |
| 256 | %f which is replaced by the file name of the original image and %t which |
| 257 | is replaced by the file name of the temporary file." |
| 258 | :type 'string |
| 259 | :group 'image-dired) |
| 260 | |
| 261 | (defcustom image-dired-cmd-pngnq-program (executable-find "pngnq") |
| 262 | "The file name of the `pngnq' program. |
| 263 | It quantizes colors of PNG images down to 256 colors." |
| 264 | :type '(choice (const :tag "Not Set" nil) string) |
| 265 | :group 'image-dired) |
| 266 | |
| 267 | (defcustom image-dired-cmd-pngcrush-program (executable-find "pngcrush") |
| 268 | "The file name of the `pngcrush' program. |
| 269 | It optimizes the compression of PNG images. Also it adds PNG textual chunks |
| 270 | with the information required by the Thumbnail Managing Standard." |
| 271 | :type '(choice (const :tag "Not Set" nil) string) |
| 272 | :group 'image-dired) |
| 273 | |
| 274 | (defcustom image-dired-cmd-create-standard-thumbnail-command |
| 275 | (concat |
| 276 | image-dired-cmd-create-thumbnail-program " " |
| 277 | "-size %wx%h \"%f\" " |
| 278 | (unless (or image-dired-cmd-pngcrush-program image-dired-cmd-pngnq-program) |
| 279 | (concat |
| 280 | "-set \"Thumb::MTime\" \"%m\" " |
| 281 | "-set \"Thumb::URI\" \"file://%f\" " |
| 282 | "-set \"Description\" \"Thumbnail of file://%f\" " |
| 283 | "-set \"Software\" \"" (emacs-version) "\" ")) |
| 284 | "-thumbnail \"%wx%h>\" png:\"%t\"" |
| 285 | (if image-dired-cmd-pngnq-program |
| 286 | (concat |
| 287 | " ; " image-dired-cmd-pngnq-program " -f \"%t\"" |
| 288 | (unless image-dired-cmd-pngcrush-program |
| 289 | " ; mv %q %t"))) |
| 290 | (if image-dired-cmd-pngcrush-program |
| 291 | (concat |
| 292 | (unless image-dired-cmd-pngcrush-program |
| 293 | " ; cp %t %q") |
| 294 | " ; " image-dired-cmd-pngcrush-program " -q " |
| 295 | "-text b \"Description\" \"Thumbnail of file://%f\" " |
| 296 | "-text b \"Software\" \"" (emacs-version) "\" " |
| 297 | ;; "-text b \"Thumb::Image::Height\" \"%oh\" " |
| 298 | ;; "-text b \"Thumb::Image::Mimetype\" \"%mime\" " |
| 299 | ;; "-text b \"Thumb::Image::Width\" \"%ow\" " |
| 300 | "-text b \"Thumb::MTime\" \"%m\" " |
| 301 | ;; "-text b \"Thumb::Size\" \"%b\" " |
| 302 | "-text b \"Thumb::URI\" \"file://%f\" " |
| 303 | "%q %t" |
| 304 | " ; rm %q"))) |
| 305 | "Command to create thumbnails according to the Thumbnail Managing Standard." |
| 306 | :type 'string |
| 307 | :group 'image-dired) |
| 308 | |
| 309 | (defcustom image-dired-cmd-rotate-thumbnail-program |
| 310 | "mogrify" |
| 311 | "Executable used to rotate thumbnail. |
| 312 | Used together with `image-dired-cmd-rotate-thumbnail-options'." |
| 313 | :type 'string |
| 314 | :group 'image-dired) |
| 315 | |
| 316 | (defcustom image-dired-cmd-rotate-thumbnail-options |
| 317 | "%p -rotate %d \"%t\"" |
| 318 | "Format of command used to rotate thumbnail image. |
| 319 | Available options are %p which is replaced by |
| 320 | `image-dired-cmd-rotate-thumbnail-program', %d which is replaced by the |
| 321 | number of (positive) degrees to rotate the image, normally 90 or 270 |
| 322 | \(for 90 degrees right and left), %t which is replaced by the file name |
| 323 | of the thumbnail file." |
| 324 | :type 'string |
| 325 | :group 'image-dired) |
| 326 | |
| 327 | (defcustom image-dired-cmd-rotate-original-program |
| 328 | "jpegtran" |
| 329 | "Executable used to rotate original image. |
| 330 | Used together with `image-dired-cmd-rotate-original-options'." |
| 331 | :type 'string |
| 332 | :group 'image-dired) |
| 333 | |
| 334 | (defcustom image-dired-cmd-rotate-original-options |
| 335 | "%p -rotate %d -copy all -outfile %t \"%o\"" |
| 336 | "Format of command used to rotate original image. |
| 337 | Available options are %p which is replaced by |
| 338 | `image-dired-cmd-rotate-original-program', %d which is replaced by the |
| 339 | number of (positive) degrees to rotate the image, normally 90 or |
| 340 | 270 \(for 90 degrees right and left), %o which is replaced by the |
| 341 | original image file name and %t which is replaced by |
| 342 | `image-dired-temp-image-file'." |
| 343 | :type 'string |
| 344 | :group 'image-dired) |
| 345 | |
| 346 | (defcustom image-dired-temp-rotate-image-file |
| 347 | (expand-file-name ".image-dired_rotate_temp" image-dired-dir) |
| 348 | "Temporary file for rotate operations." |
| 349 | :type 'string |
| 350 | :group 'image-dired) |
| 351 | |
| 352 | (defcustom image-dired-rotate-original-ask-before-overwrite t |
| 353 | "Confirm overwrite of original file after rotate operation. |
| 354 | If non-nil, ask user for confirmation before overwriting the |
| 355 | original file with `image-dired-temp-rotate-image-file'." |
| 356 | :type 'boolean |
| 357 | :group 'image-dired) |
| 358 | |
| 359 | (defcustom image-dired-cmd-write-exif-data-program |
| 360 | "exiftool" |
| 361 | "Program used to write EXIF data to image. |
| 362 | Used together with `image-dired-cmd-write-exif-data-options'." |
| 363 | :type 'string |
| 364 | :group 'image-dired) |
| 365 | |
| 366 | (defcustom image-dired-cmd-write-exif-data-options |
| 367 | "%p -%t=\"%v\" \"%f\"" |
| 368 | "Format of command used to write EXIF data. |
| 369 | Available options are %p which is replaced by |
| 370 | `image-dired-cmd-write-exif-data-program', %f which is replaced by |
| 371 | the image file name, %t which is replaced by the tag name and %v |
| 372 | which is replaced by the tag value." |
| 373 | :type 'string |
| 374 | :group 'image-dired) |
| 375 | |
| 376 | (defcustom image-dired-cmd-read-exif-data-program |
| 377 | "exiftool" |
| 378 | "Program used to read EXIF data to image. |
| 379 | Used together with `image-dired-cmd-read-exif-data-program-options'." |
| 380 | :type 'string |
| 381 | :group 'image-dired) |
| 382 | |
| 383 | (defcustom image-dired-cmd-read-exif-data-options |
| 384 | "%p -s -s -s -%t \"%f\"" |
| 385 | "Format of command used to read EXIF data. |
| 386 | Available options are %p which is replaced by |
| 387 | `image-dired-cmd-write-exif-data-program', %f which is replaced |
| 388 | by the image file name and %t which is replaced by the tag name." |
| 389 | :type 'string |
| 390 | :group 'image-dired) |
| 391 | |
| 392 | (defcustom image-dired-gallery-hidden-tags |
| 393 | (list "private" "hidden" "pending") |
| 394 | "List of \"hidden\" tags. |
| 395 | Used by `image-dired-gallery-generate' to leave out \"hidden\" images." |
| 396 | :type '(repeat string) |
| 397 | :group 'image-dired) |
| 398 | |
| 399 | (defcustom image-dired-thumb-size (if (eq 'standard image-dired-thumbnail-storage) 128 100) |
| 400 | "Size of thumbnails, in pixels. |
| 401 | This is the default size for both `image-dired-thumb-width' |
| 402 | and `image-dired-thumb-height'." |
| 403 | :type 'integer |
| 404 | :group 'image-dired) |
| 405 | |
| 406 | (defcustom image-dired-thumb-width image-dired-thumb-size |
| 407 | "Width of thumbnails, in pixels." |
| 408 | :type 'integer |
| 409 | :group 'image-dired) |
| 410 | |
| 411 | (defcustom image-dired-thumb-height image-dired-thumb-size |
| 412 | "Height of thumbnails, in pixels." |
| 413 | :type 'integer |
| 414 | :group 'image-dired) |
| 415 | |
| 416 | (defcustom image-dired-thumb-relief 2 |
| 417 | "Size of button-like border around thumbnails." |
| 418 | :type 'integer |
| 419 | :group 'image-dired) |
| 420 | |
| 421 | (defcustom image-dired-thumb-margin 2 |
| 422 | "Size of the margin around thumbnails. |
| 423 | This is where you see the cursor." |
| 424 | :type 'integer |
| 425 | :group 'image-dired) |
| 426 | |
| 427 | (defcustom image-dired-line-up-method 'dynamic |
| 428 | "Default method for line-up of thumbnails in thumbnail buffer. |
| 429 | Used by `image-dired-display-thumbs' and other functions that needs |
| 430 | to line-up thumbnails. Dynamic means to use the available width of |
| 431 | the window containing the thumbnail buffer, Fixed means to use |
| 432 | `image-dired-thumbs-per-row', Interactive is for asking the user, |
| 433 | and No line-up means that no automatic line-up will be done." |
| 434 | :type '(choice :tag "Default line-up method" |
| 435 | (const :tag "Dynamic" dynamic) |
| 436 | (const :tag "Fixed" fixed) |
| 437 | (const :tag "Interactive" interactive) |
| 438 | (const :tag "No line-up" none)) |
| 439 | :group 'image-dired) |
| 440 | |
| 441 | (defcustom image-dired-thumbs-per-row 3 |
| 442 | "Number of thumbnails to display per row in thumb buffer." |
| 443 | :type 'integer |
| 444 | :group 'image-dired) |
| 445 | |
| 446 | (defcustom image-dired-display-window-width-correction 1 |
| 447 | "Number to be used to correct image display window width. |
| 448 | Change if the default (1) does not work (i.e. if the image does not |
| 449 | completely fit)." |
| 450 | :type 'integer |
| 451 | :group 'image-dired) |
| 452 | |
| 453 | (defcustom image-dired-display-window-height-correction 0 |
| 454 | "Number to be used to correct image display window height. |
| 455 | Change if the default (0) does not work (i.e. if the image does not |
| 456 | completely fit)." |
| 457 | :type 'integer |
| 458 | :group 'image-dired) |
| 459 | |
| 460 | (defcustom image-dired-track-movement t |
| 461 | "The current state of the tracking and mirroring. |
| 462 | For more information, see the documentation for |
| 463 | `image-dired-toggle-movement-tracking'." |
| 464 | :type 'boolean |
| 465 | :group 'image-dired) |
| 466 | |
| 467 | (defcustom image-dired-append-when-browsing nil |
| 468 | "Append thumbnails in thumbnail buffer when browsing. |
| 469 | If non-nil, using `image-dired-next-line-and-display' and |
| 470 | `image-dired-previous-line-and-display' will leave a trail of thumbnail |
| 471 | images in the thumbnail buffer. If you enable this and want to clean |
| 472 | the thumbnail buffer because it is filled with too many thumbnails, |
| 473 | just call `image-dired-display-thumb' to display only the image at point. |
| 474 | This value can be toggled using `image-dired-toggle-append-browsing'." |
| 475 | :type 'boolean |
| 476 | :group 'image-dired) |
| 477 | |
| 478 | (defcustom image-dired-dired-disp-props t |
| 479 | "If non-nil, display properties for dired file when browsing. |
| 480 | Used by `image-dired-next-line-and-display', |
| 481 | `image-dired-previous-line-and-display' and `image-dired-mark-and-display-next'. |
| 482 | If the database file is large, this can slow down image browsing in |
| 483 | dired and you might want to turn it off." |
| 484 | :type 'boolean |
| 485 | :group 'image-dired) |
| 486 | |
| 487 | (defcustom image-dired-display-properties-format "%b: %f (%t): %c" |
| 488 | "Display format for thumbnail properties. |
| 489 | %b is replaced with associated dired buffer name, %f with file name |
| 490 | \(without path) of original image file, %t with the list of tags and %c |
| 491 | with the comment." |
| 492 | :type 'string |
| 493 | :group 'image-dired) |
| 494 | |
| 495 | (defcustom image-dired-external-viewer |
| 496 | ;; TODO: Use mailcap, dired-guess-shell-alist-default, |
| 497 | ;; dired-view-command-alist. |
| 498 | (cond ((executable-find "display")) |
| 499 | ((executable-find "xli")) |
| 500 | ((executable-find "qiv") "qiv -t")) |
| 501 | "Name of external viewer. |
| 502 | Including parameters. Used when displaying original image from |
| 503 | `image-dired-thumbnail-mode'." |
| 504 | :type 'string |
| 505 | :group 'image-dired) |
| 506 | |
| 507 | (defcustom image-dired-main-image-directory "~/pics/" |
| 508 | "Name of main image directory, if any. |
| 509 | Used by `image-dired-copy-with-exif-file-name'." |
| 510 | :type 'string |
| 511 | :group 'image-dired) |
| 512 | |
| 513 | (defcustom image-dired-show-all-from-dir-max-files 50 |
| 514 | "Maximum number of files to show using `image-dired-show-all-from-dir' |
| 515 | before warning the user." |
| 516 | :type 'integer |
| 517 | :group 'image-dired) |
| 518 | |
| 519 | (defmacro image-dired--with-db-file (&rest body) |
| 520 | "Run BODY in a temp buffer containing `image-dired-db-file'. |
| 521 | Return the last form in BODY." |
| 522 | `(with-temp-buffer |
| 523 | (if (file-exists-p image-dired-db-file) |
| 524 | (insert-file-contents image-dired-db-file)) |
| 525 | ,@body)) |
| 526 | |
| 527 | (defun image-dired-dir () |
| 528 | "Return the current thumbnails directory (from variable `image-dired-dir'). |
| 529 | Create the thumbnails directory if it does not exist." |
| 530 | (let ((image-dired-dir (file-name-as-directory |
| 531 | (expand-file-name image-dired-dir)))) |
| 532 | (unless (file-directory-p image-dired-dir) |
| 533 | (make-directory image-dired-dir t) |
| 534 | (message "Creating thumbnails directory")) |
| 535 | image-dired-dir)) |
| 536 | |
| 537 | (defun image-dired-insert-image (file type relief margin) |
| 538 | "Insert image FILE of image TYPE, using RELIEF and MARGIN, at point." |
| 539 | |
| 540 | (let ((i `(image :type ,type |
| 541 | :file ,file |
| 542 | :relief ,relief |
| 543 | :margin ,margin))) |
| 544 | (insert-image i))) |
| 545 | |
| 546 | (defun image-dired-get-thumbnail-image (file) |
| 547 | "Return the image descriptor for a thumbnail of image file FILE." |
| 548 | (unless (string-match (image-file-name-regexp) file) |
| 549 | (error "%s is not a valid image file" file)) |
| 550 | (let ((thumb-file (image-dired-thumb-name file))) |
| 551 | (unless (and (file-exists-p thumb-file) |
| 552 | (<= (float-time (nth 5 (file-attributes file))) |
| 553 | (float-time (nth 5 (file-attributes thumb-file))))) |
| 554 | (image-dired-create-thumb file thumb-file)) |
| 555 | (create-image thumb-file) |
| 556 | ;; (list 'image :type 'jpeg |
| 557 | ;; :file thumb-file |
| 558 | ;; :relief image-dired-thumb-relief :margin image-dired-thumb-margin) |
| 559 | )) |
| 560 | |
| 561 | (defun image-dired-insert-thumbnail (file original-file-name |
| 562 | associated-dired-buffer) |
| 563 | "Insert thumbnail image FILE. |
| 564 | Add text properties ORIGINAL-FILE-NAME and ASSOCIATED-DIRED-BUFFER." |
| 565 | (let (beg end) |
| 566 | (setq beg (point)) |
| 567 | (image-dired-insert-image file |
| 568 | ;; TODO: this should depend on the real file type |
| 569 | (if (eq 'standard image-dired-thumbnail-storage) |
| 570 | 'png 'jpeg) |
| 571 | image-dired-thumb-relief |
| 572 | image-dired-thumb-margin) |
| 573 | (setq end (point)) |
| 574 | (add-text-properties |
| 575 | beg end |
| 576 | (list 'image-dired-thumbnail t |
| 577 | 'original-file-name original-file-name |
| 578 | 'associated-dired-buffer associated-dired-buffer |
| 579 | 'tags (image-dired-list-tags original-file-name) |
| 580 | 'mouse-face 'highlight |
| 581 | 'comment (image-dired-get-comment original-file-name))))) |
| 582 | |
| 583 | (defun image-dired-thumb-name (file) |
| 584 | "Return thumbnail file name for FILE. |
| 585 | Depending on the value of `image-dired-thumbnail-storage', the file |
| 586 | name will vary. For central thumbnail file storage, make a |
| 587 | MD5-hash of the image file's directory name and add that to make |
| 588 | the thumbnail file name unique. For per-directory storage, just |
| 589 | add a subdirectory. For standard storage, produce the file name |
| 590 | according to the Thumbnail Managing Standard." |
| 591 | (cond ((eq 'standard image-dired-thumbnail-storage) |
| 592 | (expand-file-name |
| 593 | (concat "~/.thumbnails/normal/" |
| 594 | (md5 (concat "file://" (expand-file-name file))) ".png"))) |
| 595 | ((eq 'use-image-dired-dir image-dired-thumbnail-storage) |
| 596 | (let* ((f (expand-file-name file)) |
| 597 | (md5-hash |
| 598 | ;; Is MD5 hashes fast enough? The checksum of a |
| 599 | ;; thumbnail file name need not be that |
| 600 | ;; "cryptographically" good so a faster one could |
| 601 | ;; be used here. |
| 602 | (md5 (file-name-as-directory (file-name-directory f))))) |
| 603 | (format "%s%s%s.thumb.%s" |
| 604 | (file-name-as-directory (expand-file-name (image-dired-dir))) |
| 605 | (file-name-base f) |
| 606 | (if md5-hash (concat "_" md5-hash) "") |
| 607 | (file-name-extension f)))) |
| 608 | ((eq 'per-directory image-dired-thumbnail-storage) |
| 609 | (let ((f (expand-file-name file))) |
| 610 | (format "%s.image-dired/%s.thumb.%s" |
| 611 | (file-name-directory f) |
| 612 | (file-name-base f) |
| 613 | (file-name-extension f)))))) |
| 614 | |
| 615 | (defun image-dired-create-thumb (original-file thumbnail-file) |
| 616 | "For ORIGINAL-FILE, create thumbnail image named THUMBNAIL-FILE." |
| 617 | (let* ((width (int-to-string image-dired-thumb-width)) |
| 618 | (height (int-to-string image-dired-thumb-height)) |
| 619 | (modif-time (format "%.0f" (float-time (nth 5 (file-attributes |
| 620 | original-file))))) |
| 621 | (thumbnail-nq8-file (replace-regexp-in-string ".png\\'" "-nq8.png" |
| 622 | thumbnail-file)) |
| 623 | (command |
| 624 | (format-spec |
| 625 | (if (eq 'standard image-dired-thumbnail-storage) |
| 626 | image-dired-cmd-create-standard-thumbnail-command |
| 627 | image-dired-cmd-create-thumbnail-options) |
| 628 | (list |
| 629 | (cons ?p image-dired-cmd-create-thumbnail-program) |
| 630 | (cons ?w width) |
| 631 | (cons ?h height) |
| 632 | (cons ?m modif-time) |
| 633 | (cons ?f original-file) |
| 634 | (cons ?q thumbnail-nq8-file) |
| 635 | (cons ?t thumbnail-file)))) |
| 636 | thumbnail-dir) |
| 637 | (when (not (file-exists-p |
| 638 | (setq thumbnail-dir (file-name-directory thumbnail-file)))) |
| 639 | (message "Creating thumbnail directory.") |
| 640 | (make-directory thumbnail-dir)) |
| 641 | (call-process shell-file-name nil nil nil shell-command-switch command))) |
| 642 | |
| 643 | ;;;###autoload |
| 644 | (defun image-dired-dired-toggle-marked-thumbs (&optional arg) |
| 645 | "Toggle thumbnails in front of file names in the dired buffer. |
| 646 | If no marked file could be found, insert or hide thumbnails on the |
| 647 | current line. ARG, if non-nil, specifies the files to use instead |
| 648 | of the marked files. If ARG is an integer, use the next ARG (or |
| 649 | previous -ARG, if ARG<0) files." |
| 650 | (interactive "P") |
| 651 | (dired-map-over-marks |
| 652 | (let* ((image-pos (dired-move-to-filename)) |
| 653 | (image-file (dired-get-filename nil t)) |
| 654 | thumb-file |
| 655 | overlay) |
| 656 | (when (and image-file |
| 657 | (string-match-p (image-file-name-regexp) image-file)) |
| 658 | (setq thumb-file (image-dired-get-thumbnail-image image-file)) |
| 659 | ;; If image is not already added, then add it. |
| 660 | (let ((cur-ov (overlays-in (point) (1+ (point))))) |
| 661 | (if cur-ov |
| 662 | (delete-overlay (car cur-ov)) |
| 663 | (put-image thumb-file image-pos) |
| 664 | (setq overlay |
| 665 | (cl-loop for o in (overlays-in (point) (1+ (point))) |
| 666 | when (overlay-get o 'put-image) collect o into ov |
| 667 | finally return (car ov))) |
| 668 | (overlay-put overlay 'image-file image-file) |
| 669 | (overlay-put overlay 'thumb-file thumb-file))))) |
| 670 | arg ; Show or hide image on ARG next files. |
| 671 | 'show-progress) ; Update dired display after each image is updated. |
| 672 | (add-hook 'dired-after-readin-hook |
| 673 | 'image-dired-dired-after-readin-hook nil t)) |
| 674 | |
| 675 | (defun image-dired-dired-after-readin-hook () |
| 676 | "Relocate existing thumbnail overlays in dired buffer after reverting. |
| 677 | Move them to their corresponding files if they still exist. |
| 678 | Otherwise, delete overlays." |
| 679 | (mapc (lambda (overlay) |
| 680 | (when (overlay-get overlay 'put-image) |
| 681 | (let* ((image-file (overlay-get overlay 'image-file)) |
| 682 | (image-pos (dired-goto-file image-file))) |
| 683 | (if image-pos |
| 684 | (move-overlay overlay image-pos image-pos) |
| 685 | (delete-overlay overlay))))) |
| 686 | (overlays-in (point-min) (point-max)))) |
| 687 | |
| 688 | (defun image-dired-next-line-and-display () |
| 689 | "Move to next dired line and display thumbnail image." |
| 690 | (interactive) |
| 691 | (dired-next-line 1) |
| 692 | (image-dired-display-thumbs |
| 693 | t (or image-dired-append-when-browsing nil) t) |
| 694 | (if image-dired-dired-disp-props |
| 695 | (image-dired-dired-display-properties))) |
| 696 | |
| 697 | (defun image-dired-previous-line-and-display () |
| 698 | "Move to previous dired line and display thumbnail image." |
| 699 | (interactive) |
| 700 | (dired-previous-line 1) |
| 701 | (image-dired-display-thumbs |
| 702 | t (or image-dired-append-when-browsing nil) t) |
| 703 | (if image-dired-dired-disp-props |
| 704 | (image-dired-dired-display-properties))) |
| 705 | |
| 706 | (defun image-dired-toggle-append-browsing () |
| 707 | "Toggle `image-dired-append-when-browsing'." |
| 708 | (interactive) |
| 709 | (setq image-dired-append-when-browsing |
| 710 | (not image-dired-append-when-browsing)) |
| 711 | (message "Append browsing %s." |
| 712 | (if image-dired-append-when-browsing |
| 713 | "on" |
| 714 | "off"))) |
| 715 | |
| 716 | (defun image-dired-mark-and-display-next () |
| 717 | "Mark current file in dired and display next thumbnail image." |
| 718 | (interactive) |
| 719 | (dired-mark 1) |
| 720 | (image-dired-display-thumbs |
| 721 | t (or image-dired-append-when-browsing nil) t) |
| 722 | (if image-dired-dired-disp-props |
| 723 | (image-dired-dired-display-properties))) |
| 724 | |
| 725 | (defun image-dired-toggle-dired-display-properties () |
| 726 | "Toggle `image-dired-dired-disp-props'." |
| 727 | (interactive) |
| 728 | (setq image-dired-dired-disp-props |
| 729 | (not image-dired-dired-disp-props)) |
| 730 | (message "Dired display properties %s." |
| 731 | (if image-dired-dired-disp-props |
| 732 | "on" |
| 733 | "off"))) |
| 734 | |
| 735 | (defvar image-dired-thumbnail-buffer "*image-dired*" |
| 736 | "Image-Dired's thumbnail buffer.") |
| 737 | |
| 738 | (defun image-dired-create-thumbnail-buffer () |
| 739 | "Create thumb buffer and set `image-dired-thumbnail-mode'." |
| 740 | (let ((buf (get-buffer-create image-dired-thumbnail-buffer))) |
| 741 | (with-current-buffer buf |
| 742 | (setq buffer-read-only t) |
| 743 | (if (not (eq major-mode 'image-dired-thumbnail-mode)) |
| 744 | (image-dired-thumbnail-mode))) |
| 745 | buf)) |
| 746 | |
| 747 | (defvar image-dired-display-image-buffer "*image-dired-display-image*" |
| 748 | "Where larger versions of the images are display.") |
| 749 | |
| 750 | (defun image-dired-create-display-image-buffer () |
| 751 | "Create image display buffer and set `image-dired-display-image-mode'." |
| 752 | (let ((buf (get-buffer-create image-dired-display-image-buffer))) |
| 753 | (with-current-buffer buf |
| 754 | (setq buffer-read-only t) |
| 755 | (if (not (eq major-mode 'image-dired-display-image-mode)) |
| 756 | (image-dired-display-image-mode))) |
| 757 | buf)) |
| 758 | |
| 759 | (defvar image-dired-saved-window-configuration nil |
| 760 | "Saved window configuration.") |
| 761 | |
| 762 | ;;;###autoload |
| 763 | (defun image-dired-dired-with-window-configuration (dir &optional arg) |
| 764 | "Open directory DIR and create a default window configuration. |
| 765 | |
| 766 | Convenience command that: |
| 767 | |
| 768 | - Opens dired in folder DIR |
| 769 | - Splits windows in most useful (?) way |
| 770 | - Set `truncate-lines' to t |
| 771 | |
| 772 | After the command has finished, you would typically mark some |
| 773 | image files in dired and type |
| 774 | \\[image-dired-display-thumbs] (`image-dired-display-thumbs'). |
| 775 | |
| 776 | If called with prefix argument ARG, skip splitting of windows. |
| 777 | |
| 778 | The current window configuration is saved and can be restored by |
| 779 | calling `image-dired-restore-window-configuration'." |
| 780 | (interactive "DDirectory: \nP") |
| 781 | (let ((buf (image-dired-create-thumbnail-buffer)) |
| 782 | (buf2 (image-dired-create-display-image-buffer))) |
| 783 | (setq image-dired-saved-window-configuration |
| 784 | (current-window-configuration)) |
| 785 | (dired dir) |
| 786 | (delete-other-windows) |
| 787 | (when (not arg) |
| 788 | (split-window-right) |
| 789 | (setq truncate-lines t) |
| 790 | (save-excursion |
| 791 | (other-window 1) |
| 792 | (switch-to-buffer buf) |
| 793 | (select-window (split-window-below)) |
| 794 | (switch-to-buffer buf2) |
| 795 | (other-window -2))))) |
| 796 | |
| 797 | (defun image-dired-restore-window-configuration () |
| 798 | "Restore window configuration. |
| 799 | Restore any changes to the window configuration made by calling |
| 800 | `image-dired-dired-with-window-configuration'." |
| 801 | (interactive) |
| 802 | (if image-dired-saved-window-configuration |
| 803 | (set-window-configuration image-dired-saved-window-configuration) |
| 804 | (message "No saved window configuration"))) |
| 805 | |
| 806 | ;;;###autoload |
| 807 | (defun image-dired-display-thumbs (&optional arg append do-not-pop) |
| 808 | "Display thumbnails of all marked files, in `image-dired-thumbnail-buffer'. |
| 809 | If a thumbnail image does not exist for a file, it is created on the |
| 810 | fly. With prefix argument ARG, display only thumbnail for file at |
| 811 | point (this is useful if you have marked some files but want to show |
| 812 | another one). |
| 813 | |
| 814 | Recommended usage is to split the current frame horizontally so that |
| 815 | you have the dired buffer in the left window and the |
| 816 | `image-dired-thumbnail-buffer' buffer in the right window. |
| 817 | |
| 818 | With optional argument APPEND, append thumbnail to thumbnail buffer |
| 819 | instead of erasing it first. |
| 820 | |
| 821 | Optional argument DO-NOT-POP controls if `pop-to-buffer' should be |
| 822 | used or not. If non-nil, use `display-buffer' instead of |
| 823 | `pop-to-buffer'. This is used from functions like |
| 824 | `image-dired-next-line-and-display' and |
| 825 | `image-dired-previous-line-and-display' where we do not want the |
| 826 | thumbnail buffer to be selected." |
| 827 | (interactive "P") |
| 828 | (let ((buf (image-dired-create-thumbnail-buffer)) |
| 829 | thumb-name files dired-buf) |
| 830 | (if arg |
| 831 | (setq files (list (dired-get-filename))) |
| 832 | (setq files (dired-get-marked-files))) |
| 833 | (setq dired-buf (current-buffer)) |
| 834 | (with-current-buffer buf |
| 835 | (let ((inhibit-read-only t)) |
| 836 | (if (not append) |
| 837 | (erase-buffer) |
| 838 | (goto-char (point-max))) |
| 839 | (mapc |
| 840 | (lambda (curr-file) |
| 841 | (setq thumb-name (image-dired-thumb-name curr-file)) |
| 842 | (if (and (not (file-exists-p thumb-name)) |
| 843 | (not (= 0 (image-dired-create-thumb curr-file thumb-name)))) |
| 844 | (message "Thumb could not be created for file %s" curr-file) |
| 845 | (image-dired-insert-thumbnail thumb-name curr-file dired-buf))) |
| 846 | files)) |
| 847 | (cond ((eq 'dynamic image-dired-line-up-method) |
| 848 | (image-dired-line-up-dynamic)) |
| 849 | ((eq 'fixed image-dired-line-up-method) |
| 850 | (image-dired-line-up)) |
| 851 | ((eq 'interactive image-dired-line-up-method) |
| 852 | (image-dired-line-up-interactive)) |
| 853 | ((eq 'none image-dired-line-up-method) |
| 854 | nil) |
| 855 | (t |
| 856 | (image-dired-line-up-dynamic)))) |
| 857 | (if do-not-pop |
| 858 | (display-buffer image-dired-thumbnail-buffer) |
| 859 | (pop-to-buffer image-dired-thumbnail-buffer)))) |
| 860 | |
| 861 | ;;;###autoload |
| 862 | (defun image-dired-show-all-from-dir (dir) |
| 863 | "Make a preview buffer for all images in DIR and display it. |
| 864 | If the number of files in DIR matching `image-file-name-regexp' |
| 865 | exceeds `image-dired-show-all-from-dir-max-files', a warning will be |
| 866 | displayed." |
| 867 | (interactive "DDir: ") |
| 868 | (dired dir) |
| 869 | (dired-mark-files-regexp (image-file-name-regexp)) |
| 870 | (let ((files (dired-get-marked-files))) |
| 871 | (if (or (<= (length files) image-dired-show-all-from-dir-max-files) |
| 872 | (and (> (length files) image-dired-show-all-from-dir-max-files) |
| 873 | (y-or-n-p |
| 874 | (format |
| 875 | "Directory contains more than %d image files. Proceed? " |
| 876 | image-dired-show-all-from-dir-max-files)))) |
| 877 | (progn |
| 878 | (image-dired-display-thumbs) |
| 879 | (pop-to-buffer image-dired-thumbnail-buffer)) |
| 880 | (message "Cancelled.")))) |
| 881 | |
| 882 | ;;;###autoload |
| 883 | (defalias 'image-dired 'image-dired-show-all-from-dir) |
| 884 | |
| 885 | ;;;###autoload |
| 886 | (define-obsolete-function-alias 'tumme 'image-dired "24.4") |
| 887 | |
| 888 | (defun image-dired-sane-db-file () |
| 889 | "Check if `image-dired-db-file' exists. |
| 890 | If not, try to create it (including any parent directories). |
| 891 | Signal error if there are problems creating it." |
| 892 | (or (file-exists-p image-dired-db-file) |
| 893 | (let (dir buf) |
| 894 | (unless (file-directory-p (setq dir (file-name-directory |
| 895 | image-dired-db-file))) |
| 896 | (make-directory dir t)) |
| 897 | (with-current-buffer (setq buf (create-file-buffer |
| 898 | image-dired-db-file)) |
| 899 | (write-file image-dired-db-file)) |
| 900 | (kill-buffer buf) |
| 901 | (file-exists-p image-dired-db-file)) |
| 902 | (error "Could not create %s" image-dired-db-file))) |
| 903 | |
| 904 | (defun image-dired-write-tags (file-tags) |
| 905 | "Write file tags to database. |
| 906 | Write each file and tag in FILE-TAGS to the database. |
| 907 | FILE-TAGS is an alist in the following form: |
| 908 | ((FILE . TAG) ... )" |
| 909 | (image-dired-sane-db-file) |
| 910 | (let (end file tag) |
| 911 | (image-dired--with-db-file |
| 912 | (setq buffer-file-name image-dired-db-file) |
| 913 | (dolist (elt file-tags) |
| 914 | (setq file (car elt) |
| 915 | tag (cdr elt)) |
| 916 | (goto-char (point-min)) |
| 917 | (if (search-forward-regexp (format "^%s.*$" file) nil t) |
| 918 | (progn |
| 919 | (setq end (point)) |
| 920 | (beginning-of-line) |
| 921 | (when (not (search-forward (format ";%s" tag) end t)) |
| 922 | (end-of-line) |
| 923 | (insert (format ";%s" tag)))) |
| 924 | (goto-char (point-max)) |
| 925 | (insert (format "\n%s;%s" file tag)))) |
| 926 | (save-buffer)))) |
| 927 | |
| 928 | (defun image-dired-remove-tag (files tag) |
| 929 | "For all FILES, remove TAG from the image database." |
| 930 | (image-dired-sane-db-file) |
| 931 | (image-dired--with-db-file |
| 932 | (setq buffer-file-name image-dired-db-file) |
| 933 | (let (end) |
| 934 | (unless (listp files) |
| 935 | (if (stringp files) |
| 936 | (setq files (list files)) |
| 937 | (error "Files must be a string or a list of strings!"))) |
| 938 | (dolist (file files) |
| 939 | (goto-char (point-min)) |
| 940 | (when (search-forward-regexp (format "^%s" file) nil t) |
| 941 | (end-of-line) |
| 942 | (setq end (point)) |
| 943 | (beginning-of-line) |
| 944 | (when (search-forward-regexp (format "\\(;%s\\)" tag) end t) |
| 945 | (delete-region (match-beginning 1) (match-end 1)) |
| 946 | ;; Check if file should still be in the database. If |
| 947 | ;; it has no tags or comments, it will be removed. |
| 948 | (end-of-line) |
| 949 | (setq end (point)) |
| 950 | (beginning-of-line) |
| 951 | (when (not (search-forward ";" end t)) |
| 952 | (kill-line 1) |
| 953 | ;; If on empty line at end of buffer |
| 954 | (and (eobp) |
| 955 | (looking-at "^$") |
| 956 | (delete-char -1))))))) |
| 957 | (save-buffer))) |
| 958 | |
| 959 | (defun image-dired-list-tags (file) |
| 960 | "Read all tags for image FILE from the image database." |
| 961 | (image-dired-sane-db-file) |
| 962 | (image-dired--with-db-file |
| 963 | (let (end (tags "")) |
| 964 | (when (search-forward-regexp (format "^%s" file) nil t) |
| 965 | (end-of-line) |
| 966 | (setq end (point)) |
| 967 | (beginning-of-line) |
| 968 | (if (search-forward ";" end t) |
| 969 | (if (search-forward "comment:" end t) |
| 970 | (if (search-forward ";" end t) |
| 971 | (setq tags (buffer-substring (point) end))) |
| 972 | (setq tags (buffer-substring (point) end))))) |
| 973 | (split-string tags ";")))) |
| 974 | |
| 975 | ;;;###autoload |
| 976 | (defun image-dired-tag-files (arg) |
| 977 | "Tag marked file(s) in dired. With prefix ARG, tag file at point." |
| 978 | (interactive "P") |
| 979 | (let ((tag (read-string "Tags to add (separate tags with a semicolon): ")) |
| 980 | files) |
| 981 | (if arg |
| 982 | (setq files (list (dired-get-filename))) |
| 983 | (setq files (dired-get-marked-files))) |
| 984 | (image-dired-write-tags |
| 985 | (mapcar |
| 986 | (lambda (x) |
| 987 | (cons x tag)) |
| 988 | files)))) |
| 989 | |
| 990 | (defun image-dired-tag-thumbnail () |
| 991 | "Tag current thumbnail." |
| 992 | (interactive) |
| 993 | (let ((tag (read-string "Tags to add (separate tags with a semicolon): "))) |
| 994 | (image-dired-write-tags (list (cons (image-dired-original-file-name) tag)))) |
| 995 | (image-dired-update-property |
| 996 | 'tags (image-dired-list-tags (image-dired-original-file-name)))) |
| 997 | |
| 998 | ;;;###autoload |
| 999 | (defun image-dired-delete-tag (arg) |
| 1000 | "Remove tag for selected file(s). |
| 1001 | With prefix argument ARG, remove tag from file at point." |
| 1002 | (interactive "P") |
| 1003 | (let ((tag (read-string "Tag to remove: ")) |
| 1004 | files) |
| 1005 | (if arg |
| 1006 | (setq files (list (dired-get-filename))) |
| 1007 | (setq files (dired-get-marked-files))) |
| 1008 | (image-dired-remove-tag files tag))) |
| 1009 | |
| 1010 | (defun image-dired-tag-thumbnail-remove () |
| 1011 | "Remove tag from thumbnail." |
| 1012 | (interactive) |
| 1013 | (let ((tag (read-string "Tag to remove: "))) |
| 1014 | (image-dired-remove-tag (image-dired-original-file-name) tag)) |
| 1015 | (image-dired-update-property |
| 1016 | 'tags (image-dired-list-tags (image-dired-original-file-name)))) |
| 1017 | |
| 1018 | (defun image-dired-original-file-name () |
| 1019 | "Get original file name for thumbnail or display image at point." |
| 1020 | (get-text-property (point) 'original-file-name)) |
| 1021 | |
| 1022 | (defun image-dired-associated-dired-buffer () |
| 1023 | "Get associated dired buffer at point." |
| 1024 | (get-text-property (point) 'associated-dired-buffer)) |
| 1025 | |
| 1026 | (defun image-dired-get-buffer-window (buf) |
| 1027 | "Return window where buffer BUF is." |
| 1028 | (get-window-with-predicate |
| 1029 | (lambda (window) |
| 1030 | (equal (window-buffer window) buf)) |
| 1031 | nil t)) |
| 1032 | |
| 1033 | (defun image-dired-track-original-file () |
| 1034 | "Track the original file in the associated dired buffer. |
| 1035 | See documentation for `image-dired-toggle-movement-tracking'. |
| 1036 | Interactive use only useful if `image-dired-track-movement' is nil." |
| 1037 | (interactive) |
| 1038 | (let ((old-buf (current-buffer)) |
| 1039 | (dired-buf (image-dired-associated-dired-buffer)) |
| 1040 | (file-name (image-dired-original-file-name))) |
| 1041 | (when (and (buffer-live-p dired-buf) file-name) |
| 1042 | (set-buffer dired-buf) |
| 1043 | (if (not (dired-goto-file file-name)) |
| 1044 | (message "Could not track file") |
| 1045 | (set-window-point |
| 1046 | (image-dired-get-buffer-window dired-buf) (point))) |
| 1047 | (set-buffer old-buf)))) |
| 1048 | |
| 1049 | (defun image-dired-toggle-movement-tracking () |
| 1050 | "Turn on and off `image-dired-track-movement'. |
| 1051 | Tracking of the movements between thumbnail and dired buffer so that |
| 1052 | they are \"mirrored\" in the dired buffer. When this is on, moving |
| 1053 | around in the thumbnail or dired buffer will find the matching |
| 1054 | position in the other buffer." |
| 1055 | (interactive) |
| 1056 | (setq image-dired-track-movement (not image-dired-track-movement)) |
| 1057 | (message "Tracking %s" (if image-dired-track-movement "on" "off"))) |
| 1058 | |
| 1059 | (defun image-dired-track-thumbnail () |
| 1060 | "Track current dired file's thumb in `image-dired-thumbnail-buffer'. |
| 1061 | This is almost the same as what `image-dired-track-original-file' does, |
| 1062 | but the other way around." |
| 1063 | (let ((file (dired-get-filename)) |
| 1064 | (old-buf (current-buffer)) |
| 1065 | prop-val found) |
| 1066 | (when (get-buffer image-dired-thumbnail-buffer) |
| 1067 | (set-buffer image-dired-thumbnail-buffer) |
| 1068 | (goto-char (point-min)) |
| 1069 | (while (and (not (eobp)) |
| 1070 | (not found)) |
| 1071 | (if (and (setq prop-val |
| 1072 | (get-text-property (point) 'original-file-name)) |
| 1073 | (string= prop-val file)) |
| 1074 | (setq found t)) |
| 1075 | (if (not found) |
| 1076 | (forward-char 1))) |
| 1077 | (when found |
| 1078 | (set-window-point |
| 1079 | (image-dired-thumbnail-window) (point)) |
| 1080 | (image-dired-display-thumb-properties)) |
| 1081 | (set-buffer old-buf)))) |
| 1082 | |
| 1083 | (defun image-dired-dired-next-line (&optional arg) |
| 1084 | "Call `dired-next-line', then track thumbnail. |
| 1085 | This can safely replace `dired-next-line'. |
| 1086 | With prefix argument, move ARG lines." |
| 1087 | (interactive "P") |
| 1088 | (dired-next-line (or arg 1)) |
| 1089 | (if image-dired-track-movement |
| 1090 | (image-dired-track-thumbnail))) |
| 1091 | |
| 1092 | (defun image-dired-dired-previous-line (&optional arg) |
| 1093 | "Call `dired-previous-line', then track thumbnail. |
| 1094 | This can safely replace `dired-previous-line'. |
| 1095 | With prefix argument, move ARG lines." |
| 1096 | (interactive "P") |
| 1097 | (dired-previous-line (or arg 1)) |
| 1098 | (if image-dired-track-movement |
| 1099 | (image-dired-track-thumbnail))) |
| 1100 | |
| 1101 | (defun image-dired-forward-image (&optional arg) |
| 1102 | "Move to next image and display properties. |
| 1103 | Optional prefix ARG says how many images to move; default is one |
| 1104 | image." |
| 1105 | (interactive "p") |
| 1106 | (let (pos (steps (or arg 1))) |
| 1107 | (dotimes (i steps) |
| 1108 | (if (and (not (eobp)) |
| 1109 | (save-excursion |
| 1110 | (forward-char) |
| 1111 | (while (and (not (eobp)) |
| 1112 | (not (image-dired-image-at-point-p))) |
| 1113 | (forward-char)) |
| 1114 | (setq pos (point)) |
| 1115 | (image-dired-image-at-point-p))) |
| 1116 | (goto-char pos) |
| 1117 | (error "At last image")))) |
| 1118 | (when image-dired-track-movement |
| 1119 | (image-dired-track-original-file)) |
| 1120 | (image-dired-display-thumb-properties)) |
| 1121 | |
| 1122 | (defun image-dired-backward-image (&optional arg) |
| 1123 | "Move to previous image and display properties. |
| 1124 | Optional prefix ARG says how many images to move; default is one |
| 1125 | image." |
| 1126 | (interactive "p") |
| 1127 | (let (pos (steps (or arg 1))) |
| 1128 | (dotimes (i steps) |
| 1129 | (if (and (not (bobp)) |
| 1130 | (save-excursion |
| 1131 | (backward-char) |
| 1132 | (while (and (not (bobp)) |
| 1133 | (not (image-dired-image-at-point-p))) |
| 1134 | (backward-char)) |
| 1135 | (setq pos (point)) |
| 1136 | (image-dired-image-at-point-p))) |
| 1137 | (goto-char pos) |
| 1138 | (error "At first image")))) |
| 1139 | (when image-dired-track-movement |
| 1140 | (image-dired-track-original-file)) |
| 1141 | (image-dired-display-thumb-properties)) |
| 1142 | |
| 1143 | (defun image-dired-next-line () |
| 1144 | "Move to next line and display properties." |
| 1145 | (interactive) |
| 1146 | (forward-line 1) |
| 1147 | ;; If we end up in an empty spot, back up to the next thumbnail. |
| 1148 | (if (not (image-dired-image-at-point-p)) |
| 1149 | (image-dired-backward-image)) |
| 1150 | (if image-dired-track-movement |
| 1151 | (image-dired-track-original-file)) |
| 1152 | (image-dired-display-thumb-properties)) |
| 1153 | |
| 1154 | |
| 1155 | (defun image-dired-previous-line () |
| 1156 | "Move to previous line and display properties." |
| 1157 | (interactive) |
| 1158 | (forward-line -1) |
| 1159 | ;; If we end up in an empty spot, back up to the next |
| 1160 | ;; thumbnail. This should only happen if the user deleted a |
| 1161 | ;; thumbnail and did not refresh, so it is not very common. But we |
| 1162 | ;; can handle it in a good manner, so why not? |
| 1163 | (if (not (image-dired-image-at-point-p)) |
| 1164 | (image-dired-backward-image)) |
| 1165 | (if image-dired-track-movement |
| 1166 | (image-dired-track-original-file)) |
| 1167 | (image-dired-display-thumb-properties)) |
| 1168 | |
| 1169 | (defun image-dired-format-properties-string (buf file props comment) |
| 1170 | "Format display properties. |
| 1171 | BUF is the associated dired buffer, FILE is the original image file |
| 1172 | name, PROPS is a list of tags and COMMENT is the image file's |
| 1173 | comment." |
| 1174 | (format-spec |
| 1175 | image-dired-display-properties-format |
| 1176 | (list |
| 1177 | (cons ?b (or buf "")) |
| 1178 | (cons ?f file) |
| 1179 | (cons ?t (or (princ props) "")) |
| 1180 | (cons ?c (or comment ""))))) |
| 1181 | |
| 1182 | (defun image-dired-display-thumb-properties () |
| 1183 | "Display thumbnail properties in the echo area." |
| 1184 | (if (not (eobp)) |
| 1185 | (let ((file-name (file-name-nondirectory (image-dired-original-file-name))) |
| 1186 | (dired-buf (buffer-name (image-dired-associated-dired-buffer))) |
| 1187 | (props (mapconcat |
| 1188 | 'princ |
| 1189 | (get-text-property (point) 'tags) |
| 1190 | ", ")) |
| 1191 | (comment (get-text-property (point) 'comment))) |
| 1192 | (if file-name |
| 1193 | (message "%s" |
| 1194 | (image-dired-format-properties-string |
| 1195 | dired-buf |
| 1196 | file-name |
| 1197 | props |
| 1198 | comment)))))) |
| 1199 | |
| 1200 | (defun image-dired-dired-file-marked-p () |
| 1201 | "Check whether file on current line is marked or not." |
| 1202 | (save-excursion |
| 1203 | (beginning-of-line) |
| 1204 | (not (looking-at "^ .*$")))) |
| 1205 | |
| 1206 | (defun image-dired-modify-mark-on-thumb-original-file (command) |
| 1207 | "Modify mark in dired buffer. |
| 1208 | COMMAND is one of 'mark for marking file in dired, 'unmark for |
| 1209 | unmarking file in dired or 'flag for flagging file for delete in |
| 1210 | dired." |
| 1211 | (let ((file-name (image-dired-original-file-name)) |
| 1212 | (dired-buf (image-dired-associated-dired-buffer))) |
| 1213 | (if (not (and dired-buf file-name)) |
| 1214 | (message "No image, or image with correct properties, at point.") |
| 1215 | (with-current-buffer dired-buf |
| 1216 | (message "%s" file-name) |
| 1217 | (if (dired-goto-file file-name) |
| 1218 | (cond ((eq command 'mark) (dired-mark 1)) |
| 1219 | ((eq command 'unmark) (dired-unmark 1)) |
| 1220 | ((eq command 'toggle) |
| 1221 | (if (image-dired-dired-file-marked-p) |
| 1222 | (dired-unmark 1) |
| 1223 | (dired-mark 1))) |
| 1224 | ((eq command 'flag) (dired-flag-file-deletion 1)))))))) |
| 1225 | |
| 1226 | (defun image-dired-mark-thumb-original-file () |
| 1227 | "Mark original image file in associated dired buffer." |
| 1228 | (interactive) |
| 1229 | (image-dired-modify-mark-on-thumb-original-file 'mark) |
| 1230 | (image-dired-forward-image)) |
| 1231 | |
| 1232 | (defun image-dired-unmark-thumb-original-file () |
| 1233 | "Unmark original image file in associated dired buffer." |
| 1234 | (interactive) |
| 1235 | (image-dired-modify-mark-on-thumb-original-file 'unmark) |
| 1236 | (image-dired-forward-image)) |
| 1237 | |
| 1238 | (defun image-dired-flag-thumb-original-file () |
| 1239 | "Flag original image file for deletion in associated dired buffer." |
| 1240 | (interactive) |
| 1241 | (image-dired-modify-mark-on-thumb-original-file 'flag) |
| 1242 | (image-dired-forward-image)) |
| 1243 | |
| 1244 | (defun image-dired-toggle-mark-thumb-original-file () |
| 1245 | "Toggle mark on original image file in associated dired buffer." |
| 1246 | (interactive) |
| 1247 | (image-dired-modify-mark-on-thumb-original-file 'toggle)) |
| 1248 | |
| 1249 | (defun image-dired-jump-original-dired-buffer () |
| 1250 | "Jump to the dired buffer associated with the current image file. |
| 1251 | You probably want to use this together with |
| 1252 | `image-dired-track-original-file'." |
| 1253 | (interactive) |
| 1254 | (let ((buf (image-dired-associated-dired-buffer)) |
| 1255 | window frame) |
| 1256 | (setq window (image-dired-get-buffer-window buf)) |
| 1257 | (if window |
| 1258 | (progn |
| 1259 | (if (not (equal (selected-frame) (setq frame (window-frame window)))) |
| 1260 | (select-frame-set-input-focus frame)) |
| 1261 | (select-window window)) |
| 1262 | (message "Associated dired buffer not visible")))) |
| 1263 | |
| 1264 | ;;;###autoload |
| 1265 | (defun image-dired-jump-thumbnail-buffer () |
| 1266 | "Jump to thumbnail buffer." |
| 1267 | (interactive) |
| 1268 | (let ((window (image-dired-thumbnail-window)) |
| 1269 | frame) |
| 1270 | (if window |
| 1271 | (progn |
| 1272 | (if (not (equal (selected-frame) (setq frame (window-frame window)))) |
| 1273 | (select-frame-set-input-focus frame)) |
| 1274 | (select-window window)) |
| 1275 | (message "Thumbnail buffer not visible")))) |
| 1276 | |
| 1277 | (defvar image-dired-thumbnail-mode-map (make-sparse-keymap) |
| 1278 | "Keymap for `image-dired-thumbnail-mode'.") |
| 1279 | |
| 1280 | (defvar image-dired-thumbnail-mode-line-up-map (make-sparse-keymap) |
| 1281 | "Keymap for line-up commands in `image-dired-thumbnail-mode'.") |
| 1282 | |
| 1283 | (defvar image-dired-thumbnail-mode-tag-map (make-sparse-keymap) |
| 1284 | "Keymap for tag commands in `image-dired-thumbnail-mode'.") |
| 1285 | |
| 1286 | (defun image-dired-define-thumbnail-mode-keymap () |
| 1287 | "Define keymap for `image-dired-thumbnail-mode'." |
| 1288 | |
| 1289 | ;; Keys |
| 1290 | (define-key image-dired-thumbnail-mode-map [right] 'image-dired-forward-image) |
| 1291 | (define-key image-dired-thumbnail-mode-map [left] 'image-dired-backward-image) |
| 1292 | (define-key image-dired-thumbnail-mode-map [up] 'image-dired-previous-line) |
| 1293 | (define-key image-dired-thumbnail-mode-map [down] 'image-dired-next-line) |
| 1294 | (define-key image-dired-thumbnail-mode-map "\C-f" 'image-dired-forward-image) |
| 1295 | (define-key image-dired-thumbnail-mode-map "\C-b" 'image-dired-backward-image) |
| 1296 | (define-key image-dired-thumbnail-mode-map "\C-p" 'image-dired-previous-line) |
| 1297 | (define-key image-dired-thumbnail-mode-map "\C-n" 'image-dired-next-line) |
| 1298 | |
| 1299 | (define-key image-dired-thumbnail-mode-map "d" 'image-dired-flag-thumb-original-file) |
| 1300 | (define-key image-dired-thumbnail-mode-map [delete] |
| 1301 | 'image-dired-flag-thumb-original-file) |
| 1302 | (define-key image-dired-thumbnail-mode-map "m" 'image-dired-mark-thumb-original-file) |
| 1303 | (define-key image-dired-thumbnail-mode-map "u" 'image-dired-unmark-thumb-original-file) |
| 1304 | (define-key image-dired-thumbnail-mode-map "." 'image-dired-track-original-file) |
| 1305 | (define-key image-dired-thumbnail-mode-map [tab] 'image-dired-jump-original-dired-buffer) |
| 1306 | |
| 1307 | ;; add line-up map |
| 1308 | (define-key image-dired-thumbnail-mode-map "g" image-dired-thumbnail-mode-line-up-map) |
| 1309 | |
| 1310 | ;; map it to "g" so that the user can press it more quickly |
| 1311 | (define-key image-dired-thumbnail-mode-line-up-map "g" 'image-dired-line-up-dynamic) |
| 1312 | ;; "f" for "fixed" number of thumbs per row |
| 1313 | (define-key image-dired-thumbnail-mode-line-up-map "f" 'image-dired-line-up) |
| 1314 | ;; "i" for "interactive" |
| 1315 | (define-key image-dired-thumbnail-mode-line-up-map "i" 'image-dired-line-up-interactive) |
| 1316 | |
| 1317 | ;; add tag map |
| 1318 | (define-key image-dired-thumbnail-mode-map "t" image-dired-thumbnail-mode-tag-map) |
| 1319 | |
| 1320 | ;; map it to "t" so that the user can press it more quickly |
| 1321 | (define-key image-dired-thumbnail-mode-tag-map "t" 'image-dired-tag-thumbnail) |
| 1322 | ;; "r" for "remove" |
| 1323 | (define-key image-dired-thumbnail-mode-tag-map "r" 'image-dired-tag-thumbnail-remove) |
| 1324 | |
| 1325 | (define-key image-dired-thumbnail-mode-map "\C-m" |
| 1326 | 'image-dired-display-thumbnail-original-image) |
| 1327 | (define-key image-dired-thumbnail-mode-map [C-return] |
| 1328 | 'image-dired-thumbnail-display-external) |
| 1329 | |
| 1330 | (define-key image-dired-thumbnail-mode-map "l" 'image-dired-rotate-thumbnail-left) |
| 1331 | (define-key image-dired-thumbnail-mode-map "r" 'image-dired-rotate-thumbnail-right) |
| 1332 | |
| 1333 | (define-key image-dired-thumbnail-mode-map "L" 'image-dired-rotate-original-left) |
| 1334 | (define-key image-dired-thumbnail-mode-map "R" 'image-dired-rotate-original-right) |
| 1335 | |
| 1336 | (define-key image-dired-thumbnail-mode-map "D" |
| 1337 | 'image-dired-thumbnail-set-image-description) |
| 1338 | |
| 1339 | (define-key image-dired-thumbnail-mode-map "\C-d" 'image-dired-delete-char) |
| 1340 | (define-key image-dired-thumbnail-mode-map " " |
| 1341 | 'image-dired-display-next-thumbnail-original) |
| 1342 | (define-key image-dired-thumbnail-mode-map |
| 1343 | (kbd "DEL") 'image-dired-display-previous-thumbnail-original) |
| 1344 | (define-key image-dired-thumbnail-mode-map "c" 'image-dired-comment-thumbnail) |
| 1345 | (define-key image-dired-thumbnail-mode-map "q" 'image-dired-kill-buffer-and-window) |
| 1346 | |
| 1347 | ;; Mouse |
| 1348 | (define-key image-dired-thumbnail-mode-map [mouse-2] 'image-dired-mouse-display-image) |
| 1349 | (define-key image-dired-thumbnail-mode-map [mouse-1] 'image-dired-mouse-select-thumbnail) |
| 1350 | |
| 1351 | ;; Seems I must first set C-down-mouse-1 to undefined, or else it |
| 1352 | ;; will trigger the buffer menu. If I try to instead bind |
| 1353 | ;; C-down-mouse-1 to `image-dired-mouse-toggle-mark', I get a message |
| 1354 | ;; about C-mouse-1 not being defined afterwards. Annoying, but I |
| 1355 | ;; probably do not completely understand mouse events. |
| 1356 | |
| 1357 | (define-key image-dired-thumbnail-mode-map [C-down-mouse-1] 'undefined) |
| 1358 | (define-key image-dired-thumbnail-mode-map [C-mouse-1] 'image-dired-mouse-toggle-mark) |
| 1359 | |
| 1360 | ;; Menu |
| 1361 | (define-key image-dired-thumbnail-mode-map [menu-bar image-dired] |
| 1362 | (cons "Image-Dired" (make-sparse-keymap "Image-Dired"))) |
| 1363 | |
| 1364 | (define-key image-dired-thumbnail-mode-map |
| 1365 | [menu-bar image-dired image-dired-kill-buffer-and-window] |
| 1366 | '("Quit" . image-dired-kill-buffer-and-window)) |
| 1367 | |
| 1368 | (define-key image-dired-thumbnail-mode-map |
| 1369 | [menu-bar image-dired image-dired-delete-char] |
| 1370 | '("Delete thumbnail from buffer" . image-dired-delete-char)) |
| 1371 | |
| 1372 | (define-key image-dired-thumbnail-mode-map |
| 1373 | [menu-bar image-dired image-dired-tag-thumbnail-remove] |
| 1374 | '("Remove tag from thumbnail" . image-dired-tag-thumbnail-remove)) |
| 1375 | |
| 1376 | (define-key image-dired-thumbnail-mode-map |
| 1377 | [menu-bar image-dired image-dired-tag-thumbnail] |
| 1378 | '("Tag thumbnail" . image-dired-tag-thumbnail)) |
| 1379 | |
| 1380 | (define-key image-dired-thumbnail-mode-map |
| 1381 | [menu-bar image-dired image-dired-comment-thumbnail] |
| 1382 | '("Comment thumbnail" . image-dired-comment-thumbnail)) |
| 1383 | |
| 1384 | (define-key image-dired-thumbnail-mode-map |
| 1385 | [menu-bar image-dired image-dired-refresh-thumb] |
| 1386 | '("Refresh thumb" . image-dired-refresh-thumb)) |
| 1387 | (define-key image-dired-thumbnail-mode-map |
| 1388 | [menu-bar image-dired image-dired-line-up-dynamic] |
| 1389 | '("Dynamic line up" . image-dired-line-up-dynamic)) |
| 1390 | (define-key image-dired-thumbnail-mode-map |
| 1391 | [menu-bar image-dired image-dired-line-up] |
| 1392 | '("Line up thumbnails" . image-dired-line-up)) |
| 1393 | |
| 1394 | (define-key image-dired-thumbnail-mode-map |
| 1395 | [menu-bar image-dired image-dired-rotate-thumbnail-left] |
| 1396 | '("Rotate thumbnail left" . image-dired-rotate-thumbnail-left)) |
| 1397 | (define-key image-dired-thumbnail-mode-map |
| 1398 | [menu-bar image-dired image-dired-rotate-thumbnail-right] |
| 1399 | '("Rotate thumbnail right" . image-dired-rotate-thumbnail-right)) |
| 1400 | |
| 1401 | (define-key image-dired-thumbnail-mode-map |
| 1402 | [menu-bar image-dired image-dired-rotate-original-left] |
| 1403 | '("Rotate original left" . image-dired-rotate-original-left)) |
| 1404 | (define-key image-dired-thumbnail-mode-map |
| 1405 | [menu-bar image-dired image-dired-rotate-original-right] |
| 1406 | '("Rotate original right" . image-dired-rotate-original-right)) |
| 1407 | |
| 1408 | (define-key image-dired-thumbnail-mode-map |
| 1409 | [menu-bar image-dired image-dired-toggle-movement-tracking] |
| 1410 | '("Toggle movement tracking on/off" . image-dired-toggle-movement-tracking)) |
| 1411 | |
| 1412 | (define-key image-dired-thumbnail-mode-map |
| 1413 | [menu-bar image-dired image-dired-jump-original-dired-buffer] |
| 1414 | '("Jump to dired buffer" . image-dired-jump-original-dired-buffer)) |
| 1415 | (define-key image-dired-thumbnail-mode-map |
| 1416 | [menu-bar image-dired image-dired-track-original-file] |
| 1417 | '("Track original" . image-dired-track-original-file)) |
| 1418 | |
| 1419 | (define-key image-dired-thumbnail-mode-map |
| 1420 | [menu-bar image-dired image-dired-flag-thumb-original-file] |
| 1421 | '("Flag original for deletion" . image-dired-flag-thumb-original-file)) |
| 1422 | (define-key image-dired-thumbnail-mode-map |
| 1423 | [menu-bar image-dired image-dired-unmark-thumb-original-file] |
| 1424 | '("Unmark original" . image-dired-unmark-thumb-original-file)) |
| 1425 | (define-key image-dired-thumbnail-mode-map |
| 1426 | [menu-bar image-dired image-dired-mark-thumb-original-file] |
| 1427 | '("Mark original" . image-dired-mark-thumb-original-file)) |
| 1428 | |
| 1429 | (define-key image-dired-thumbnail-mode-map |
| 1430 | [menu-bar image-dired image-dired-thumbnail-display-external] |
| 1431 | '("Display in external viewer" . image-dired-thumbnail-display-external)) |
| 1432 | (define-key image-dired-thumbnail-mode-map |
| 1433 | [menu-bar image-dired image-dired-display-thumbnail-original-image] |
| 1434 | '("Display image" . image-dired-display-thumbnail-original-image))) |
| 1435 | |
| 1436 | (defvar image-dired-display-image-mode-map (make-sparse-keymap) |
| 1437 | "Keymap for `image-dired-display-image-mode'.") |
| 1438 | |
| 1439 | (defun image-dired-define-display-image-mode-keymap () |
| 1440 | "Define keymap for `image-dired-display-image-mode'." |
| 1441 | |
| 1442 | ;; Keys |
| 1443 | (define-key image-dired-display-image-mode-map "q" 'image-dired-kill-buffer-and-window) |
| 1444 | |
| 1445 | (define-key image-dired-display-image-mode-map "f" |
| 1446 | 'image-dired-display-current-image-full) |
| 1447 | |
| 1448 | (define-key image-dired-display-image-mode-map "s" |
| 1449 | 'image-dired-display-current-image-sized) |
| 1450 | |
| 1451 | ;; Menu |
| 1452 | (define-key image-dired-display-image-mode-map [menu-bar image-dired] |
| 1453 | (cons "Image-Dired" (make-sparse-keymap "Image-Dired"))) |
| 1454 | |
| 1455 | (define-key image-dired-display-image-mode-map |
| 1456 | [menu-bar image-dired image-dired-kill-buffer-and-window] |
| 1457 | '("Quit" . image-dired-kill-buffer-and-window)) |
| 1458 | |
| 1459 | (define-key image-dired-display-image-mode-map |
| 1460 | [menu-bar image-dired image-dired-display-current-image-sized] |
| 1461 | '("Display original, sized to fit" . image-dired-display-current-image-sized)) |
| 1462 | |
| 1463 | (define-key image-dired-display-image-mode-map |
| 1464 | [menu-bar image-dired image-dired-display-current-image-full] |
| 1465 | '("Display original, full size" . image-dired-display-current-image-full)) |
| 1466 | |
| 1467 | ) |
| 1468 | |
| 1469 | (defun image-dired-display-current-image-full () |
| 1470 | "Display current image in full size." |
| 1471 | (interactive) |
| 1472 | (let ((file (image-dired-original-file-name))) |
| 1473 | (if file |
| 1474 | (progn |
| 1475 | (image-dired-display-image file t) |
| 1476 | (message "Full size image displayed")) |
| 1477 | (error "No original file name at point")))) |
| 1478 | |
| 1479 | (defun image-dired-display-current-image-sized () |
| 1480 | "Display current image in sized to fit window dimensions." |
| 1481 | (interactive) |
| 1482 | (let ((file (image-dired-original-file-name))) |
| 1483 | (if file |
| 1484 | (progn |
| 1485 | (image-dired-display-image file) |
| 1486 | (message "Full size image displayed")) |
| 1487 | (error "No original file name at point")))) |
| 1488 | |
| 1489 | (define-derived-mode image-dired-thumbnail-mode |
| 1490 | fundamental-mode "image-dired-thumbnail" |
| 1491 | "Browse and manipulate thumbnail images using dired. |
| 1492 | Use `image-dired-dired' and `image-dired-setup-dired-keybindings' to get a |
| 1493 | nice setup to start with." |
| 1494 | (image-dired-define-thumbnail-mode-keymap) |
| 1495 | (message "image-dired-thumbnail-mode enabled")) |
| 1496 | |
| 1497 | (define-derived-mode image-dired-display-image-mode |
| 1498 | fundamental-mode "image-dired-image-display" |
| 1499 | "Mode for displaying and manipulating original image. |
| 1500 | Resized or in full-size." |
| 1501 | (image-dired-define-display-image-mode-keymap) |
| 1502 | (message "image-dired-display-image-mode enabled")) |
| 1503 | |
| 1504 | ;;;###autoload |
| 1505 | (defun image-dired-setup-dired-keybindings () |
| 1506 | "Setup easy-to-use keybindings for the commands to be used in dired mode. |
| 1507 | Note that n, p and <down> and <up> will be hijacked and bound to |
| 1508 | `image-dired-dired-x-line'." |
| 1509 | (interactive) |
| 1510 | |
| 1511 | ;; Hijack previous and next line movement. Let C-p and C-b be |
| 1512 | ;; though... |
| 1513 | |
| 1514 | (define-key dired-mode-map "p" 'image-dired-dired-previous-line) |
| 1515 | (define-key dired-mode-map "n" 'image-dired-dired-next-line) |
| 1516 | (define-key dired-mode-map [up] 'image-dired-dired-previous-line) |
| 1517 | (define-key dired-mode-map [down] 'image-dired-dired-next-line) |
| 1518 | |
| 1519 | (define-key dired-mode-map (kbd "C-S-n") 'image-dired-next-line-and-display) |
| 1520 | (define-key dired-mode-map (kbd "C-S-p") 'image-dired-previous-line-and-display) |
| 1521 | (define-key dired-mode-map (kbd "C-S-m") 'image-dired-mark-and-display-next) |
| 1522 | |
| 1523 | (define-key dired-mode-map "\C-td" 'image-dired-display-thumbs) |
| 1524 | (define-key dired-mode-map "\C-tt" 'image-dired-tag-files) |
| 1525 | (define-key dired-mode-map "\C-tr" 'image-dired-delete-tag) |
| 1526 | (define-key dired-mode-map [tab] 'image-dired-jump-thumbnail-buffer) |
| 1527 | (define-key dired-mode-map "\C-ti" 'image-dired-dired-display-image) |
| 1528 | (define-key dired-mode-map "\C-tx" 'image-dired-dired-display-external) |
| 1529 | (define-key dired-mode-map "\C-ta" 'image-dired-display-thumbs-append) |
| 1530 | (define-key dired-mode-map "\C-t." 'image-dired-display-thumb) |
| 1531 | (define-key dired-mode-map "\C-tc" 'image-dired-dired-comment-files) |
| 1532 | (define-key dired-mode-map "\C-tf" 'image-dired-mark-tagged-files) |
| 1533 | |
| 1534 | ;; Menu for dired |
| 1535 | (define-key dired-mode-map [menu-bar image-dired] |
| 1536 | (cons "Image-Dired" (make-sparse-keymap "Image-Dired"))) |
| 1537 | |
| 1538 | (define-key dired-mode-map [menu-bar image-dired image-dired-copy-with-exif-file-name] |
| 1539 | '("Copy with EXIF file name" . image-dired-copy-with-exif-file-name)) |
| 1540 | |
| 1541 | (define-key dired-mode-map [menu-bar image-dired image-dired-dired-comment-files] |
| 1542 | '("Comment files" . image-dired-dired-comment-files)) |
| 1543 | |
| 1544 | (define-key dired-mode-map [menu-bar image-dired image-dired-mark-tagged-files] |
| 1545 | '("Mark tagged files" . image-dired-mark-tagged-files)) |
| 1546 | |
| 1547 | (define-key dired-mode-map [menu-bar image-dired image-dired-delete-tag] |
| 1548 | '("Remove tag from files" . image-dired-delete-tag)) |
| 1549 | |
| 1550 | (define-key dired-mode-map [menu-bar image-dired image-dired-tag-files] |
| 1551 | '("Tag files" . image-dired-tag-files)) |
| 1552 | |
| 1553 | (define-key dired-mode-map [menu-bar image-dired image-dired-jump-thumbnail-buffer] |
| 1554 | '("Jump to thumbnail buffer" . image-dired-jump-thumbnail-buffer)) |
| 1555 | |
| 1556 | (define-key dired-mode-map [menu-bar image-dired image-dired-toggle-movement-tracking] |
| 1557 | '("Toggle movement tracking" . image-dired-toggle-movement-tracking)) |
| 1558 | |
| 1559 | (define-key dired-mode-map |
| 1560 | [menu-bar image-dired image-dired-toggle-append-browsing] |
| 1561 | '("Toggle append browsing" . image-dired-toggle-append-browsing)) |
| 1562 | |
| 1563 | (define-key dired-mode-map |
| 1564 | [menu-bar image-dired image-dired-toggle-disp-props] |
| 1565 | '("Toggle display properties" . image-dired-toggle-dired-display-properties)) |
| 1566 | |
| 1567 | (define-key dired-mode-map |
| 1568 | [menu-bar image-dired image-dired-dired-display-external] |
| 1569 | '("Display in external viewer" . image-dired-dired-display-external)) |
| 1570 | (define-key dired-mode-map |
| 1571 | [menu-bar image-dired image-dired-dired-display-image] |
| 1572 | '("Display image" . image-dired-dired-display-image)) |
| 1573 | (define-key dired-mode-map |
| 1574 | [menu-bar image-dired image-dired-display-thumb] |
| 1575 | '("Display this thumbnail" . image-dired-display-thumb)) |
| 1576 | (define-key dired-mode-map |
| 1577 | [menu-bar image-dired image-dired-display-thumbs-append] |
| 1578 | '("Display thumbnails append" . image-dired-display-thumbs-append)) |
| 1579 | (define-key dired-mode-map |
| 1580 | [menu-bar image-dired image-dired-display-thumbs] |
| 1581 | '("Display thumbnails" . image-dired-display-thumbs)) |
| 1582 | |
| 1583 | (define-key dired-mode-map |
| 1584 | [menu-bar image-dired image-dired-create-thumbs] |
| 1585 | '("Create thumbnails for marked files" . image-dired-create-thumbs)) |
| 1586 | |
| 1587 | (define-key dired-mode-map |
| 1588 | [menu-bar image-dired image-dired-mark-and-display-next] |
| 1589 | '("Mark and display next" . image-dired-mark-and-display-next)) |
| 1590 | (define-key dired-mode-map |
| 1591 | [menu-bar image-dired image-dired-previous-line-and-display] |
| 1592 | '("Display thumb for previous file" . image-dired-previous-line-and-display)) |
| 1593 | (define-key dired-mode-map |
| 1594 | [menu-bar image-dired image-dired-next-line-and-display] |
| 1595 | '("Display thumb for next file" . image-dired-next-line-and-display))) |
| 1596 | |
| 1597 | (declare-function clear-image-cache "image.c" (&optional filter)) |
| 1598 | |
| 1599 | (defun image-dired-create-thumbs (&optional arg) |
| 1600 | "Create thumbnail images for all marked files in dired. |
| 1601 | With prefix argument ARG, create thumbnails even if they already exist |
| 1602 | \(i.e. use this to refresh your thumbnails)." |
| 1603 | (interactive "P") |
| 1604 | (let (thumb-name files) |
| 1605 | (setq files (dired-get-marked-files)) |
| 1606 | (mapcar |
| 1607 | (lambda (curr-file) |
| 1608 | (setq thumb-name (image-dired-thumb-name curr-file)) |
| 1609 | ;; If the user overrides the exist check, we must clear the |
| 1610 | ;; image cache so that if the user wants to display the |
| 1611 | ;; thumbnail, it is not fetched from cache. |
| 1612 | (if arg |
| 1613 | (clear-image-cache)) |
| 1614 | (if (or (not (file-exists-p thumb-name)) |
| 1615 | arg) |
| 1616 | (if (not (= 0 (image-dired-create-thumb curr-file |
| 1617 | (image-dired-thumb-name curr-file)))) |
| 1618 | (error "Thumb could not be created")))) |
| 1619 | files))) |
| 1620 | |
| 1621 | (defvar image-dired-slideshow-timer nil |
| 1622 | "Slideshow timer.") |
| 1623 | |
| 1624 | (defvar image-dired-slideshow-count 0 |
| 1625 | "Keeping track on number of images in slideshow.") |
| 1626 | |
| 1627 | (defvar image-dired-slideshow-times 0 |
| 1628 | "Number of pictures to display in slideshow.") |
| 1629 | |
| 1630 | (defun image-dired-slideshow-step () |
| 1631 | "Step to next file, if `image-dired-slideshow-times' has not been reached." |
| 1632 | (if (< image-dired-slideshow-count image-dired-slideshow-times) |
| 1633 | (progn |
| 1634 | (message "%s" (1+ image-dired-slideshow-count)) |
| 1635 | (setq image-dired-slideshow-count (1+ image-dired-slideshow-count)) |
| 1636 | (image-dired-next-line-and-display)) |
| 1637 | (image-dired-slideshow-stop))) |
| 1638 | |
| 1639 | (defun image-dired-slideshow-start () |
| 1640 | "Start slideshow. |
| 1641 | Ask user for number of images to show and the delay in between." |
| 1642 | (interactive) |
| 1643 | (setq image-dired-slideshow-count 0) |
| 1644 | (setq image-dired-slideshow-times (string-to-number (read-string "How many: "))) |
| 1645 | (let ((repeat (string-to-number |
| 1646 | (read-string |
| 1647 | "Delay, in seconds. Decimals are accepted : " "1")))) |
| 1648 | (setq image-dired-slideshow-timer |
| 1649 | (run-with-timer |
| 1650 | 0 repeat |
| 1651 | 'image-dired-slideshow-step)))) |
| 1652 | |
| 1653 | (defun image-dired-slideshow-stop () |
| 1654 | "Cancel slideshow." |
| 1655 | (interactive) |
| 1656 | (cancel-timer image-dired-slideshow-timer)) |
| 1657 | |
| 1658 | (defun image-dired-delete-char () |
| 1659 | "Remove current thumbnail from thumbnail buffer and line up." |
| 1660 | (interactive) |
| 1661 | (let ((inhibit-read-only t)) |
| 1662 | (delete-char 1) |
| 1663 | (if (looking-at " ") |
| 1664 | (delete-char 1)))) |
| 1665 | |
| 1666 | ;;;###autoload |
| 1667 | (defun image-dired-display-thumbs-append () |
| 1668 | "Append thumbnails to `image-dired-thumbnail-buffer'." |
| 1669 | (interactive) |
| 1670 | (image-dired-display-thumbs nil t t)) |
| 1671 | |
| 1672 | ;;;###autoload |
| 1673 | (defun image-dired-display-thumb () |
| 1674 | "Shorthand for `image-dired-display-thumbs' with prefix argument." |
| 1675 | (interactive) |
| 1676 | (image-dired-display-thumbs t nil t)) |
| 1677 | |
| 1678 | (defun image-dired-line-up () |
| 1679 | "Line up thumbnails according to `image-dired-thumbs-per-row'. |
| 1680 | See also `image-dired-line-up-dynamic'." |
| 1681 | (interactive) |
| 1682 | (let ((inhibit-read-only t)) |
| 1683 | (goto-char (point-min)) |
| 1684 | (while (and (not (image-dired-image-at-point-p)) |
| 1685 | (not (eobp))) |
| 1686 | (delete-char 1)) |
| 1687 | (while (not (eobp)) |
| 1688 | (forward-char) |
| 1689 | (while (and (not (image-dired-image-at-point-p)) |
| 1690 | (not (eobp))) |
| 1691 | (delete-char 1))) |
| 1692 | (goto-char (point-min)) |
| 1693 | (let ((count 0)) |
| 1694 | (while (not (eobp)) |
| 1695 | (forward-char) |
| 1696 | (if (= image-dired-thumbs-per-row 1) |
| 1697 | (insert "\n") |
| 1698 | (insert " ") |
| 1699 | (setq count (1+ count)) |
| 1700 | (when (and (= count (- image-dired-thumbs-per-row 1)) |
| 1701 | (not (eobp))) |
| 1702 | (forward-char) |
| 1703 | (insert "\n") |
| 1704 | (setq count 0))))) |
| 1705 | (goto-char (point-min)))) |
| 1706 | |
| 1707 | (defun image-dired-line-up-dynamic () |
| 1708 | "Line up thumbnails images dynamically. |
| 1709 | Calculate how many thumbnails fit." |
| 1710 | (interactive) |
| 1711 | (let* ((char-width (frame-char-width)) |
| 1712 | (width (image-dired-window-width-pixels (image-dired-thumbnail-window))) |
| 1713 | (image-dired-thumbs-per-row |
| 1714 | (/ width |
| 1715 | (+ (* 2 image-dired-thumb-relief) |
| 1716 | (* 2 image-dired-thumb-margin) |
| 1717 | image-dired-thumb-width char-width)))) |
| 1718 | (image-dired-line-up))) |
| 1719 | |
| 1720 | (defun image-dired-line-up-interactive () |
| 1721 | "Line up thumbnails interactively. |
| 1722 | Ask user how many thumbnails should be displayed per row." |
| 1723 | (interactive) |
| 1724 | (let ((image-dired-thumbs-per-row |
| 1725 | (string-to-number (read-string "How many thumbs per row: ")))) |
| 1726 | (if (not (> image-dired-thumbs-per-row 0)) |
| 1727 | (message "Number must be greater than 0") |
| 1728 | (image-dired-line-up)))) |
| 1729 | |
| 1730 | (defun image-dired-thumbnail-display-external () |
| 1731 | "Display original image for thumbnail at point using external viewer." |
| 1732 | (interactive) |
| 1733 | (let ((file (image-dired-original-file-name))) |
| 1734 | (if (not (image-dired-image-at-point-p)) |
| 1735 | (message "No thumbnail at point") |
| 1736 | (if (not file) |
| 1737 | (message "No original file name found") |
| 1738 | (call-process shell-file-name nil nil nil shell-command-switch |
| 1739 | (format "%s \"%s\"" image-dired-external-viewer file)))))) |
| 1740 | |
| 1741 | ;;;###autoload |
| 1742 | (defun image-dired-dired-display-external () |
| 1743 | "Display file at point using an external viewer." |
| 1744 | (interactive) |
| 1745 | (let ((file (dired-get-filename))) |
| 1746 | (call-process shell-file-name nil nil nil shell-command-switch |
| 1747 | (format "%s \"%s\"" image-dired-external-viewer file)))) |
| 1748 | |
| 1749 | (defun image-dired-window-width-pixels (window) |
| 1750 | "Calculate WINDOW width in pixels." |
| 1751 | (* (window-width window) (frame-char-width))) |
| 1752 | |
| 1753 | (defun image-dired-window-height-pixels (window) |
| 1754 | "Calculate WINDOW height in pixels." |
| 1755 | ;; Note: The mode-line consumes one line |
| 1756 | (* (- (window-height window) 1) (frame-char-height))) |
| 1757 | |
| 1758 | (defun image-dired-display-window () |
| 1759 | "Return window where `image-dired-display-image-buffer' is visible." |
| 1760 | (get-window-with-predicate |
| 1761 | (lambda (window) |
| 1762 | (equal (buffer-name (window-buffer window)) image-dired-display-image-buffer)) |
| 1763 | nil t)) |
| 1764 | |
| 1765 | (defun image-dired-thumbnail-window () |
| 1766 | "Return window where `image-dired-thumbnail-buffer' is visible." |
| 1767 | (get-window-with-predicate |
| 1768 | (lambda (window) |
| 1769 | (equal (buffer-name (window-buffer window)) image-dired-thumbnail-buffer)) |
| 1770 | nil t)) |
| 1771 | |
| 1772 | (defun image-dired-associated-dired-buffer-window () |
| 1773 | "Return window where associated dired buffer is visible." |
| 1774 | (let (buf) |
| 1775 | (if (image-dired-image-at-point-p) |
| 1776 | (progn |
| 1777 | (setq buf (image-dired-associated-dired-buffer)) |
| 1778 | (get-window-with-predicate |
| 1779 | (lambda (window) |
| 1780 | (equal (window-buffer window) buf)))) |
| 1781 | (error "No thumbnail image at point")))) |
| 1782 | |
| 1783 | (defun image-dired-display-window-width () |
| 1784 | "Return width, in pixels, of image-dired's image display window." |
| 1785 | (- (image-dired-window-width-pixels (image-dired-display-window)) |
| 1786 | image-dired-display-window-width-correction)) |
| 1787 | |
| 1788 | (defun image-dired-display-window-height () |
| 1789 | "Return height, in pixels, of image-dired's image display window." |
| 1790 | (- (image-dired-window-height-pixels (image-dired-display-window)) |
| 1791 | image-dired-display-window-height-correction)) |
| 1792 | |
| 1793 | (defun image-dired-display-image (file &optional original-size) |
| 1794 | "Display image FILE in image buffer. |
| 1795 | Use this when you want to display the image, semi sized, in a new |
| 1796 | window. The image is sized to fit the display window (using a |
| 1797 | temporary file, don't worry). Because of this, it will not be as |
| 1798 | quick as opening it directly, but on most modern systems it |
| 1799 | should feel snappy enough. |
| 1800 | |
| 1801 | If optional argument ORIGINAL-SIZE is non-nil, display image in its |
| 1802 | original size." |
| 1803 | (let ((new-file (expand-file-name image-dired-temp-image-file)) |
| 1804 | width height command ret |
| 1805 | (image-type 'jpeg)) |
| 1806 | (setq file (expand-file-name file)) |
| 1807 | (if (not original-size) |
| 1808 | (progn |
| 1809 | (setq width (image-dired-display-window-width)) |
| 1810 | (setq height (image-dired-display-window-height)) |
| 1811 | (setq command |
| 1812 | (format-spec |
| 1813 | image-dired-cmd-create-temp-image-options |
| 1814 | (list |
| 1815 | (cons ?p image-dired-cmd-create-temp-image-program) |
| 1816 | (cons ?w width) |
| 1817 | (cons ?h height) |
| 1818 | (cons ?f file) |
| 1819 | (cons ?t new-file)))) |
| 1820 | (setq ret (call-process shell-file-name nil nil nil |
| 1821 | shell-command-switch command)) |
| 1822 | (if (not (= 0 ret)) |
| 1823 | (error "Could not resize image"))) |
| 1824 | (setq image-type (image-type-from-file-name file)) |
| 1825 | (copy-file file new-file t)) |
| 1826 | (with-current-buffer (image-dired-create-display-image-buffer) |
| 1827 | (let ((inhibit-read-only t)) |
| 1828 | (erase-buffer) |
| 1829 | (clear-image-cache) |
| 1830 | (image-dired-insert-image image-dired-temp-image-file image-type 0 0) |
| 1831 | (goto-char (point-min)) |
| 1832 | (image-dired-update-property 'original-file-name file))))) |
| 1833 | |
| 1834 | (defun image-dired-display-thumbnail-original-image (&optional arg) |
| 1835 | "Display current thumbnail's original image in display buffer. |
| 1836 | See documentation for `image-dired-display-image' for more information. |
| 1837 | With prefix argument ARG, display image in its original size." |
| 1838 | (interactive "P") |
| 1839 | (let ((file (image-dired-original-file-name))) |
| 1840 | (if (not (string-equal major-mode "image-dired-thumbnail-mode")) |
| 1841 | (message "Not in image-dired-thumbnail-mode") |
| 1842 | (if (not (image-dired-image-at-point-p)) |
| 1843 | (message "No thumbnail at point") |
| 1844 | (if (not file) |
| 1845 | (message "No original file name found") |
| 1846 | (image-dired-create-display-image-buffer) |
| 1847 | (display-buffer image-dired-display-image-buffer) |
| 1848 | (image-dired-display-image file arg)))))) |
| 1849 | |
| 1850 | |
| 1851 | ;;;###autoload |
| 1852 | (defun image-dired-dired-display-image (&optional arg) |
| 1853 | "Display current image file. |
| 1854 | See documentation for `image-dired-display-image' for more information. |
| 1855 | With prefix argument ARG, display image in its original size." |
| 1856 | (interactive "P") |
| 1857 | (image-dired-create-display-image-buffer) |
| 1858 | (display-buffer image-dired-display-image-buffer) |
| 1859 | (image-dired-display-image (dired-get-filename) arg)) |
| 1860 | |
| 1861 | (defun image-dired-image-at-point-p () |
| 1862 | "Return true if there is an image-dired thumbnail at point." |
| 1863 | (get-text-property (point) 'image-dired-thumbnail)) |
| 1864 | |
| 1865 | (defun image-dired-rotate-thumbnail (degrees) |
| 1866 | "Rotate thumbnail DEGREES degrees." |
| 1867 | (if (not (image-dired-image-at-point-p)) |
| 1868 | (message "No thumbnail at point") |
| 1869 | (let ((file (image-dired-thumb-name (image-dired-original-file-name))) |
| 1870 | command) |
| 1871 | (setq command (format-spec |
| 1872 | image-dired-cmd-rotate-thumbnail-options |
| 1873 | (list |
| 1874 | (cons ?p image-dired-cmd-rotate-thumbnail-program) |
| 1875 | (cons ?d degrees) |
| 1876 | (cons ?t (expand-file-name file))))) |
| 1877 | (call-process shell-file-name nil nil nil shell-command-switch command) |
| 1878 | ;; Clear the cache to refresh image. I wish I could just refresh |
| 1879 | ;; the current file but I do not know how to do that. Yet... |
| 1880 | (clear-image-cache)))) |
| 1881 | |
| 1882 | (defun image-dired-rotate-thumbnail-left () |
| 1883 | "Rotate thumbnail left (counter clockwise) 90 degrees. |
| 1884 | The result of the rotation is displayed in the image display area |
| 1885 | and a confirmation is needed before the original image files is |
| 1886 | overwritten. This confirmation can be turned off using |
| 1887 | `image-dired-rotate-original-ask-before-overwrite'." |
| 1888 | (interactive) |
| 1889 | (image-dired-rotate-thumbnail "270")) |
| 1890 | |
| 1891 | (defun image-dired-rotate-thumbnail-right () |
| 1892 | "Rotate thumbnail counter right (clockwise) 90 degrees. |
| 1893 | The result of the rotation is displayed in the image display area |
| 1894 | and a confirmation is needed before the original image files is |
| 1895 | overwritten. This confirmation can be turned off using |
| 1896 | `image-dired-rotate-original-ask-before-overwrite'." |
| 1897 | (interactive) |
| 1898 | (image-dired-rotate-thumbnail "90")) |
| 1899 | |
| 1900 | (defun image-dired-refresh-thumb () |
| 1901 | "Force creation of new image for current thumbnail." |
| 1902 | (interactive) |
| 1903 | (let ((file (image-dired-original-file-name))) |
| 1904 | (clear-image-cache) |
| 1905 | (image-dired-create-thumb file (image-dired-thumb-name file)))) |
| 1906 | |
| 1907 | (defun image-dired-rotate-original (degrees) |
| 1908 | "Rotate original image DEGREES degrees." |
| 1909 | (if (not (image-dired-image-at-point-p)) |
| 1910 | (message "No image at point") |
| 1911 | (let ((file (image-dired-original-file-name)) |
| 1912 | command) |
| 1913 | (if (not (string-match "\.[jJ][pP[eE]?[gG]$" file)) |
| 1914 | (error "Only JPEG images can be rotated!")) |
| 1915 | (setq command (format-spec |
| 1916 | image-dired-cmd-rotate-original-options |
| 1917 | (list |
| 1918 | (cons ?p image-dired-cmd-rotate-original-program) |
| 1919 | (cons ?d degrees) |
| 1920 | (cons ?o (expand-file-name file)) |
| 1921 | (cons ?t image-dired-temp-rotate-image-file)))) |
| 1922 | (if (not (= 0 (call-process shell-file-name nil nil nil |
| 1923 | shell-command-switch command))) |
| 1924 | (error "Could not rotate image") |
| 1925 | (image-dired-display-image image-dired-temp-rotate-image-file) |
| 1926 | (if (or (and image-dired-rotate-original-ask-before-overwrite |
| 1927 | (y-or-n-p |
| 1928 | "Rotate to temp file OK. Overwrite original image? ")) |
| 1929 | (not image-dired-rotate-original-ask-before-overwrite)) |
| 1930 | (progn |
| 1931 | (copy-file image-dired-temp-rotate-image-file file t) |
| 1932 | (image-dired-refresh-thumb)) |
| 1933 | (image-dired-display-image file)))))) |
| 1934 | |
| 1935 | (defun image-dired-rotate-original-left () |
| 1936 | "Rotate original image left (counter clockwise) 90 degrees." |
| 1937 | (interactive) |
| 1938 | (image-dired-rotate-original "270")) |
| 1939 | |
| 1940 | (defun image-dired-rotate-original-right () |
| 1941 | "Rotate original image right (clockwise) 90 degrees." |
| 1942 | (interactive) |
| 1943 | (image-dired-rotate-original "90")) |
| 1944 | |
| 1945 | (defun image-dired-get-exif-file-name (file) |
| 1946 | "Use the image's EXIF information to return a unique file name. |
| 1947 | The file name should be unique as long as you do not take more than |
| 1948 | one picture per second. The original file name is suffixed at the end |
| 1949 | for traceability. The format of the returned file name is |
| 1950 | YYYY_MM_DD_HH_MM_DD_ORIG_FILE_NAME.jpg. Used from |
| 1951 | `image-dired-copy-with-exif-file-name'." |
| 1952 | (let (data no-exif-data-found) |
| 1953 | (if (not (string-match "\.[Jj][Pp][Ee]?[Gg]$" (expand-file-name file))) |
| 1954 | (progn |
| 1955 | (setq no-exif-data-found t) |
| 1956 | (setq data |
| 1957 | (format-time-string |
| 1958 | "%Y:%m:%d %H:%M:%S" |
| 1959 | (nth 5 (file-attributes (expand-file-name file)))))) |
| 1960 | (setq data (image-dired-get-exif-data (expand-file-name file) |
| 1961 | "DateTimeOriginal"))) |
| 1962 | (while (string-match "[ :]" data) |
| 1963 | (setq data (replace-match "_" nil nil data))) |
| 1964 | (format "%s%s%s" data |
| 1965 | (if no-exif-data-found |
| 1966 | "_noexif_" |
| 1967 | "_") |
| 1968 | (file-name-nondirectory file)))) |
| 1969 | |
| 1970 | (defun image-dired-thumbnail-set-image-description () |
| 1971 | "Set the ImageDescription EXIF tag for the original image. |
| 1972 | If the image already has a value for this tag, it is used as the |
| 1973 | default value at the prompt." |
| 1974 | (interactive) |
| 1975 | (if (not (image-dired-image-at-point-p)) |
| 1976 | (message "No thumbnail at point") |
| 1977 | (let* ((file (image-dired-original-file-name)) |
| 1978 | (old-value (image-dired-get-exif-data file "ImageDescription"))) |
| 1979 | (if (eq 0 |
| 1980 | (image-dired-set-exif-data file "ImageDescription" |
| 1981 | (read-string "Value of ImageDescription: " |
| 1982 | old-value))) |
| 1983 | (message "Successfully wrote ImageDescription tag.") |
| 1984 | (error "Could not write ImageDescription tag"))))) |
| 1985 | |
| 1986 | (defun image-dired-set-exif-data (file tag-name tag-value) |
| 1987 | "In FILE, set EXIF tag TAG-NAME to value TAG-VALUE." |
| 1988 | (let (command) |
| 1989 | (setq command (format-spec |
| 1990 | image-dired-cmd-write-exif-data-options |
| 1991 | (list |
| 1992 | (cons ?p image-dired-cmd-write-exif-data-program) |
| 1993 | (cons ?f (expand-file-name file)) |
| 1994 | (cons ?t tag-name) |
| 1995 | (cons ?v tag-value)))) |
| 1996 | (call-process shell-file-name nil nil nil shell-command-switch command))) |
| 1997 | |
| 1998 | (defun image-dired-get-exif-data (file tag-name) |
| 1999 | "From FILE, return EXIF tag TAG-NAME." |
| 2000 | (let ((buf (get-buffer-create "*image-dired-get-exif-data*")) |
| 2001 | command tag-value) |
| 2002 | (setq command (format-spec |
| 2003 | image-dired-cmd-read-exif-data-options |
| 2004 | (list |
| 2005 | (cons ?p image-dired-cmd-read-exif-data-program) |
| 2006 | (cons ?f file) |
| 2007 | (cons ?t tag-name)))) |
| 2008 | (with-current-buffer buf |
| 2009 | (delete-region (point-min) (point-max)) |
| 2010 | (if (not (eq (call-process shell-file-name nil t nil |
| 2011 | shell-command-switch command) 0)) |
| 2012 | (error "Could not get EXIF tag") |
| 2013 | (goto-char (point-min)) |
| 2014 | ;; Clean buffer from newlines and carriage returns before |
| 2015 | ;; getting final info |
| 2016 | (while (search-forward-regexp "[\n\r]" nil t) |
| 2017 | (replace-match "" nil t)) |
| 2018 | (setq tag-value (buffer-substring (point-min) (point-max))))) |
| 2019 | tag-value)) |
| 2020 | |
| 2021 | (defun image-dired-copy-with-exif-file-name () |
| 2022 | "Copy file with unique name to main image directory. |
| 2023 | Copy current or all marked files in dired to a new file in your |
| 2024 | main image directory, using a file name generated by |
| 2025 | `image-dired-get-exif-file-name'. A typical usage for this if when |
| 2026 | copying images from a digital camera into the image directory. |
| 2027 | |
| 2028 | Typically, you would open up the folder with the incoming |
| 2029 | digital images, mark the files to be copied, and execute this |
| 2030 | function. The result is a couple of new files in |
| 2031 | `image-dired-main-image-directory' called |
| 2032 | 2005_05_08_12_52_00_dscn0319.jpg, |
| 2033 | 2005_05_08_14_27_45_dscn0320.jpg etc." |
| 2034 | (interactive) |
| 2035 | (let (new-name |
| 2036 | (files (dired-get-marked-files))) |
| 2037 | (mapcar |
| 2038 | (lambda (curr-file) |
| 2039 | (setq new-name |
| 2040 | (format "%s/%s" |
| 2041 | (file-name-as-directory |
| 2042 | (expand-file-name image-dired-main-image-directory)) |
| 2043 | (image-dired-get-exif-file-name curr-file))) |
| 2044 | (message "Copying %s to %s" curr-file new-name) |
| 2045 | (copy-file curr-file new-name)) |
| 2046 | files))) |
| 2047 | |
| 2048 | (defun image-dired-display-next-thumbnail-original () |
| 2049 | "In thumbnail buffer, move to next thumbnail and display the image." |
| 2050 | (interactive) |
| 2051 | (image-dired-forward-image) |
| 2052 | (image-dired-display-thumbnail-original-image)) |
| 2053 | |
| 2054 | (defun image-dired-display-previous-thumbnail-original () |
| 2055 | "Move to previous thumbnail and display image." |
| 2056 | (interactive) |
| 2057 | (image-dired-backward-image) |
| 2058 | (image-dired-display-thumbnail-original-image)) |
| 2059 | |
| 2060 | (defun image-dired-write-comments (file-comments) |
| 2061 | "Write file comments to database. |
| 2062 | Write file comments to one or more files. |
| 2063 | FILE-COMMENTS is an alist on the following form: |
| 2064 | ((FILE . COMMENT) ... )" |
| 2065 | (image-dired-sane-db-file) |
| 2066 | (let (end comment-beg-pos comment-end-pos file comment) |
| 2067 | (image-dired--with-db-file |
| 2068 | (setq buffer-file-name image-dired-db-file) |
| 2069 | (dolist (elt file-comments) |
| 2070 | (setq file (car elt) |
| 2071 | comment (cdr elt)) |
| 2072 | (goto-char (point-min)) |
| 2073 | (if (search-forward-regexp (format "^%s.*$" file) nil t) |
| 2074 | (progn |
| 2075 | (setq end (point)) |
| 2076 | (beginning-of-line) |
| 2077 | ;; Delete old comment, if any |
| 2078 | (when (search-forward ";comment:" end t) |
| 2079 | (setq comment-beg-pos (match-beginning 0)) |
| 2080 | ;; Any tags after the comment? |
| 2081 | (if (search-forward ";" end t) |
| 2082 | (setq comment-end-pos (- (point) 1)) |
| 2083 | (setq comment-end-pos end)) |
| 2084 | ;; Delete comment tag and comment |
| 2085 | (delete-region comment-beg-pos comment-end-pos)) |
| 2086 | ;; Insert new comment |
| 2087 | (beginning-of-line) |
| 2088 | (unless (search-forward ";" end t) |
| 2089 | (end-of-line) |
| 2090 | (insert ";")) |
| 2091 | (insert (format "comment:%s;" comment))) |
| 2092 | ;; File does not exist in database - add it. |
| 2093 | (goto-char (point-max)) |
| 2094 | (insert (format "\n%s;comment:%s" file comment)))) |
| 2095 | (save-buffer)))) |
| 2096 | |
| 2097 | (defun image-dired-update-property (prop value) |
| 2098 | "Update text property PROP with value VALUE at point." |
| 2099 | (let ((inhibit-read-only t)) |
| 2100 | (put-text-property |
| 2101 | (point) (1+ (point)) |
| 2102 | prop |
| 2103 | value))) |
| 2104 | |
| 2105 | ;;;###autoload |
| 2106 | (defun image-dired-dired-comment-files () |
| 2107 | "Add comment to current or marked files in dired." |
| 2108 | (interactive) |
| 2109 | (let ((comment (image-dired-read-comment))) |
| 2110 | (image-dired-write-comments |
| 2111 | (mapcar |
| 2112 | (lambda (curr-file) |
| 2113 | (cons curr-file comment)) |
| 2114 | (dired-get-marked-files))))) |
| 2115 | |
| 2116 | (defun image-dired-comment-thumbnail () |
| 2117 | "Add comment to current thumbnail in thumbnail buffer." |
| 2118 | (interactive) |
| 2119 | (let* ((file (image-dired-original-file-name)) |
| 2120 | (comment (image-dired-read-comment file))) |
| 2121 | (image-dired-write-comments (list (cons file comment))) |
| 2122 | (image-dired-update-property 'comment comment)) |
| 2123 | (image-dired-display-thumb-properties)) |
| 2124 | |
| 2125 | (defun image-dired-read-comment (&optional file) |
| 2126 | "Read comment for an image. |
| 2127 | Optionally use old comment from FILE as initial value." |
| 2128 | (let ((comment |
| 2129 | (read-string |
| 2130 | "Comment: " |
| 2131 | (if file (image-dired-get-comment file))))) |
| 2132 | comment)) |
| 2133 | |
| 2134 | (defun image-dired-get-comment (file) |
| 2135 | "Get comment for file FILE." |
| 2136 | (image-dired-sane-db-file) |
| 2137 | (image-dired--with-db-file |
| 2138 | (let (end comment-beg-pos comment-end-pos comment) |
| 2139 | (when (search-forward-regexp (format "^%s" file) nil t) |
| 2140 | (end-of-line) |
| 2141 | (setq end (point)) |
| 2142 | (beginning-of-line) |
| 2143 | (when (search-forward ";comment:" end t) |
| 2144 | (setq comment-beg-pos (point)) |
| 2145 | (if (search-forward ";" end t) |
| 2146 | (setq comment-end-pos (- (point) 1)) |
| 2147 | (setq comment-end-pos end)) |
| 2148 | (setq comment (buffer-substring |
| 2149 | comment-beg-pos comment-end-pos)))) |
| 2150 | comment))) |
| 2151 | |
| 2152 | ;;;###autoload |
| 2153 | (defun image-dired-mark-tagged-files () |
| 2154 | "Use regexp to mark files with matching tag. |
| 2155 | A `tag' is a keyword, a piece of meta data, associated with an |
| 2156 | image file and stored in image-dired's database file. This command |
| 2157 | lets you input a regexp and this will be matched against all tags |
| 2158 | on all image files in the database file. The files that have a |
| 2159 | matching tag will be marked in the dired buffer." |
| 2160 | (interactive) |
| 2161 | (image-dired-sane-db-file) |
| 2162 | (let ((tag (read-string "Mark tagged files (regexp): ")) |
| 2163 | (hits 0) |
| 2164 | files) |
| 2165 | (image-dired--with-db-file |
| 2166 | ;; Collect matches |
| 2167 | (while (search-forward-regexp |
| 2168 | (concat "\\(^[^;\n]+\\);.*" tag ".*$") nil t) |
| 2169 | (push (match-string 1) files))) |
| 2170 | ;; Mark files |
| 2171 | (dolist (curr-file files) |
| 2172 | ;; I tried using `dired-mark-files-regexp' but it was waaaay to |
| 2173 | ;; slow. Don't bother about hits found in other directories |
| 2174 | ;; than the current one. |
| 2175 | (when (string= (file-name-as-directory |
| 2176 | (expand-file-name default-directory)) |
| 2177 | (file-name-as-directory |
| 2178 | (file-name-directory curr-file))) |
| 2179 | (setq curr-file (file-name-nondirectory curr-file)) |
| 2180 | (goto-char (point-min)) |
| 2181 | (when (search-forward-regexp (format "\\s %s$" curr-file) nil t) |
| 2182 | (setq hits (+ hits 1)) |
| 2183 | (dired-mark 1)))) |
| 2184 | (message "%d files with matching tag marked." hits))) |
| 2185 | |
| 2186 | (defun image-dired-mouse-display-image (event) |
| 2187 | "Use mouse EVENT, call `image-dired-display-image' to display image. |
| 2188 | Track this in associated dired buffer if `image-dired-track-movement' is |
| 2189 | non-nil." |
| 2190 | (interactive "e") |
| 2191 | (mouse-set-point event) |
| 2192 | (goto-char (posn-point (event-end event))) |
| 2193 | (let ((file (image-dired-original-file-name))) |
| 2194 | (when file |
| 2195 | (if image-dired-track-movement |
| 2196 | (image-dired-track-original-file)) |
| 2197 | (image-dired-create-display-image-buffer) |
| 2198 | (display-buffer image-dired-display-image-buffer) |
| 2199 | (image-dired-display-image file)))) |
| 2200 | |
| 2201 | (defun image-dired-mouse-select-thumbnail (event) |
| 2202 | "Use mouse EVENT to select thumbnail image. |
| 2203 | Track this in associated dired buffer if `image-dired-track-movement' is |
| 2204 | non-nil." |
| 2205 | (interactive "e") |
| 2206 | (mouse-set-point event) |
| 2207 | (goto-char (posn-point (event-end event))) |
| 2208 | (if image-dired-track-movement |
| 2209 | (image-dired-track-original-file)) |
| 2210 | (image-dired-display-thumb-properties)) |
| 2211 | |
| 2212 | (defun image-dired-mouse-toggle-mark (event) |
| 2213 | "Use mouse EVENT to toggle dired mark for thumbnail. |
| 2214 | Track this in associated dired buffer if `image-dired-track-movement' is |
| 2215 | non-nil." |
| 2216 | (interactive "e") |
| 2217 | (mouse-set-point event) |
| 2218 | (goto-char (posn-point (event-end event))) |
| 2219 | (if image-dired-track-movement |
| 2220 | (image-dired-track-original-file)) |
| 2221 | (image-dired-toggle-mark-thumb-original-file)) |
| 2222 | |
| 2223 | (defun image-dired-dired-display-properties () |
| 2224 | "Display properties for dired file in the echo area." |
| 2225 | (interactive) |
| 2226 | (let* ((file (dired-get-filename)) |
| 2227 | (file-name (file-name-nondirectory file)) |
| 2228 | (dired-buf (buffer-name (current-buffer))) |
| 2229 | (props (mapconcat |
| 2230 | 'princ |
| 2231 | (image-dired-list-tags file) |
| 2232 | ", ")) |
| 2233 | (comment (image-dired-get-comment file))) |
| 2234 | (if file-name |
| 2235 | (message "%s" |
| 2236 | (image-dired-format-properties-string |
| 2237 | dired-buf |
| 2238 | file-name |
| 2239 | props |
| 2240 | comment))))) |
| 2241 | |
| 2242 | (defvar image-dired-tag-file-list nil |
| 2243 | "List to store tag-file structure.") |
| 2244 | |
| 2245 | (defvar image-dired-file-tag-list nil |
| 2246 | "List to store file-tag structure.") |
| 2247 | |
| 2248 | (defvar image-dired-file-comment-list nil |
| 2249 | "List to store file comments.") |
| 2250 | |
| 2251 | (defun image-dired-add-to-tag-file-list (tag file) |
| 2252 | "Add relation between TAG and FILE." |
| 2253 | (let (curr) |
| 2254 | (if image-dired-tag-file-list |
| 2255 | (if (setq curr (assoc tag image-dired-tag-file-list)) |
| 2256 | (if (not (member file curr)) |
| 2257 | (setcdr curr (cons file (cdr curr)))) |
| 2258 | (setcdr image-dired-tag-file-list |
| 2259 | (cons (list tag file) (cdr image-dired-tag-file-list)))) |
| 2260 | (setq image-dired-tag-file-list (list (list tag file)))))) |
| 2261 | |
| 2262 | (defun image-dired-add-to-tag-file-lists (tag file) |
| 2263 | "Helper function used from `image-dired-create-gallery-lists'. |
| 2264 | |
| 2265 | Add TAG to FILE in one list and FILE to TAG in the other. |
| 2266 | |
| 2267 | Lisp structures look like the following: |
| 2268 | |
| 2269 | image-dired-file-tag-list: |
| 2270 | |
| 2271 | ((\"filename1\" \"tag1\" \"tag2\" \"tag3\" ...) |
| 2272 | (\"filename2\" \"tag1\" \"tag2\" \"tag3\" ...) |
| 2273 | ...) |
| 2274 | |
| 2275 | image-dired-tag-file-list: |
| 2276 | |
| 2277 | ((\"tag1\" \"filename1\" \"filename2\" \"filename3\" ...) |
| 2278 | (\"tag2\" \"filename1\" \"filename2\" \"filename3\" ...) |
| 2279 | ...)" |
| 2280 | ;; Add tag to file list |
| 2281 | (let (curr) |
| 2282 | (if image-dired-file-tag-list |
| 2283 | (if (setq curr (assoc file image-dired-file-tag-list)) |
| 2284 | (setcdr curr (cons tag (cdr curr))) |
| 2285 | (setcdr image-dired-file-tag-list |
| 2286 | (cons (list file tag) (cdr image-dired-file-tag-list)))) |
| 2287 | (setq image-dired-file-tag-list (list (list file tag)))) |
| 2288 | ;; Add file to tag list |
| 2289 | (if image-dired-tag-file-list |
| 2290 | (if (setq curr (assoc tag image-dired-tag-file-list)) |
| 2291 | (if (not (member file curr)) |
| 2292 | (setcdr curr (cons file (cdr curr)))) |
| 2293 | (setcdr image-dired-tag-file-list |
| 2294 | (cons (list tag file) (cdr image-dired-tag-file-list)))) |
| 2295 | (setq image-dired-tag-file-list (list (list tag file)))))) |
| 2296 | |
| 2297 | (defun image-dired-add-to-file-comment-list (file comment) |
| 2298 | "Helper function used from `image-dired-create-gallery-lists'. |
| 2299 | |
| 2300 | For FILE, add COMMENT to list. |
| 2301 | |
| 2302 | Lisp structure looks like the following: |
| 2303 | |
| 2304 | image-dired-file-comment-list: |
| 2305 | |
| 2306 | ((\"filename1\" . \"comment1\") |
| 2307 | (\"filename2\" . \"comment2\") |
| 2308 | ...)" |
| 2309 | (if image-dired-file-comment-list |
| 2310 | (if (not (assoc file image-dired-file-comment-list)) |
| 2311 | (setcdr image-dired-file-comment-list |
| 2312 | (cons (cons file comment) |
| 2313 | (cdr image-dired-file-comment-list)))) |
| 2314 | (setq image-dired-file-comment-list (list (cons file comment))))) |
| 2315 | |
| 2316 | (defun image-dired-create-gallery-lists () |
| 2317 | "Create temporary lists used by `image-dired-gallery-generate'." |
| 2318 | (image-dired-sane-db-file) |
| 2319 | (image-dired--with-db-file |
| 2320 | (let (end beg file row-tags) |
| 2321 | (setq image-dired-tag-file-list nil) |
| 2322 | (setq image-dired-file-tag-list nil) |
| 2323 | (setq image-dired-file-comment-list nil) |
| 2324 | (goto-char (point-min)) |
| 2325 | (while (search-forward-regexp "^." nil t) |
| 2326 | (end-of-line) |
| 2327 | (setq end (point)) |
| 2328 | (beginning-of-line) |
| 2329 | (setq beg (point)) |
| 2330 | (unless (search-forward ";" end nil) |
| 2331 | (error "Something is really wrong, check format of database")) |
| 2332 | (setq row-tags (split-string |
| 2333 | (buffer-substring beg end) ";")) |
| 2334 | (setq file (car row-tags)) |
| 2335 | (dolist (x (cdr row-tags)) |
| 2336 | (if (not (string-match "^comment:\\(.*\\)" x)) |
| 2337 | (image-dired-add-to-tag-file-lists x file) |
| 2338 | (image-dired-add-to-file-comment-list file (match-string 1 x))))))) |
| 2339 | ;; Sort tag-file list |
| 2340 | (setq image-dired-tag-file-list |
| 2341 | (sort image-dired-tag-file-list |
| 2342 | (lambda (x y) |
| 2343 | (string< (car x) (car y)))))) |
| 2344 | |
| 2345 | (defun image-dired-hidden-p (file) |
| 2346 | "Return t if image FILE has a \"hidden\" tag." |
| 2347 | (let (hidden) |
| 2348 | (mapc |
| 2349 | (lambda (tag) |
| 2350 | (if (member tag image-dired-gallery-hidden-tags) |
| 2351 | (setq hidden t))) |
| 2352 | (cdr (assoc file image-dired-file-tag-list))) |
| 2353 | hidden)) |
| 2354 | |
| 2355 | (defun image-dired-gallery-generate () |
| 2356 | "Generate gallery pages. |
| 2357 | First we create a couple of Lisp structures from the database to make |
| 2358 | it easier to generate, then HTML-files are created in |
| 2359 | `image-dired-gallery-dir'." |
| 2360 | (interactive) |
| 2361 | (if (eq 'per-directory image-dired-thumbnail-storage) |
| 2362 | (error "Currently, gallery generation is not supported \ |
| 2363 | when using per-directory thumbnail file storage")) |
| 2364 | (image-dired-create-gallery-lists) |
| 2365 | (let ((tags image-dired-tag-file-list) |
| 2366 | (index-file (format "%s/index.html" image-dired-gallery-dir)) |
| 2367 | count tag tag-file |
| 2368 | comment file-tags tag-link tag-link-list) |
| 2369 | ;; Make sure gallery root exist |
| 2370 | (if (file-exists-p image-dired-gallery-dir) |
| 2371 | (if (not (file-directory-p image-dired-gallery-dir)) |
| 2372 | (error "Variable image-dired-gallery-dir is not a directory")) |
| 2373 | (make-directory image-dired-gallery-dir)) |
| 2374 | ;; Open index file |
| 2375 | (with-temp-file index-file |
| 2376 | (if (file-exists-p index-file) |
| 2377 | (insert-file-contents index-file)) |
| 2378 | (insert "<html>\n") |
| 2379 | (insert " <body>\n") |
| 2380 | (insert " <h2>Image-Dired Gallery</h2>\n") |
| 2381 | (insert (format "<p>\n Gallery generated %s\n <p>\n" |
| 2382 | (current-time-string))) |
| 2383 | (insert " <h3>Tag index</h3>\n") |
| 2384 | (setq count 1) |
| 2385 | ;; Pre-generate list of all tag links |
| 2386 | (dolist (curr tags) |
| 2387 | (setq tag (car curr)) |
| 2388 | (when (not (member tag image-dired-gallery-hidden-tags)) |
| 2389 | (setq tag-link (format "<a href=\"%d.html\">%s</a>" count tag)) |
| 2390 | (if tag-link-list |
| 2391 | (setq tag-link-list |
| 2392 | (append tag-link-list (list (cons tag tag-link)))) |
| 2393 | (setq tag-link-list (list (cons tag tag-link)))) |
| 2394 | (setq count (1+ count)))) |
| 2395 | (setq count 1) |
| 2396 | ;; Main loop where we generated thumbnail pages per tag |
| 2397 | (dolist (curr tags) |
| 2398 | (setq tag (car curr)) |
| 2399 | ;; Don't display hidden tags |
| 2400 | (when (not (member tag image-dired-gallery-hidden-tags)) |
| 2401 | ;; Insert link to tag page in index |
| 2402 | (insert (format " %s<br>\n" (cdr (assoc tag tag-link-list)))) |
| 2403 | ;; Open per-tag file |
| 2404 | (setq tag-file (format "%s/%s.html" image-dired-gallery-dir count)) |
| 2405 | (with-temp-file tag-file |
| 2406 | (if (file-exists-p tag-file) |
| 2407 | (insert-file-contents tag-file)) |
| 2408 | (erase-buffer) |
| 2409 | (insert "<html>\n") |
| 2410 | (insert " <body>\n") |
| 2411 | (insert " <p><a href=\"index.html\">Index</a></p>\n") |
| 2412 | (insert (format " <h2>Images with tag "%s"</h2>" tag)) |
| 2413 | ;; Main loop for files per tag page |
| 2414 | (dolist (file (cdr curr)) |
| 2415 | (unless (image-dired-hidden-p file) |
| 2416 | ;; Insert thumbnail with link to full image |
| 2417 | (insert |
| 2418 | (format "<a href=\"%s/%s\"><img src=\"%s/%s\"%s></a>\n" |
| 2419 | image-dired-gallery-image-root-url |
| 2420 | (file-name-nondirectory file) |
| 2421 | image-dired-gallery-thumb-image-root-url |
| 2422 | (file-name-nondirectory (image-dired-thumb-name file)) file)) |
| 2423 | ;; Insert comment, if any |
| 2424 | (if (setq comment (cdr (assoc file image-dired-file-comment-list))) |
| 2425 | (insert (format "<br>\n%s<br>\n" comment)) |
| 2426 | (insert "<br>\n")) |
| 2427 | ;; Insert links to other tags, if any |
| 2428 | (when (> (length |
| 2429 | (setq file-tags (assoc file image-dired-file-tag-list))) 2) |
| 2430 | (insert "[ ") |
| 2431 | (dolist (extra-tag file-tags) |
| 2432 | ;; Only insert if not file name or the main tag |
| 2433 | (if (and (not (equal extra-tag tag)) |
| 2434 | (not (equal extra-tag file))) |
| 2435 | (insert |
| 2436 | (format "%s " (cdr (assoc extra-tag tag-link-list)))))) |
| 2437 | (insert "]<br>\n")))) |
| 2438 | (insert " <p><a href=\"index.html\">Index</a></p>\n") |
| 2439 | (insert " </body>\n") |
| 2440 | (insert "</html>\n")) |
| 2441 | (setq count (1+ count)))) |
| 2442 | (insert " </body>\n") |
| 2443 | (insert "</html>")))) |
| 2444 | |
| 2445 | (defun image-dired-kill-buffer-and-window () |
| 2446 | "Kill the current buffer and, if possible, also the window." |
| 2447 | (interactive) |
| 2448 | (let ((buffer (current-buffer))) |
| 2449 | (condition-case nil |
| 2450 | (delete-window (selected-window)) |
| 2451 | (error nil)) |
| 2452 | (kill-buffer buffer))) |
| 2453 | |
| 2454 | (defvar image-dired-widget-list nil |
| 2455 | "List to keep track of meta data in edit buffer.") |
| 2456 | |
| 2457 | (declare-function widget-forward "wid-edit" (arg)) |
| 2458 | |
| 2459 | ;;;###autoload |
| 2460 | (defun image-dired-dired-edit-comment-and-tags () |
| 2461 | "Edit comment and tags of current or marked image files. |
| 2462 | Edit comment and tags for all marked image files in an |
| 2463 | easy-to-use form." |
| 2464 | (interactive) |
| 2465 | (setq image-dired-widget-list nil) |
| 2466 | ;; Setup buffer. |
| 2467 | (let ((files (dired-get-marked-files))) |
| 2468 | (switch-to-buffer "*Image-Dired Edit Meta Data*") |
| 2469 | (kill-all-local-variables) |
| 2470 | (make-local-variable 'widget-example-repeat) |
| 2471 | (let ((inhibit-read-only t)) |
| 2472 | (erase-buffer)) |
| 2473 | (remove-overlays) |
| 2474 | ;; Some help for the user. |
| 2475 | (widget-insert |
| 2476 | "\nEdit comments and tags for each image. Separate multiple tags |
| 2477 | with a comma. Move forward between fields using TAB or RET. |
| 2478 | Move to the previous field using backtab (S-TAB). Save by |
| 2479 | activating the Save button at the bottom of the form or cancel |
| 2480 | the operation by activating the Cancel button.\n\n") |
| 2481 | ;; Here comes all images and a comment and tag field for each |
| 2482 | ;; image. |
| 2483 | (let (thumb-file img comment-widget tag-widget) |
| 2484 | |
| 2485 | (dolist (file files) |
| 2486 | |
| 2487 | (setq thumb-file (image-dired-thumb-name file) |
| 2488 | img (create-image thumb-file)) |
| 2489 | |
| 2490 | (insert-image img) |
| 2491 | (widget-insert "\n\nComment: ") |
| 2492 | (setq comment-widget |
| 2493 | (widget-create 'editable-field |
| 2494 | :size 60 |
| 2495 | :format "%v " |
| 2496 | :value (or (image-dired-get-comment file) ""))) |
| 2497 | (widget-insert "\nTags: ") |
| 2498 | (setq tag-widget |
| 2499 | (widget-create 'editable-field |
| 2500 | :size 60 |
| 2501 | :format "%v " |
| 2502 | :value (or (mapconcat |
| 2503 | (lambda (tag) |
| 2504 | tag) |
| 2505 | (image-dired-list-tags file) |
| 2506 | ",") ""))) |
| 2507 | ;; Save information in all widgets so that we can use it when |
| 2508 | ;; the user saves the form. |
| 2509 | (setq image-dired-widget-list |
| 2510 | (append image-dired-widget-list |
| 2511 | (list (list file comment-widget tag-widget)))) |
| 2512 | (widget-insert "\n\n"))) |
| 2513 | |
| 2514 | ;; Footer with Save and Cancel button. |
| 2515 | (widget-insert "\n") |
| 2516 | (widget-create 'push-button |
| 2517 | :notify |
| 2518 | (lambda (&rest _ignore) |
| 2519 | (image-dired-save-information-from-widgets) |
| 2520 | (bury-buffer) |
| 2521 | (message "Done.")) |
| 2522 | "Save") |
| 2523 | (widget-insert " ") |
| 2524 | (widget-create 'push-button |
| 2525 | :notify |
| 2526 | (lambda (&rest _ignore) |
| 2527 | (bury-buffer) |
| 2528 | (message "Operation canceled.")) |
| 2529 | "Cancel") |
| 2530 | (widget-insert "\n") |
| 2531 | (use-local-map widget-keymap) |
| 2532 | (widget-setup) |
| 2533 | ;; Jump to the first widget. |
| 2534 | (widget-forward 1))) |
| 2535 | |
| 2536 | (defun image-dired-save-information-from-widgets () |
| 2537 | "Save information found in `image-dired-widget-list'. |
| 2538 | Use the information in `image-dired-widget-list' to save comments and |
| 2539 | tags to their respective image file. Internal function used by |
| 2540 | `image-dired-dired-edit-comment-and-tags'." |
| 2541 | (let (file comment tag-string tag-list lst) |
| 2542 | (image-dired-write-comments |
| 2543 | (mapcar |
| 2544 | (lambda (widget) |
| 2545 | (setq file (car widget) |
| 2546 | comment (widget-value (cadr widget))) |
| 2547 | (cons file comment)) |
| 2548 | image-dired-widget-list)) |
| 2549 | (image-dired-write-tags |
| 2550 | (dolist (widget image-dired-widget-list lst) |
| 2551 | (setq file (car widget) |
| 2552 | tag-string (widget-value (car (cddr widget))) |
| 2553 | tag-list (split-string tag-string ",")) |
| 2554 | (dolist (tag tag-list) |
| 2555 | (push (cons file tag) lst)))))) |
| 2556 | |
| 2557 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; |
| 2558 | ;;;;;;;;; TEST-SECTION ;;;;;;;;;;; |
| 2559 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; |
| 2560 | |
| 2561 | ;; (defvar image-dired-dir-max-size 12300000) |
| 2562 | |
| 2563 | ;; (defun image-dired-test-clean-old-files () |
| 2564 | ;; "Clean `image-dired-dir' from old thumbnail files. |
| 2565 | ;; \"Oldness\" measured using last access time. If the total size of all |
| 2566 | ;; thumbnail files in `image-dired-dir' is larger than 'image-dired-dir-max-size', |
| 2567 | ;; old files are deleted until the max size is reached." |
| 2568 | ;; (let* ((files |
| 2569 | ;; (sort |
| 2570 | ;; (mapcar |
| 2571 | ;; (lambda (f) |
| 2572 | ;; (let ((fattribs (file-attributes f))) |
| 2573 | ;; ;; Get last access time and file size |
| 2574 | ;; `(,(nth 4 fattribs) ,(nth 7 fattribs) ,f))) |
| 2575 | ;; (directory-files (image-dired-dir) t ".+\.thumb\..+$")) |
| 2576 | ;; ;; Sort function. Compare time between two files. |
| 2577 | ;; (lambda (l1 l2) |
| 2578 | ;; (time-less-p (car l1) (car l2))))) |
| 2579 | ;; (dirsize (apply '+ (mapcar (lambda (x) (cadr x)) files)))) |
| 2580 | ;; (while (> dirsize image-dired-dir-max-size) |
| 2581 | ;; (y-or-n-p |
| 2582 | ;; (format "Size of thumbnail directory: %d, delete old file %s? " |
| 2583 | ;; dirsize (cadr (cdar files)))) |
| 2584 | ;; (delete-file (cadr (cdar files))) |
| 2585 | ;; (setq dirsize (- dirsize (car (cdar files)))) |
| 2586 | ;; (setq files (cdr files))))) |
| 2587 | |
| 2588 | ;;;;;;;;;;;;;;;;;;;;;;, |
| 2589 | |
| 2590 | ;; (defun dired-speedbar-buttons (dired-buffer) |
| 2591 | ;; (when (and (boundp 'image-dired-use-speedbar) |
| 2592 | ;; image-dired-use-speedbar) |
| 2593 | ;; (let ((filename (with-current-buffer dired-buffer |
| 2594 | ;; (dired-get-filename)))) |
| 2595 | ;; (when (and (not (string-equal filename (buffer-string))) |
| 2596 | ;; (string-match (image-file-name-regexp) filename)) |
| 2597 | ;; (erase-buffer) |
| 2598 | ;; (insert (propertize |
| 2599 | ;; filename |
| 2600 | ;; 'display |
| 2601 | ;; (image-dired-get-thumbnail-image filename))))))) |
| 2602 | |
| 2603 | ;; (setq image-dired-use-speedbar t) |
| 2604 | |
| 2605 | (provide 'image-dired) |
| 2606 | |
| 2607 | ;;; image-dired.el ends here |