├── Readme.org ├── doc-present.el ├── docpresent-main.png └── docpresent-overview.png /Readme.org: -------------------------------------------------------------------------------- 1 | * doc-present: Showing slides with Emacs 2 | 3 | This package is meant to display presentation slides with a two 4 | monitor setup. On one screen it shows the actual slides, and on the 5 | other a so called "presenter screen", showing not only the current 6 | slide but also the next one, a timer and also optional notes which can 7 | be drawn from an Org file. It also features an overview mode to 8 | quickly select certain slides. 9 | 10 | ** Requirements 11 | 12 | - Emacs 24.x (I test only with x=3; older version may or may not work). 13 | 14 | - Emacs must be built with Imagemagick support (so no Win32/OSX at 15 | the moment) 16 | 17 | ** Quick Tour 18 | 19 | - First, you should increase the variable =doc-view-resolution= for 20 | better rendering quality (300 should be enough). 21 | 22 | - Load the file /doc-present.el/ and =eval-buffer= it. 23 | 24 | - Load the PDF file containing your slides; doc-view mode should kick 25 | in and display the first slide, converting the rest in the 26 | background. Wait until all slides are converted (progress is shown 27 | in the modeline). 28 | 29 | - Connect your laptop to the presentation monitor/projector and do 30 | whatever you have to do to set it up so that you have a usual 31 | two-monitor setup, i.e., one screen is (virtually) next to the other 32 | and you can drag windows to and fro. 33 | 34 | - =M-x doc-present= 35 | 36 | - A new frame should pop up, showing the first slide and a little text 37 | saying that you should drag this frame to the presentation 38 | screen. Do exactly that and then press =f= to toggle fullscreen. 39 | 40 | - In the other frame, on your laptop screen, you should see the 41 | presenter window; it shows the current slide, next slide, timer, 42 | etc. 43 | 44 | - Use =s= to start the stop-watch timer at the beginning of your talk. 45 | Use cursor keys to navigate between slides, the =.= key to blank out 46 | the presentation screen, and =o= to enter overview mode. Press =h= 47 | for a quick summary of available keys. 48 | 49 | - When finished, press =q=. 50 | 51 | ** The long story 52 | 53 | Being fed up with the existing PDF viewers, especially their rendering 54 | quality and lacking capabilities for doing presentations, I decided 55 | that Emacs must save the day yet once again. 56 | 57 | *** doc-view 58 | 59 | Emacs ships with doc-view, which can render PDFs by using ghostscript 60 | to convert the pages to png files. By increasing the variable 61 | =doc-view-resolution=, you should get excellent quality, and from my 62 | experience ghostscript's PDF support is better than libpoppler's. 63 | 64 | The first thing you need to do though is to increase the value for 65 | =doc-view-resolution=. The needed value depends on the resolution of 66 | your presentation monitor, of course, but don't be fooled by your 67 | 24"-monitor 1920x1200 pixel IPS-panel; from my experience, most 68 | presentation projectors still have 1024x768, which means that a value 69 | of 300 should be more than enough. You can choose higher values just 70 | to be on the safe side, but converting and scaling those images to the 71 | correct size will be noticeably slower, so I'd rather not do that. 72 | 73 | *** The main slide frame 74 | 75 | When you start doc-present, a new frame will be generated which is the 76 | main slide frame. It is a special frame with no minibuffer, mode-line, 77 | fringes, etc., so that almost all space can be used for displaying 78 | your slides. I'm saying 'almost' because there are two caveats: 79 | 80 | - The size of an Emacs frame can only be multiples of the frame's 81 | default font size. That means, if you have a 8x16 font, it's width 82 | and height will be automatically reduced to be divisible by 8 and 16, 83 | resp. 84 | 85 | - Since the frame has no fringes, its rightmost column is reserved for 86 | displaying the truncation/continuation characters for overlong 87 | lines. 88 | 89 | There's a dirty trick though: We simply choose a /very/ tiny default 90 | font for the frame. Since Emacs can nowadays use freely scalable XFT 91 | fonts, we can choose such a font for the main slide frame and scale it 92 | down so that it is just 1 pixel wide and 2 pixels high. That means, 93 | the rightmost column will just be one pixel, and it will always 94 | exactly fit in the screen's resolution. 95 | 96 | All this happens automatically, but you have to make sure that the 97 | used font family does actually exist on your system. Simply do 98 | 99 | =M-x customize-face RET doc-present-tiny-xft-font RET= 100 | 101 | The default is /Bitstream Charter/, which I guess should exist on most 102 | modern GNU/Linux systems. 103 | 104 | You should set =doc-present-slide-frame-background-color= to the 105 | background color of your slides (default: white); this is important if 106 | your slides don't exactly fit the presentation screen (because of 107 | different aspect ratios). Also, the rightmost 1-pixel column will have 108 | this color. 109 | 110 | *** The presenter frame 111 | 112 | By default, the presenter frame shows a stop-watch timer, the current 113 | and maximum slide number, the local time, the current and next slide 114 | and optionally notes for the current slide. 115 | 116 | This can be changed in practically every way; see the variable 117 | =doc-present-presenter-layout= to change what and where stuff is 118 | displayed. Also, =doc-present-current-slide-width= and 119 | =doc-present-next-slide-width= change the width of the current/next 120 | slide. 121 | 122 | *** Available keys 123 | 124 | - =Right, Down, PgDown, Space, Return=: Next Slide 125 | 126 | - =Left, Up, PgUp=: Previous Slide 127 | 128 | - =f=: Toggle fullscreen of main slide frame 129 | 130 | - =s=: Start/Stop the stop-watch timer 131 | 132 | - =.=: Black out the main slide frame 133 | 134 | - =o=: Start overview mode 135 | 136 | - =m=: Create a new main slide frame 137 | 138 | - =h=: Quick help 139 | 140 | - =q=: Quit 141 | 142 | *** Overview mode 143 | 144 | The overview mode shows all slides of your presentation in a miniature 145 | view; it can be triggered by pressing =o=. You can then move between 146 | the different slides with the cursor keys, and pressing =Return= will 147 | show a slide in the main slide frame while staying in overview mode 148 | in the presenter frame. Pressing the =Space= key will show the slide 149 | and switch to the presentation mode. 150 | 151 | If you press =o= once again in overview mode, the main slide frame 152 | will switch to overview mode as well. This can be very helpful for the 153 | Q&A after the talk since it makes it easier for people in the audience 154 | to refer to certain slides. 155 | 156 | *** Notes 157 | 158 | You can display additional notes on the presenter frame for each 159 | slide. This is done by creating an Org file which has the same name 160 | like your PDF, but with the suffix '-notes' added to it; that means, if 161 | your PDF file is called /presentation.pdf/, your Org file must be 162 | named /presentation-notes.org/. It must be of the following form: 163 | 164 | : * 1 165 | : - These are the notes for slide 1 166 | : - Do your motivational thing 167 | : * 2-5 168 | : - These are other notes 169 | : - They will be displayed on slides 2 to 5 170 | 171 | Granted, this is not very flexible, especially when you insert slides, 172 | since then you'll have to adapt all the numbers, so you should really 173 | do your notes when the slides are finished. Also note that if you're 174 | successively revealing parts of your slides, those will be separate 175 | pages in your PDF. If you have better ideas on how to uniquely link 176 | notes to certain slides, I'm all ears. 177 | 178 | *** Frame focus 179 | 180 | Usually, it shouldn't make a difference which of the frames currently 181 | has focus. Keys should work in both of them, but still you should make 182 | sure that the presenter frame always has focus; it's simply better 183 | tested and also "more natural". While it is possible in Emacs to 184 | select a certain frame, your window manager likely changes that 185 | depending on your mouse position. Therefore, this is something only 186 | you can manually ensure. If doc-present notices that the presenter 187 | frame does not have focus, it will show you a warning, but it will 188 | work nonetheless. 189 | 190 | Another reason why you should give focus to the presenter frame is to 191 | avoid redraws of the slide, which usually leads to flickering. This is 192 | especially noticeable when the mouse is over the slide picture. Still, 193 | a small flicker from time to time seems to be unavoidable, but I don't 194 | think it's a big issue (just blame it on the projector). 195 | 196 | *** Speed 197 | 198 | Scaling images with Emacs isn't particularly fast, so there usually is 199 | a noticeable delay before the next slide is displayed. You will notice 200 | however that once a slide was displayed, moving back and forth again 201 | will be fast because the image is now in Emacs' image 202 | cache. Unfortunately, the only way to cache images is to actually 203 | display them on the correct frame in the scaled size, which usually 204 | isn't feasible before a presentation. 205 | 206 | So really, you will have to live with this delay. If you want to 207 | quickly select frames, you should use overview mode. You might also 208 | want to try setting =imagemagick-render-type= to '1', which can speed 209 | things up as well. 210 | 211 | *** Screenshots 212 | 213 | [[docpresent-main.png]] 214 | 215 | [[docpresent-overview.png]] 216 | -------------------------------------------------------------------------------- /doc-present.el: -------------------------------------------------------------------------------- 1 | ;;; doc-present.el --- Present slides with Emacs 2 | 3 | ;; Copyright (C) 2013 David Engster 4 | 5 | ;; Author: David Engster 6 | ;; Keywords: presentation 7 | ;; 8 | ;; This file is not part of GNU Emacs. 9 | ;; 10 | ;; doc-present.el is free software: you can redistribute it and/or 11 | ;; modify it under the terms of the GNU General Public License as 12 | ;; published by the Free Software Foundation, either version 3 of the 13 | ;; License, or (at your option) any later version. 14 | 15 | ;; doc-present.el is distributed in the hope that it will be useful, 16 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of 17 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 18 | ;; General Public License for more details. 19 | 20 | ;; You should have received a copy of the GNU General Public License 21 | ;; along with GNU Emacs. If not, see . 22 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 23 | ;; 24 | ;;; Commentary: 25 | 26 | ;; Being fed up with the existing PDF viewers and their lacking 27 | ;; capabilities for doing presentations, I decided that Emacs must 28 | ;; save the day yet once again. 29 | 30 | ;; This package is meant to display presentation slides with a two 31 | ;; monitor setup. On one screen it shows the actual slides, and on 32 | ;; the other a so called "presenter screen", showing not only the 33 | ;; current slide but also the next one, a timer and also optional 34 | ;; notes which can be drawn from an Org file. It also features an 35 | ;; overview mode to quickly select certain slides. 36 | 37 | ;; This package uses doc-view, which ships with Emacs, to render the 38 | ;; actual document. So if you'd like to change how the document is 39 | ;; rendered, look at the doc-view variables. Most importantly, you 40 | ;; should increase `doc-view-resolution' (300 should be enough for a 41 | ;; projector, but large TFTs might require more). Also, doc-present 42 | ;; should work with any format which doc-view is able to render, 43 | ;; though I am only testing with PDFs. 44 | 45 | ;; Your Emacs must be build with Imagemagick support for this to work 46 | ;; correctly. This also implies that currently this won't work on W32 47 | ;; or OSX. Also, I'm pretty sure that you'll need Emacs version 24.x. 48 | 49 | ;; Usage in a nutshell: Load a PDF file into Emacs, which should 50 | ;; trigger doc-view mode. Wait till the whole document was rendered 51 | ;; (look in the modeline). Then do M-x doc-present-start and follow 52 | ;; the instructions. 53 | 54 | ;; Keys: 55 | ;; Left/Right, Up/Down, PgUp/PgDown: Previous/Next Slide 56 | ;; Space, Return, Mouse-Click: Next Slide 57 | ;; .: Black screen 58 | ;; f: Toggle fullscreen of slide frame 59 | ;; s: Start/Stop Timer 60 | ;; o: Slide overview (on presenter frame) 61 | ;; m: create a new main slide frame 62 | ;; h: Quick help 63 | ;; q: Quit 64 | 65 | ;; In Slide Overview: 66 | ;; 67 | ;; left/right/up/down: Choose slide 68 | ;; Return: Display slide in main slide frame, keep overview mode 69 | ;; Space: Display slide in main slide frame, switch to presenter mode 70 | ;; o: Show overview on slide frame as well 71 | ;; q: Quit overview and return to presenter mode 72 | 73 | ;;; Code: 74 | 75 | (require 'doc-view) 76 | (eval-when-compile (require 'cl)) 77 | 78 | (declare-function org-narrow-to-subtree "org") 79 | (declare-function org-map-entries "org") 80 | 81 | (defgroup doc-present nil 82 | "Present slides using Emacs." 83 | :group 'applications 84 | :group 'data 85 | :group 'multimedia 86 | :prefix "doc-present-") 87 | 88 | ;; User options 89 | 90 | (defcustom doc-present-slide-frame-background-color "white" 91 | "Color for the slide frame background. 92 | It might be that your slides do not fit exactly into a frame, so 93 | you should choose the color which matches those of your slides. 94 | Also, Emacs always reserves the rightmost column for displaying 95 | continuation or truncation characters, so this column will have 96 | this color. However, since we display the frame with a tiny 97 | font, this should be hardly noticeable (see also 98 | `doc-present-tiny-xft-font')." 99 | :type 'color 100 | :group 'doc-present) 101 | 102 | (defcustom doc-present-current-slide-width 600 103 | "Width for displaying the current slide." 104 | :type 'number 105 | :group 'doc-present) 106 | 107 | (defcustom doc-present-next-slide-width 500 108 | "Width for displaying the following slide." 109 | :type 'number 110 | :group 'doc-present) 111 | 112 | (defcustom doc-present-overview-image-width 200 113 | "Width of slides in Overview mode." 114 | :type 'number 115 | :group 'doc-present) 116 | 117 | (defcustom doc-present-presenter-layout 118 | "%W Current Slide: %N / %M Time: %T \n%C %N\n%O" 119 | "Layout for the presenter screen. 120 | The following keys can be used: 121 | %W: Stopwatch timer 122 | %T: Current time 123 | %N,%M: Current/max slide number 124 | %C,%N: Current/next slide image 125 | %O: Org notes" 126 | :type 'string 127 | :group 'doc-present) 128 | 129 | (defcustom doc-present-stopwatch-format "%.2m:%.2s" 130 | "Time format of the stop watch. 131 | See `format-seconds' on how to use thise." 132 | :type 'string 133 | :group 'doc-present) 134 | 135 | (defcustom doc-present-clock-time-format "%H:%M" 136 | "Time format for the clock. 137 | See `format-time-string' on how to use this." 138 | :type 'string 139 | :group 'doc-present) 140 | 141 | (defcustom doc-present-slide-frame-display nil 142 | "Display specification for slide frame. 143 | You will only need to set this if your secondary monitor is 144 | treated as a separate display by the X server, like \":0.1\". If 145 | you don't know what that means, just leave it at 'nil', which 146 | means to use the default display; this is the correct choice for 147 | xrandr/TwinView setups." 148 | :type '(choice :tag "Display for slide frame" 149 | (const :tag "Default") 150 | string) 151 | :group 'doc-present) 152 | 153 | ;; Other options 154 | 155 | (defvar doc-present-presenter-buffer-name "*doc-present main*" 156 | "Name of the presenter buffer.") 157 | (defvar doc-present-slide-buffer-name "*doc-present slide*" 158 | "Name of the slide buffer.") 159 | (defvar doc-present-overview-buffer-name "*doc-present overview*" 160 | "Name of the overview buffer.") 161 | 162 | (defvar doc-present-presenter-widgets 163 | '(("%W" doc-present-insert-stopwatch) 164 | ("%T" doc-present-insert-clock) 165 | ("%N" doc-present-insert-current-slide-number) 166 | ("%M" doc-present-insert-max-slide-number) 167 | ("%C" doc-present-insert-current-slide-image) 168 | ("%N" doc-present-insert-next-slide-image) 169 | ("%O" doc-present-insert-org-notes)) 170 | "Alist which defines what to show for which keys. 171 | If you'd like to define new keys for 172 | `doc-present-presenter-layout', just put them here together with 173 | the corresponding display function.") 174 | 175 | (defvar doc-present-help-text 176 | "Press 'q' to exit this buffer.\n\n 177 | Right, Down, PgDown, Space, Return: Next Slide 178 | Left, Up, PgUp: Previous Slide 179 | f: Toggle fullscreen of slide frame 180 | s: Start/Stop Timer 181 | .: Black out screen 182 | o: Slide overview 183 | m: Create new slide frame 184 | h,?: This 185 | q: Quit\n") 186 | 187 | (defvar doc-present-image-type 'imagemagick 188 | "Type of the image given to `create-image'.") 189 | 190 | ;; Faces 191 | 192 | (defface doc-present-stopwatch-face 193 | '((t :height 300)) 194 | "Face for displaying the stopwatch.") 195 | 196 | (defface doc-present-clock-face 197 | '((t :height 300)) 198 | "Face for displaying the clock.") 199 | 200 | (defface doc-present-slide-number-face 201 | '((t :height 300)) 202 | "Face for displaying the slide number.") 203 | 204 | (defface doc-present-notes-face 205 | '((t :height 200)) 206 | "Face for displaying the notes.") 207 | 208 | (defface doc-present-tiny-xft-font 209 | '((t :family "Bitstream Charter" :height 15)) 210 | "Tiny font for the slide display frame. 211 | This should be a really tiny font which will be chosen for 212 | displaying the main slide frame. The reason for this is a bit 213 | peculiar: Emacs frames cannot have arbitrary sizes, but are 214 | multiples of the font's width and height. Hence, the smaller the 215 | frame's font, the more flexibility you have in choosing the 216 | frame's size. The other reason is that without fringes, the 217 | rightmost column is always reserved for displaying 218 | truncation/continuation characters, hence it cannot be used for 219 | the slide. The smaller the font, the less noticeable this 220 | rightmost column will be.") 221 | 222 | ;; Internal variables 223 | 224 | (defstruct doc-present-status filename current max sframe pframe 225 | cachedir seconds-overlay clock-overlay slide-overlay 226 | notes seconds oldface oldsize renderwin slide-max-size 227 | slide-size aspect) 228 | 229 | (defvar doc-present--st (make-doc-present-status)) 230 | 231 | (defvar doc-present--notes nil) 232 | (defvar doc-present--stopwatch-timer nil) 233 | (defvar doc-present--clock-timer nil) 234 | 235 | ;; Code 236 | 237 | ;; `user-error' isn't defined in Emacs < 24.3 238 | (unless (fboundp 'user-error) 239 | (defalias 'user-error 'error)) 240 | 241 | (defun doc-present () 242 | (interactive) 243 | (when (doc-present-is-converter-process-running) 244 | (user-error "Conversion not finished yet")) 245 | (let ((st doc-present--st) 246 | size) 247 | (when (not (eq major-mode 'doc-view-mode)) 248 | (user-error "Not a doc-view buffer")) 249 | ;; Initialize our status structure 250 | (setf (doc-present-status-pframe st) (selected-frame)) 251 | (setf (doc-present-status-cachedir st) (doc-present-get-current-cache-dir-of-doc-view)) 252 | (setf (doc-present-status-current st) 1) 253 | (setf (doc-present-status-max st) (doc-view-last-page-number)) 254 | (setf (doc-present-status-filename st) (buffer-file-name)) 255 | (setf (doc-present-status-seconds st) 0) 256 | ;; Set aspect ratio of slides 257 | (setq size 258 | (image-size 259 | (create-image 260 | (expand-file-name 261 | "page-1.png" 262 | (doc-present-status-cachedir doc-present--st))) t)) 263 | (setf (doc-present-status-aspect st) (/ (float (car size)) (cdr size))) 264 | ;; Read in notes file 265 | (doc-present-snarf-notes-from-orgfile) 266 | ;; Create slide and presenter frames 267 | (doc-present-create-slide-frame) 268 | (doc-present-create-presenter-frame) 269 | (message "Press 'h' for help."))) 270 | 271 | (defun doc-present-get-current-cache-dir-of-doc-view () 272 | (if (boundp 'doc-view-current-cache-dir) 273 | doc-view-current-cache-dir 274 | doc-view--current-cache-dir)) 275 | 276 | (defun doc-present-is-converter-process-running () 277 | (if (boundp 'doc-view-current-converter-processes) 278 | doc-view-current-converter-processes 279 | doc-view--current-converter-processes)) 280 | 281 | (defvar doc-present-mode-map) 282 | (defun doc-present-mode () 283 | (interactive) 284 | (kill-all-local-variables) 285 | (setq major-mode 'doc-present-mode) 286 | (setq mode-name "doc-present") 287 | (set-syntax-table text-mode-syntax-table) 288 | (use-local-map doc-present-mode-map) 289 | (setq buffer-read-only t)) 290 | 291 | (defvar doc-present-mode-map 292 | (let ((map (make-keymap))) 293 | (define-key map [(right)] 'doc-present-next-slide) 294 | (define-key map [(left)] 'doc-present-previous-slide) 295 | (define-key map [(up)] 'doc-present-next-slide) 296 | (define-key map [(down)] 'doc-present-previous-slide) 297 | (define-key map [(prior)] 'doc-present-next-slide) 298 | (define-key map [(return)] 'doc-present-next-slide) 299 | (define-key map [(next)] 'doc-present-previous-slide) 300 | (define-key map [(s)] 'doc-present-toggle-stopwatch) 301 | (define-key map [(f)] 'doc-present-toggle-fullscreen) 302 | (define-key map [(q)] 'doc-present-quit) 303 | (define-key map [(o)] 'doc-present-overview) 304 | (define-key map [(.)] 'doc-present-toggle-black-out) 305 | (define-key map [(m)] 'doc-present-create-slide-frame) 306 | (define-key map [(h)] 'doc-present-help) 307 | (define-key map [(q)] 'doc-present-quit) 308 | map) 309 | "") 310 | 311 | (defun doc-present-create-slide-frame () 312 | (interactive) 313 | (when (frame-live-p (doc-present-status-sframe doc-present--st)) 314 | (user-error "Slide frame already existing")) 315 | (with-selected-frame 316 | (let ((parameters 317 | `((minibuffer . nil) 318 | (left-fringe . 0) 319 | (right-fringe . 0) 320 | (menu-bar-lines . 0) 321 | (internal-border-width . 0) 322 | (vertical-scroll-bars . nil) 323 | (unsplittable . t) 324 | (border-color . ,doc-present-slide-frame-background-color) 325 | (cursor-type . nil) 326 | (tool-bar-lines . 0)))) 327 | (when doc-present-slide-frame-display 328 | (setq parameters 329 | (append parameters 330 | `((display . ,doc-present-slide-frame-display))))) 331 | (make-frame parameters)) 332 | (setf (doc-present-status-sframe doc-present--st) (selected-frame)) 333 | (setf (doc-present-status-oldface doc-present--st) 334 | (cons (face-attribute 'default :family) 335 | (face-attribute 'default :height))) 336 | (switch-to-buffer (get-buffer-create doc-present-slide-buffer-name)) 337 | (doc-present-update-main-slide t) 338 | (doc-present-mode) 339 | (setq mode-line-format nil) 340 | (split-window-below 5) 341 | (switch-to-buffer (get-buffer-create "*doc-present-help-slide*")) 342 | (erase-buffer) 343 | (insert (concat "This is the main slide frame.\n\nDrag it to the " 344 | "presentation monitor and press 'f' to switch to " 345 | "fullscreen.")) 346 | (other-window 1) 347 | (redisplay))) 348 | 349 | (defun doc-present-create-presenter-frame () 350 | (let ((st doc-present--st)) 351 | (switch-to-buffer (get-buffer-create doc-present-presenter-buffer-name)) 352 | (doc-present-draw-presenter-frame) 353 | (doc-present-mode))) 354 | 355 | (defun doc-present-draw-presenter-frame () 356 | (unless (buffer-live-p (get-buffer doc-present-presenter-buffer-name)) 357 | (user-error "Presenter buffer was deleted. Restart presentation.")) 358 | (with-current-buffer doc-present-presenter-buffer-name 359 | (setq buffer-read-only nil) 360 | (erase-buffer) 361 | (save-excursion (insert doc-present-presenter-layout)) 362 | (dolist (cur doc-present-presenter-widgets) 363 | (save-excursion 364 | (when (search-forward (car cur) nil t) 365 | (delete-region (match-beginning 0) (match-end 0)) 366 | (funcall (cadr cur))))) 367 | (goto-char (point-max)))) 368 | 369 | (defun doc-present-insert-current-slide-image () 370 | (doc-present-insert-slide (doc-present-status-current doc-present--st) 371 | doc-present-current-slide-width 10)) 372 | 373 | (defun doc-present-insert-next-slide-image () 374 | (if (= (doc-present-status-current doc-present--st) 375 | (doc-present-status-max doc-present--st)) 376 | (insert "Last slide") 377 | (doc-present-insert-slide (1+ (doc-present-status-current doc-present--st)) 378 | doc-present-next-slide-width 10))) 379 | 380 | (defun doc-present-insert-slide (number width &optional margin relief) 381 | (let ((file 382 | (expand-file-name (format "page-%d.png" number) 383 | (doc-present-status-cachedir doc-present--st)))) 384 | (insert-image 385 | (create-image file doc-present-image-type nil :width width 386 | :margin (or margin 0) :relief (or relief 0))))) 387 | 388 | (defun doc-present-insert-clock () 389 | (let ((ov (make-overlay (point) (1+ (point))))) 390 | (setf (doc-present-status-clock-overlay doc-present--st) ov) 391 | (overlay-put ov 'display 392 | (propertize 393 | (format-time-string doc-present-clock-time-format (current-time)) 394 | 'face 'doc-present-clock-face)) 395 | (unless doc-present--clock-timer 396 | (setq doc-present--clock-timer 397 | (run-at-time t 1 'doc-present-update-clock))))) 398 | 399 | (defun doc-present-update-clock () 400 | (let ((ov (doc-present-status-clock-overlay doc-present--st)) 401 | (warntext "Warning: Presenter frame does not have focus.")) 402 | (when (and ov 403 | (buffer-live-p (get-buffer doc-present-presenter-buffer-name))) 404 | (with-current-buffer (get-buffer doc-present-presenter-buffer-name) 405 | (overlay-put ov 'display 406 | (propertize 407 | (format-time-string doc-present-clock-time-format (current-time)) 408 | 'face 'doc-present-clock-face)))) 409 | ;; Also warn if mouse is not in presenter frame 410 | (if (or (not (eq (car (mouse-position)) (doc-present-status-pframe doc-present--st))) 411 | (not (eq (selected-frame) (doc-present-status-pframe doc-present--st)))) 412 | (message warntext) 413 | (when (string-match-p warntext (or (current-message) "")) 414 | (message nil))))) 415 | 416 | (defun doc-present-insert-stopwatch () 417 | (let* ((ov (make-overlay (point) (1+ (point)))) 418 | (seconds (doc-present-status-seconds doc-present--st))) 419 | (setf (doc-present-status-seconds-overlay doc-present--st) ov) 420 | (overlay-put ov 'display 421 | (propertize 422 | (format-seconds doc-present-stopwatch-format seconds) 423 | 'face 'doc-present-stopwatch-face)))) 424 | 425 | (defun doc-present-insert-current-slide-number () 426 | (let ((number (doc-present-status-current doc-present--st))) 427 | (insert (propertize (number-to-string number) 428 | 'face 'doc-present-slide-number-face)))) 429 | 430 | (defun doc-present-insert-max-slide-number () 431 | (insert (propertize (number-to-string 432 | (doc-present-status-max doc-present--st)) 433 | 'face 'doc-present-slide-number-face))) 434 | 435 | (defun doc-present-insert-org-notes () 436 | (let ((notes (doc-present-status-notes doc-present--st)) 437 | (current (doc-present-status-current doc-present--st)) 438 | found cur) 439 | (when notes 440 | (while (setq cur (car notes)) 441 | (if (or (= current (caar cur)) 442 | (and (> current (caar cur)) 443 | (<= current (cdar cur)))) 444 | (setq found cur 445 | notes nil) 446 | (setq notes (cdr notes)))) 447 | (when found 448 | (insert (propertize (cadr found) 'face 'doc-present-notes-face)))))) 449 | 450 | (defun doc-present-parse-org-headlines () 451 | (org-narrow-to-subtree) 452 | (when (looking-at "^[*]+\\s-*\\([0-9]+\\)\\s-*\\(?:-\\s-*\\([0-9]+\\)\\)?") 453 | (let* ((first (match-string-no-properties 1)) 454 | (second (or (match-string-no-properties 2) first)) 455 | (text (progn (forward-line) 456 | (buffer-substring-no-properties (point) (point-max))))) 457 | (widen) 458 | `(,(cons (string-to-number first) 459 | (string-to-number second)) ,text)))) 460 | 461 | (defun doc-present-snarf-notes-from-orgfile () 462 | (let* ((filename (concat (file-name-sans-extension 463 | (doc-present-status-filename doc-present--st)) 464 | "-notes.org"))) 465 | (if (file-exists-p filename) 466 | (with-current-buffer (find-file-noselect filename) 467 | (setf (doc-present-status-notes doc-present--st) 468 | (org-map-entries 'doc-present-parse-org-headlines))) 469 | (setf (doc-present-status-notes doc-present--st) nil)))) 470 | 471 | (defun doc-present-quit () 472 | (interactive) 473 | (when (y-or-n-p "Really quit presentation?") 474 | (when (framep (doc-present-status-sframe doc-present--st)) 475 | (delete-frame (doc-present-status-sframe doc-present--st))) 476 | (kill-buffer doc-present-presenter-buffer-name) 477 | (kill-buffer doc-present-slide-buffer-name) 478 | (when (timerp doc-present--clock-timer) 479 | (cancel-timer doc-present--clock-timer)) 480 | (when (timerp doc-present--stopwatch-timer) 481 | (cancel-timer doc-present--stopwatch-timer)))) 482 | 483 | (defun doc-present-help-mode () 484 | (interactive) 485 | (kill-all-local-variables) 486 | (setq major-mode 'doc-present-help-mode) 487 | (setq mode-name "doc-present-help") 488 | (set-syntax-table text-mode-syntax-table) 489 | (use-local-map doc-present-help-mode-map) 490 | (setq buffer-read-only t)) 491 | 492 | (defvar doc-present-help-mode-map 493 | (let ((map (make-keymap))) 494 | (define-key map [(q)] 'doc-present-help-quit) 495 | map) 496 | "") 497 | 498 | (defun doc-present-help () 499 | (interactive) 500 | (select-window (split-window-horizontally -70)) 501 | (switch-to-buffer "*doc-present-help*") 502 | (insert doc-present-help-text) 503 | (doc-present-help-mode)) 504 | 505 | (defun doc-present-help-quit () 506 | (interactive) 507 | (kill-buffer "*doc-present-help*") 508 | (delete-window)) 509 | 510 | (defun doc-present-toggle-black-out () 511 | (interactive) 512 | (unless (frame-live-p (doc-present-status-sframe doc-present--st)) 513 | (user-error "No slide frame available (create a new one with 'm')")) 514 | (doc-present-ensure-full-slide-window) 515 | (with-selected-frame (doc-present-status-sframe doc-present--st) 516 | (set-buffer doc-present-slide-buffer-name) 517 | (setq buffer-read-only nil) 518 | (if (= (point-min) (point-max)) 519 | (doc-present-update-main-slide) 520 | (erase-buffer) 521 | (set-background-color "black")) 522 | (setq buffer-read-only t))) 523 | 524 | (defun doc-present-toggle-stopwatch () 525 | (interactive) 526 | (if doc-present--stopwatch-timer 527 | (progn 528 | (cancel-timer doc-present--stopwatch-timer) 529 | (message "Stopwatch stopped") 530 | (setq doc-present--stopwatch-timer nil)) 531 | (setq doc-present--stopwatch-timer 532 | (run-at-time t 1 'doc-present-update-stopwatch)) 533 | (message "Stopwatch started"))) 534 | 535 | (defun doc-present-update-stopwatch () 536 | (let ((ov (doc-present-status-seconds-overlay doc-present--st)) 537 | (seconds (incf (doc-present-status-seconds doc-present--st)))) 538 | (when (and ov 539 | (buffer-live-p (get-buffer doc-present-presenter-buffer-name))) 540 | (with-current-buffer (get-buffer doc-present-presenter-buffer-name) 541 | (overlay-put ov 'display 542 | (propertize 543 | (format-seconds doc-present-stopwatch-format seconds) 544 | 'face 'doc-present-stopwatch-face)))))) 545 | 546 | (defvar doc-present--prerender-workload nil) 547 | (defvar doc-present--prerender-timer nil) 548 | (defun doc-present-create-slide-cache () 549 | (interactive) 550 | (message "Starting to create slide cache") 551 | (setq doc-present--prerender-workload nil) 552 | (with-selected-frame (doc-present-status-pframe doc-present--st) 553 | (setf (doc-present-status-renderwin doc-present--st) 554 | (split-window-horizontally -15))) 555 | (unless (frame-live-p (doc-present-status-sframe doc-present--st)) 556 | (user-error "No slide frame available (create a new one with 'm')")) 557 | (unless (eq (frame-parameter (doc-present-status-sframe doc-present--st) 558 | 'fullscreen) 'fullboth) 559 | (user-error "Maximize slide frame on presentation projector first")) 560 | (dolist (i (number-sequence 1 (doc-present-status-max doc-present--st))) 561 | (setq doc-present--prerender-workload 562 | (append 563 | doc-present--prerender-workload 564 | `((,i ,(doc-present-status-slide-size doc-present--st)) 565 | (,i ,(cons :width doc-present-current-slide-width)) 566 | (,i ,(cons :width doc-present-next-slide-width)))))) 567 | (setq doc-present--prerender-timer 568 | (run-with-idle-timer 1 1 'doc-present-prerender-slides))) 569 | 570 | ;; Caching only happens for a frame, so this is pointless... 571 | (defun doc-present-prerender-slides () 572 | (if (null doc-present--prerender-workload) 573 | (progn 574 | (message "Slide cache creation finished") 575 | (when (window-live-p (doc-present-status-renderwin doc-present--st)) 576 | (delete-window (doc-present-status-renderwin doc-present--st))) 577 | (kill-buffer "*doc-present-prerender*") 578 | (cancel-timer doc-present--prerender-timer)) 579 | (let (current size) 580 | (catch 'exit 581 | (while (setq current (pop doc-present--prerender-workload)) 582 | (setq size (cadr current)) 583 | (unless (window-live-p (doc-present-status-renderwin doc-present--st)) 584 | (message "Prerender window not visible. Canceling cache creation.") 585 | (cancel-timer doc-present--prerender-timer)) 586 | (with-selected-window (doc-present-status-renderwin doc-present--st) 587 | (switch-to-buffer (get-buffer-create "*doc-present-prerender*")) 588 | (erase-buffer) 589 | (insert-image 590 | (create-image 591 | (expand-file-name 592 | (format "page-%d.png" (car current)) 593 | (doc-present-status-cachedir doc-present--st)) 594 | doc-present-image-type nil (car size) (cdr size))) 595 | (redisplay t) 596 | (when (input-pending-p) 597 | (throw 'exit nil)))))))) 598 | 599 | (defun doc-present-select-presenter-frame () 600 | "Make sure we are on the presenter frame." 601 | (unless (eq (selected-frame) (doc-present-status-pframe doc-present--st)) 602 | (select-frame (doc-present-status-pframe doc-present--st)))) 603 | 604 | (defun doc-present-toggle-fullscreen () 605 | (interactive) 606 | (unless (frame-live-p (doc-present-status-sframe doc-present--st)) 607 | (user-error "No slide frame available (create a new one with 'm')")) 608 | (with-selected-frame (doc-present-status-sframe doc-present--st) 609 | (if (eq (frame-parameter nil 'fullscreen) 'fullboth) 610 | (progn 611 | ;; switch back from fullscreen 612 | (message "Switching back") 613 | (modify-frame-parameters nil '((fullscreen . nil))) 614 | (let ((oldface (doc-present-status-oldface doc-present--st)) 615 | (oldsize (doc-present-status-oldsize doc-present--st))) 616 | (set-face-attribute 'default (selected-frame) 617 | :family (car oldface) :height (cdr oldface)) 618 | (message "Restoring image width/height %d %d" (car oldsize) (cdr oldsize)) 619 | (modify-frame-parameters nil `((width . ,(car oldsize)) 620 | (height . ,(cdr oldsize)))))) 621 | ;; switch to fullscreen 622 | (message "Switch to fullscreen") 623 | (setf (doc-present-status-oldsize doc-present--st) 624 | (cons (frame-parameter nil 'width) 625 | (frame-parameter nil 'height))) 626 | (delete-other-windows) 627 | (modify-frame-parameters nil '((fullscreen . fullboth))) 628 | (redisplay t) 629 | (set-face-attribute 630 | 'default (selected-frame) 631 | :family (face-attribute 'doc-present-tiny-xft-font :family) 632 | :height (face-attribute 'doc-present-tiny-xft-font :height)) 633 | (redisplay t) 634 | (set-cursor-color doc-present-slide-frame-background-color) 635 | (set-background-color doc-present-slide-frame-background-color) 636 | (setf (doc-present-status-slide-max-size doc-present--st) 637 | (cons (frame-pixel-width) (frame-pixel-height)))) 638 | (if (get-buffer-window doc-present-overview-buffer-name) 639 | (doc-present-overview-on-slide-frame) 640 | (doc-present-update-main-slide)))) 641 | 642 | (defun doc-present-presentation-start () 643 | (interactive) 644 | (with-selected-frame (doc-present-status-sframe doc-present--st) 645 | (set-face-attribute 646 | 'default (selected-frame) 647 | :family (face-attribute 'doc-present-tiny-xft-font :family) 648 | :height (face-attribute 'doc-present-tiny-xft-font :height)) 649 | (set-cursor-color doc-present-slide-frame-background-color) 650 | (set-background-color doc-present-slide-frame-background-color) 651 | (modify-frame-parameters nil '((fullscreen . fullboth))) 652 | (redisplay t) 653 | (with-current-buffer doc-present-slide-buffer-name 654 | (setq buffer-read-only nil) 655 | (goto-char (point-min)) 656 | (delete-region (point) (1+ (point-at-eol))) 657 | (goto-char (point-max)) 658 | (let ((disp (get-text-property (point-min) 'display))) 659 | (setcdr disp (plist-put 660 | (cdr disp) :width (frame-pixel-width))) 661 | (setcdr disp (plist-put 662 | (cdr disp) 663 | :height (frame-pixel-height))) 664 | (put-text-property (point-min) (1+ (point-min)) 665 | 'display 666 | disp)) 667 | (setq buffer-read-only t))) 668 | (select-frame (doc-present-status-pframe doc-present--st))) 669 | 670 | (defun doc-present-next-slide () 671 | (interactive) 672 | (if (= (doc-present-status-current doc-present--st) 673 | (doc-present-status-max doc-present--st)) 674 | (message "Already on last slide.") 675 | (incf (doc-present-status-current doc-present--st)) 676 | (doc-present-select-presenter-frame) 677 | (doc-present-draw-presenter-frame) 678 | (doc-present-update-main-slide))) 679 | 680 | (defun doc-present-previous-slide () 681 | (interactive) 682 | (if (= (doc-present-status-current doc-present--st) 1) 683 | (message "Already on first slide.") 684 | (decf (doc-present-status-current doc-present--st)) 685 | (doc-present-select-presenter-frame) 686 | (doc-present-draw-presenter-frame) 687 | (doc-present-update-main-slide))) 688 | 689 | (defun doc-present-ensure-full-slide-window () 690 | (if (not (frame-live-p (doc-present-status-sframe doc-present--st))) 691 | (message "Slide frame was deleted (hit 'm' to create a new one)") 692 | (with-selected-frame (doc-present-status-sframe doc-present--st) 693 | (let ((slidewin (get-buffer-window doc-present-slide-buffer-name))) 694 | (unless (and (window-full-width-p slidewin) 695 | (window-full-height-p slidewin)) 696 | (with-selected-window slidewin 697 | (delete-other-windows))))))) 698 | 699 | (defun doc-present-update-main-slide (&optional use-window-size) 700 | (if (not (frame-live-p (doc-present-status-sframe doc-present--st))) 701 | (message "Slide frame was deleted (hit 'm' to create a new one)") 702 | (let* ((number (doc-present-status-current doc-present--st)) 703 | (cachedir (doc-present-status-cachedir doc-present--st)) 704 | (file 705 | (expand-file-name 706 | (format "page-%d.png" number) cachedir)) 707 | width height props) 708 | (doc-present-ensure-full-slide-window) 709 | (if use-window-size 710 | (with-selected-window 711 | (get-buffer-window doc-present-slide-buffer-name) 712 | (let ((edges (window-inside-pixel-edges))) 713 | (setq width (- (nth 2 edges) (car edges)) 714 | height (- (nth 3 edges) (nth 1 edges))))) 715 | (with-selected-frame (doc-present-status-sframe doc-present--st) 716 | (set-background-color doc-present-slide-frame-background-color) 717 | (setq width (frame-pixel-width) 718 | height (frame-pixel-height)))) 719 | (with-current-buffer (get-buffer doc-present-slide-buffer-name) 720 | (setq buffer-read-only nil) 721 | (erase-buffer) 722 | (if (> (/ (float width) height) 723 | (doc-present-status-aspect doc-present--st)) 724 | (progn 725 | (setq props 726 | (plist-put props :height height)) 727 | (setf (doc-present-status-slide-size doc-present--st) 728 | (cons :height height))) 729 | (setq props 730 | (plist-put props :width width)) 731 | (setf (doc-present-status-slide-size doc-present--st) 732 | (cons :width width))) 733 | (insert-image 734 | (apply 'create-image file doc-present-image-type nil props)) 735 | (setq buffer-read-only t))))) 736 | 737 | ;;; Slide Overview 738 | 739 | ;; Internal variables 740 | 741 | (defvar doc-present-overview-columns nil) 742 | (defvar doc-present-overview-lines nil) 743 | (defvar doc-present-overview-old-image 0) 744 | (defvar doc-present-overview-current-image 1) 745 | (defvar doc-present-overview-max-number 0) 746 | (defvar doc-present-overview-image-positions nil) 747 | 748 | ;; Code 749 | 750 | (defun doc-present-overview () 751 | (interactive) 752 | (select-frame (doc-present-status-pframe doc-present--st)) 753 | (if (eq major-mode 'doc-present-overview-mode) 754 | (doc-present-overview-on-slide-frame) 755 | (doc-present-overview-page))) 756 | 757 | (defun doc-present-overview-page () 758 | (let* ((cachedir (doc-present-status-cachedir doc-present--st)) 759 | (numpages (doc-present-status-max doc-present--st)) 760 | (edges (window-inside-pixel-edges)) 761 | (windowwidth (- (nth 2 edges) (car edges))) 762 | (charwidth (frame-char-width)) 763 | (counter 1) 764 | file) 765 | (when (eq (frame-parameter nil 'fullscreen) 'fullboth) 766 | (setq windowwidth (car (doc-present-status-slide-max-size doc-present--st)))) 767 | (setq doc-present-overview-columns 768 | (round (/ windowwidth 769 | (+ doc-present-overview-image-width 30)))) 770 | (setq doc-present-overview-image-positions 771 | (make-vector (1+ numpages) 0)) 772 | (switch-to-buffer (get-buffer-create doc-present-overview-buffer-name)) 773 | (setq buffer-read-only nil 774 | doc-present-overview-old-image 0) 775 | (erase-buffer) 776 | (doc-present-overview-insert-numbers 1 doc-present-overview-columns) 777 | (while (< counter numpages) 778 | (setq file (expand-file-name (format "page-%d.png" counter) 779 | cachedir)) 780 | (aset doc-present-overview-image-positions counter (point)) 781 | (insert-image 782 | (create-image file doc-present-image-type nil 783 | :width doc-present-overview-image-width 784 | :margin charwidth 785 | :relief 0)) 786 | (when (zerop (mod counter doc-present-overview-columns)) 787 | (insert "\n") 788 | (redisplay t) 789 | (doc-present-overview-insert-numbers 790 | (1+ counter) (min (+ counter doc-present-overview-columns) 791 | (1- numpages)))) 792 | (setq counter (1+ counter))) 793 | (doc-present-overview-mode) 794 | (setq doc-present-overview-lines (line-number-at-pos)) 795 | (goto-char (point-min)) 796 | (forward-line) 797 | (doc-present-overview-maybe-raise) 798 | (setq doc-present-overview-current-image 1 799 | doc-present-overview-max-number numpages))) 800 | 801 | (defun doc-present-overview-insert-numbers (from to) 802 | (let* ((charwidth (frame-char-width)) 803 | (chars-per-image (+ (/ doc-present-overview-image-width charwidth) 2)) 804 | (tab-stop-list (number-sequence (/ chars-per-image 2) 1000 805 | chars-per-image))) 806 | (mapc 807 | (lambda (num) 808 | (tab-to-tab-stop) 809 | (insert (number-to-string num))) 810 | (number-sequence from to)) 811 | (insert "\n"))) 812 | 813 | (defvar doc-present-overview-mode-map) 814 | (defun doc-present-overview-mode () 815 | (interactive) 816 | (kill-all-local-variables) 817 | (setq major-mode 'doc-present-overview-mode) 818 | (setq mode-name "doc-present-overview") 819 | (set-syntax-table text-mode-syntax-table) 820 | (use-local-map doc-present-overview-mode-map) 821 | (setq buffer-read-only t)) 822 | 823 | (defvar doc-present-overview-mode-map 824 | (let ((map (make-keymap))) 825 | (define-key map [(right)] 'doc-present-overview-right) 826 | (define-key map [(left)] 'doc-present-overview-left) 827 | (define-key map [(up)] 'doc-present-overview-up) 828 | (define-key map [(down)] 'doc-present-overview-down) 829 | (define-key map [(o)] 'doc-present-overview-on-slide-frame) 830 | (define-key map [(return)] 'doc-present-overview-show-slide) 831 | (define-key map (kbd "SPC") 'doc-present-overview-show-slide-and-quit) 832 | (define-key map [(f)] 'doc-present-toggle-fullscreen) 833 | (define-key map [(q)] 'doc-present-overview-quit) 834 | map) 835 | "") 836 | 837 | (defun doc-present-overview-show-slide () 838 | (interactive) 839 | (setf (doc-present-status-current doc-present--st) 840 | doc-present-overview-current-image) 841 | (doc-present-update-main-slide)) 842 | 843 | (defun doc-present-overview-quit () 844 | (interactive) 845 | (kill-buffer)) 846 | 847 | (defun doc-present-overview-show-slide-and-quit () 848 | (interactive) 849 | (doc-present-overview-show-slide) 850 | (doc-present-overview-quit) 851 | (with-selected-frame (doc-present-status-pframe doc-present--st) 852 | (when (buffer-live-p (get-buffer doc-present-presenter-buffer-name)) 853 | (switch-to-buffer (get-buffer doc-present-presenter-buffer-name)) 854 | (delete-other-windows)) 855 | (doc-present-draw-presenter-frame))) 856 | 857 | (defun doc-present-overview-on-slide-frame () 858 | (interactive) 859 | (let ((bc (frame-parameter (doc-present-status-pframe doc-present--st) 860 | 'background-color))) 861 | (with-selected-frame (doc-present-status-sframe doc-present--st) 862 | (let ((oldface (doc-present-status-oldface doc-present--st))) 863 | (set-face-attribute 'default (selected-frame) 864 | :family (car oldface) :height (cdr oldface)) 865 | (set-background-color bc) 866 | (set-frame-parameter nil 'cursor-type 'box)) 867 | (redisplay t) 868 | (switch-to-buffer doc-present-overview-buffer-name) 869 | (doc-present-overview-page) 870 | (goto-char (point-min)) 871 | (forward-line) 872 | (setq mode-line-format nil) 873 | (set-window-start nil (point-min))) 874 | (with-selected-frame (doc-present-status-pframe doc-present--st) 875 | (with-current-buffer doc-present-overview-buffer-name 876 | (goto-char (point-min)) 877 | (forward-line) 878 | (doc-present-overview-maybe-raise))))) 879 | 880 | (defun doc-present-overview-right () 881 | (interactive) 882 | (unless (or (= (current-column) (1- doc-present-overview-columns)) 883 | (>= (1+ doc-present-overview-current-image) 884 | doc-present-overview-max-number)) 885 | (forward-char) 886 | (doc-present-overview-sync-frames) 887 | (setq doc-present-overview-current-image 888 | (1+ doc-present-overview-current-image)) 889 | (doc-present-overview-maybe-raise))) 890 | 891 | (defun doc-present-overview-left () 892 | (interactive) 893 | (unless (= (current-column) 0) 894 | (forward-char -1) 895 | (doc-present-overview-sync-frames) 896 | (setq doc-present-overview-current-image 897 | (1- doc-present-overview-current-image)) 898 | (doc-present-overview-maybe-raise))) 899 | 900 | (defun doc-present-overview-up () 901 | (interactive) 902 | (unless (= (line-number-at-pos) 2) 903 | (setq doc-present-overview-current-image 904 | (- doc-present-overview-current-image 905 | doc-present-overview-columns)) 906 | (goto-char (aref doc-present-overview-image-positions 907 | doc-present-overview-current-image)) 908 | (doc-present-overview-sync-frames) 909 | (doc-present-overview-maybe-raise))) 910 | 911 | (defun doc-present-overview-down () 912 | (interactive) 913 | (unless (>= (+ doc-present-overview-current-image 914 | doc-present-overview-columns) 915 | doc-present-overview-max-number) 916 | (setq doc-present-overview-current-image 917 | (+ doc-present-overview-current-image 918 | doc-present-overview-columns)) 919 | (goto-char (aref doc-present-overview-image-positions 920 | doc-present-overview-current-image)) 921 | (doc-present-overview-sync-frames) 922 | (doc-present-overview-maybe-raise))) 923 | 924 | (defun doc-present-overview-sync-frames () 925 | (let ((pt (point))) 926 | (if (eq (selected-frame) 927 | (doc-present-status-sframe doc-present--st)) 928 | (with-selected-frame (doc-present-status-pframe doc-present--st) 929 | (goto-char pt)) 930 | (with-selected-frame (doc-present-status-sframe doc-present--st) 931 | (goto-char pt))))) 932 | 933 | (defun doc-present-overview-maybe-raise () 934 | (unless (= (point) doc-present-overview-old-image) 935 | (let ((disp (get-text-property (point) 'display))) 936 | (with-silent-modifications 937 | (when (eq (car disp) 'image) 938 | (setcdr disp (plist-put (cdr disp) :margin 4)) 939 | (setcdr disp (plist-put (cdr disp) :relief 4)) 940 | (set-text-properties (point) (1+ (point)) `(display ,disp)) 941 | (when (not (zerop doc-present-overview-old-image)) 942 | (setq disp (get-text-property doc-present-overview-old-image 'display)) 943 | (setcdr disp (plist-put (cdr disp) :margin 8)) 944 | (setcdr disp (plist-put (cdr disp) :relief 0)) 945 | (set-text-properties doc-present-overview-old-image 946 | (1+ doc-present-overview-old-image) `(display ,disp))) 947 | (setq doc-present-overview-old-image (point))))))) 948 | 949 | (provide 'doc-present) 950 | 951 | ;; doc-present.el ends here 952 | -------------------------------------------------------------------------------- /docpresent-main.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dengste/doc-present/1cfa7c064a3e0324087ef97f9ba504465cbf9c2c/docpresent-main.png -------------------------------------------------------------------------------- /docpresent-overview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dengste/doc-present/1cfa7c064a3e0324087ef97f9ba504465cbf9c2c/docpresent-overview.png --------------------------------------------------------------------------------