]> git.donarmstrong.com Git - lilypond.git/blob - scm/framework-gnome.scm
* lily/lily-guile.cc (ly_to_string, ly_to_symbol): New function.
[lilypond.git] / scm / framework-gnome.scm
1 ;;;; framework-gnome.scm --
2 ;;;;
3 ;;;;  source file of the GNU LilyPond music typesetter
4 ;;;; 
5 ;;;; (c)  2004 Jan Nieuwenhuizen <janneke@gnu.org>
6
7 ;;;; See output-gnome.scm for usage information.
8
9
10 (define-module (scm framework-gnome))
11
12 (use-modules (guile) (oop goops) (lily))
13
14 (use-modules
15  (srfi srfi-2)
16  (ice-9 regex)
17  (gnome gtk)
18  (gnome gtk gdk-event)
19  (gnome gw canvas))
20
21 (define-public (output-framework outputter book scopes fields basename)
22   (gnome-main book basename))
23
24 (define SCROLLBAR-SIZE 20)
25 (define BUTTON-HEIGHT 25)
26 (define PANELS-HEIGHT 80)
27
28 (define PIXELS-PER-UNIT 2)
29 (define OUTPUT-SCALE (* 2.5 PIXELS-PER-UNIT))
30 (define-public output-scale OUTPUT-SCALE)
31
32 (define (stderr string . rest)
33   (apply format (cons (current-error-port) (cons string rest)))
34   (force-output (current-error-port)))
35
36 (define (debugf string . rest)
37   (if #f
38       (stderr (cons string rest))))
39       
40 (define-class <gnome-outputter> ()
41   (name #:init-value "untitled" #:init-keyword #:name #:accessor name)
42   (page-stencils ;;#:init-value '#()
43    #:init-keyword #:page-stencils #:accessor page-stencils)
44   (window #:init-value (make <gtk-window> #:type 'toplevel) #:accessor window)
45   (scrolled #:init-value (make <gtk-scrolled-window>) #:accessor scrolled)
46   (canvas #:init-value #f #:accessor canvas)
47   (page-number #:init-value 0 #:accessor page-number)
48   (pixels-per-unit #:init-value PIXELS-PER-UNIT #:accessor pixels-per-unit)
49   (text-items #:init-value '() #:accessor text-items)
50   (grob #:init-value #f #:accessor grob)
51   (item-grobs #:init-value (make-hash-table 31) #:accessor item-grobs)
52   (grob-tweaks #:init-value (make-hash-table 31) #:accessor grob-tweaks)
53   (window-width #:init-keyword #:window-width #:accessor window-width)
54   (window-height #:init-keyword #:window-height #:accessor window-height)
55   (canvas-width #:init-keyword #:canvas-width #:accessor canvas-width)
56   (canvas-height #:init-keyword #:canvas-height #:accessor canvas-height))
57
58 (define-method (initialize (go <gnome-outputter>))
59   (let* ((save (make <gtk-button> #:label "Save"))
60          (exit (make <gtk-button> #:label "Exit"))
61          (next (make <gtk-button> #:label "Next"))
62          (prev (make <gtk-button> #:label "Previous"))
63          (vbox (make <gtk-vbox> #:homogeneous #f))
64          (hbox (make <gtk-hbox> #:homogeneous #f)))
65
66     (set-size-request (window go) (window-width go) (window-height go))
67     
68     (set-size-request (scrolled go) (window-width go) (- (window-height go)
69                                                          BUTTON-HEIGHT
70                                                          SCROLLBAR-SIZE))
71
72     (new-canvas go)
73
74     (add (window go) vbox)
75     (add vbox (scrolled go))
76     
77     (add (scrolled go) (canvas go))
78
79     ;; buttons
80     (add vbox hbox)
81     (set-size-request hbox (window-width go) BUTTON-HEIGHT)
82
83     ;; hmm?  These are broken when using <gnome-outputter>.
84     ;;(set-child-packing vbox hbox #f #f 0 'end)
85     ;;(set-child-packing hbox button #f #f 0 'end)
86     
87     (set-size-request exit (quotient (window-width go) 2) BUTTON-HEIGHT)
88
89     
90     (add hbox next)
91     (add hbox prev)
92     (add hbox save)
93     (add hbox exit)
94
95     ;; signals
96     (connect exit 'clicked (lambda (b) (gtk-main-quit)))
97     (connect save 'clicked (lambda (b) (save-tweaks go)))
98     (connect next 'clicked (lambda (b) (dump-page go (1+ (page-number go)))))
99     (connect prev 'clicked (lambda (b) (dump-page go (1- (page-number go)))))
100     (connect (window go) 'key-press-event
101              (lambda (w e) (key-press-event go w e)))
102     
103     (show-all (window go))))
104
105
106 (define (gnome-main book name)
107   (let* ((paper (ly:paper-book-paper book))
108          (hsize (ly:output-def-lookup paper 'hsize))
109          (vsize (ly:output-def-lookup paper 'vsize))
110          (page-width (inexact->exact (ceiling (* OUTPUT-SCALE hsize))))
111          (page-height (inexact->exact (ceiling (* OUTPUT-SCALE vsize))))
112          ;;(page-width (inexact->exact (ceiling hsize)))
113          ;;(page-height (inexact->exact (ceiling vsize)))
114
115          (screen-width (gdk-screen-width))
116          (screen-height (gdk-screen-height))
117          (desktop-height (- screen-height PANELS-HEIGHT))
118
119          (go (make <gnome-outputter>
120                #:name name
121                #:page-stencils (list->vector (ly:paper-book-pages book))
122                #:canvas-width page-width
123                #:canvas-height page-height
124                #:window-width
125                ;; huh, *2 -- pixels-per-unit?
126                (min (+ SCROLLBAR-SIZE (* page-width 2)) screen-width)
127                #:window-height
128                (min (+ BUTTON-HEIGHT SCROLLBAR-SIZE (* page-height 2))
129                     desktop-height))))
130
131     ;; ugh.  The GOOPS doc promises this is called automagically.
132     ;; possibly a goops 1.6.4 problem
133     (initialize go)
134
135     (map ly:pango-add-afm-decoder
136          '("lilypond-feta"
137            "lilypond-braces"
138            "lilypond-dyn"
139            "lilypond-parmesan"))
140
141     (dump-page go 0)
142
143     (gtk-main)))
144
145 (define (dump-page go number)
146   (if (or (not (page-stencils go))
147           (< number 0)
148           (>= number (vector-length (page-stencils go))))
149       (stderr "No such page: ~S\n" (1+ number))
150       
151       (let ((old-canvas (canvas go)))
152         (new-canvas go)
153         (set! (page-number go) number)
154         
155         ;; no destroy method for gnome-canvas-text yet.
156         ;;(map destroy (gtk-container-get-children main-canvas))
157         ;;(map destroy text-items)
158
159         (set! (text-items go) '())
160         (debugf "page-stencil ~S: ~S\n"
161                 (page-number go)                
162                 (vector-ref (page-stencils go) (page-number go)))
163         
164         (ly:interpret-stencil-expression 
165          ;; ;;(vector-ref (page-stencils go) (page-number go))
166          (ly:stencil-expr (vector-ref (page-stencils go) (page-number go)))
167          gnome-output-expression go '(0 . 0))
168
169         (if old-canvas (destroy old-canvas))
170         (add (scrolled go) (canvas go))
171         (show (canvas go)))))
172
173 (define x-editor #f)
174 (define (get-x-editor)
175   (if (not x-editor)
176       (set! x-editor (getenv "XEDITOR")))
177   x-editor)
178
179 (define ifs #f)
180 (define (get-ifs)
181   (if (not ifs)
182       (set! ifs (getenv "IFS")))
183   (if (not ifs)
184       (set! ifs "       "))
185   ifs)
186       
187 (define (spawn-editor location)
188   (let* ((file-name (car location))
189          (line (cadr location))
190          (column (caddr location))
191          (template (substring (get-x-editor) 0))
192          
193          ;; Adhere to %l %c %f?
194          (command
195           (regexp-substitute/global
196            #f "%l" (regexp-substitute/global
197                     #f "%c"
198                     (regexp-substitute/global
199                      #f "%f" template 'pre file-name 'post)
200                     'pre (number->string column)
201                     'post)
202            'pre (number->string line) 'post)))
203     
204     (debugf "spawning: ~s\n" command)
205     (if (= (primitive-fork) 0)
206         (let ((command-list (string-split command #\ )));; (get-ifs))))
207           (apply execlp command-list)
208           (primitive-exit)))))
209           
210 (define location-callback spawn-editor)
211
212 (define (get-location grob)
213   (and-let* ((p? (procedure? point-and-click))
214              (g grob)
215              (cause (ly:grob-property grob 'cause))
216              (music-origin (if (ly:music? cause)
217                                (ly:music-property cause 'origin)
218                                ;; How come #<unspecied> [and '()]
219                                ;; are #t? :-(
220                                #f)))
221             (if (ly:input-location? music-origin)
222                 (ly:input-location music-origin)
223                 #f)))
224
225 (define-method (tweak (go <gnome-outputter>) item offset)
226   (let* ((grob (hashq-ref (item-grobs go) item #f))
227          (extra-offset (ly:grob-property grob 'extra-offset))
228          (origin (hashq-ref (grob-tweaks go) grob
229                             (cons (car extra-offset)
230                                   (- 0 (cdr extra-offset))))))
231     (if grob
232         (hashq-set! (grob-tweaks go) grob (cons (+ (car origin) (car offset))
233                                                 (+ (cdr origin) (cdr offset)))))
234     (move item (car offset) (cdr offset))))
235
236 (define-method (save-tweaks (go <gnome-outputter>))
237   (let ;;((file (current-error-port)))
238       ((file (open-file (string-append (name go) ".ly.t") "w")))
239     (format file ";;; TWEAKS \n")
240     (format file ";;(define grob-id-tweak-alist \n'(\n")
241     (hash-fold
242      (lambda (key value seed)
243        (format file "(~S extra-offset ~S)\n"
244                (if (ly:grob? key) (ly:grob-id key) ";;unidentified grob")
245                value))
246      #f (grob-tweaks go))
247     (format file ")\n;;)\n")))
248
249 ;;;(define (item-event go grob item event)
250 (define (item-event go item event)
251   (case (gdk-event:type event)
252     ((enter-notify) (gobject-set-property item 'fill-color "red"))
253     ((leave-notify) (gobject-set-property item 'fill-color "black"))
254     ((button-press)
255      (let ((button (gdk-event-button:button event)))
256        (cond
257         ((= button 1)
258          (and-let* ((grob (hashq-ref (item-grobs go) item #f))
259                     (location (get-location grob)))
260                    (location-callback location)))
261         ((= button 2)
262
263          (and-let*
264           ((grob (hashq-ref (item-grobs go) item #f)))
265           
266           (let ((properties (ly:grob-properties grob))
267                 (basic-properties (ly:grob-basic-properties grob))
268                 (id (ly:grob-id grob))
269                 (x (inexact->exact (gdk-event-button:x-root event)))
270                 (y (inexact->exact (gdk-event-button:y-root event))))
271                
272             (debugf "GROB: ~S\n" grob)
273             (debugf "PROPERTIES: ~S\n" properties)
274             (debugf "BASIC PROPERTIES: ~S\n" basic-properties)
275             
276             (let ((window (make <gtk-window>))
277                   (vbox (make <gtk-vbox>))
278                   (button (make <gtk-button> #:label "Ok")))
279               
280               (add window vbox)
281               (connect button 'clicked (lambda (b) (destroy window)))
282
283               (for-each
284                (lambda (x)
285                  (let ((button (make <gtk-button>
286                                  #:xalign 0.0
287                                  #:label
288                                  (string-append
289                                   (symbol->string (car x))
290                                   ": "
291                                   (format #f "~S" (cdr x))))))
292                    (set-size-request button 150 BUTTON-HEIGHT)
293                    (add vbox button)))
294                (cons (list id) properties))
295               (add vbox button)
296               
297               ;; FIXME: how to do window placement?
298               ;; - no effect:
299               (move window x y)
300               (show-all window)
301               ;; - shows actual movement:
302               (move window x y)
303               )))))))
304     
305     ((2button-press) (gobject-set-property item 'fill-color "green"))
306     ((key-press)
307      (let ((keyval (gdk-event-key:keyval event))
308            (mods (gdk-event-key:modifiers event))
309            (step (quotient (pixels-per-unit go) 2)))
310        (cond ((and (null? mods)
311                    (eq? keyval gdk:Up))
312               (tweak go item (cons 0 (- 0 step))))
313              ((and (null? mods)
314                    (eq? keyval gdk:Down))
315               (tweak go item (cons 0 step)))
316              ((and (null? mods)
317                    (eq? keyval gdk:Left))
318               (tweak go item (cons (- 0 step) 0)))
319              ((and (null? mods)
320                    (eq? keyval gdk:Right))
321               (tweak go item (cons step 0)))))))
322   #t)
323
324 (define (scale-canvas go factor)
325   (set! (pixels-per-unit go) (* (pixels-per-unit go) factor))
326   (set-pixels-per-unit (canvas go) (pixels-per-unit go))
327   (for-each
328    (lambda (x)
329      (let ((scale (gobject-get-property x 'scale))
330            (points (gobject-get-property x 'size-points)))
331        ;;(gobject-set-property x 'scale pixels-per-unit)
332        (gobject-set-property x 'size-points (* points factor))))
333      (text-items go)))
334
335 (define (key-press-event go item event)
336   (let ((keyval (gdk-event-key:keyval event))
337         (mods (gdk-event-key:modifiers event)))
338     (cond ((and (or (eq? keyval gdk:q)
339                     (eq? keyval gdk:w))
340                 (equal? mods '(control-mask modifier-mask)))
341            (gtk-main-quit))
342           ((and #t ;;(null? mods)
343                 (eq? keyval gdk:plus))
344            (scale-canvas go 2))
345           ((and #t ;; (null? mods)
346                 (eq? keyval gdk:minus))
347            (scale-canvas go 0.5))
348           ((or (eq? keyval gdk:Page-Up)
349                (eq? keyval gdk:BackSpace))
350            (dump-page go (1- (page-number go))))
351           ((or (eq? keyval gdk:Page-Down)
352                (eq? keyval gdk:space))
353            (dump-page go (1+ (page-number go)))))
354     #f))
355
356 (define (new-canvas go)
357   (set! (canvas go) (make <gnome-canvas>))
358   (set-size-request (canvas go) (window-width go) (window-height go))
359   (set-scroll-region (canvas go) 0 0 (canvas-width go) (canvas-height go))
360   (set-pixels-per-unit (canvas go) (pixels-per-unit go))
361   (make <gnome-canvas-rect>
362     #:parent (root (canvas go))
363     #:x2 (canvas-width go) #:y2 (canvas-height go)
364     #:fill-color "white"))
365
366 (define output-gnome-module #f)
367 (define (get-output-gnome-module go)
368   (if (not output-gnome-module)
369       (let ((m  (resolve-module '(scm output-gnome))))
370         (module-define! m 'canvas-root (lambda () (root (canvas go))))
371         (module-define! m 'output-scale output-scale)
372         (set! output-gnome-module m)))
373   output-gnome-module)
374
375 (define-public (gnome-output-expression go expr)
376   (let* ((m (get-output-gnome-module go))
377          (result (eval expr m)))
378     (cond
379      ((ly:grob? result) (set! (grob go) result))
380      ((is-a? result <gnome-canvas-item>)
381       
382       ;; AAARGH; grobs happen after stencils
383       ;; (connect result 'event (lambda (w e) (item-event go (grob go) w e)))
384       (connect result 'event (lambda (w e) (item-event go w e)))
385       (if (grob go)
386           (hashq-set! (item-grobs go) result (grob go)))
387       (set! (grob go) #f)
388       
389       (if (is-a? result <gnome-canvas-text>)
390           (set! (text-items go) (cons result (text-items go))))))))
391