-
Notifications
You must be signed in to change notification settings - Fork 6
/
sheets.lisp
1099 lines (911 loc) · 45.2 KB
/
sheets.lisp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
;;; -*- Mode: Lisp; Package: CLIM-INTERNALS -*-
;;; (c) copyright 1998,1999,2000 by Michael McDonald ([email protected]),
;;; (c) copyright 2000 by
;;; Iban Hatchondo ([email protected])
;;; Julien Boninfante ([email protected])
;;; Robert Strandh ([email protected])
;;; (c) copyright 2001 by
;;; Arnaud Rouanet ([email protected])
;;; Lionel Salabartan ([email protected])
;;; This library is free software; you can redistribute it and/or
;;; modify it under the terms of the GNU Library General Public
;;; License as published by the Free Software Foundation; either
;;; version 2 of the License, or (at your option) any later version.
;;;
;;; This library is distributed in the hope that it will be useful,
;;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
;;; Library General Public License for more details.
;;;
;;; You should have received a copy of the GNU Library General Public
;;; License along with this library; if not, write to the
;;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
;;; Boston, MA 02111-1307 USA.
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;
;;; The sheet protocol
(in-package :clim-internals)
(defgeneric raise-sheet-internal (sheet parent))
(defgeneric bury-sheet-internal (sheet parent))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;
;;; input protocol
(defgeneric dispatch-event (client event))
(defgeneric queue-event (client event))
(defgeneric schedule-event (client event delay))
(defgeneric handle-event (client event))
(defgeneric event-read (client))
(defgeneric event-read-no-hang (client))
(defgeneric event-peek (client &optional event-type))
(defgeneric event-unread (client event))
(defgeneric event-listen (client))
;(defgeneric sheet-direct-mirror (sheet))
;(defgeneric sheet-mirrored-ancestor (sheet))
;(defgeneric sheet-mirror (sheet))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;
;;; repaint protocol
(defgeneric dispatch-repaint (sheet region))
;(defgeneric queue-repaint (sheet region))
;(defgeneric handle-repaint (sheet region))
;(defgeneric repaint-sheet (sheet region))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;
;;; notification protocol
(defgeneric note-sheet-grafted (sheet))
(defgeneric note-sheet-degrafted (sheet))
(defgeneric note-sheet-adopted (sheet))
(defgeneric note-sheet-disowned (sheet))
(defgeneric note-sheet-enabled (sheet))
(defgeneric note-sheet-disabled (sheet))
(defgeneric note-sheet-region-changed (sheet))
(defgeneric note-sheet-transformation-changed (sheet))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;
;;;; sheet protocol class
(defclass basic-sheet (sheet)
((region :type region
:initarg :region
:initform (make-bounding-rectangle 0 0 100 100)
:accessor sheet-region)
(native-transformation :type (or null transformation)
;:initform nil
:initform +identity-transformation+
:writer %%set-sheet-native-transformation
:reader %%sheet-native-transformation)
(native-region :type (or null region)
:initform nil)
(device-transformation :type (or null transformation)
:initform nil)
(device-region :type (or null region)
:initform nil)
(pointer-cursor :accessor sheet-pointer-cursor
:initarg :pointer-cursor
:initform :default)
(enabled-p :type boolean
:initarg :enabled-p
:initform t
:accessor sheet-enabled-p)))
; Native region is volatile, and is only computed at the first request when it's equal to nil.
; Invalidate-cached-region method sets the native-region to nil.
(defmethod sheet-parent ((sheet basic-sheet))
nil)
(defmethod sheet-children ((sheet basic-sheet))
nil)
(defmethod sheet-adopt-child ((sheet basic-sheet) (child sheet))
(error "~S attempting to adopt ~S" sheet child))
(defmethod sheet-adopt-child :after ((sheet basic-sheet) (child sheet))
(note-sheet-adopted child)
(when (sheet-grafted-p sheet)
(note-sheet-grafted child)))
(define-condition sheet-is-not-child (error) ())
(defmethod sheet-disown-child :before ((sheet basic-sheet) (child sheet) &key (errorp t))
(when (and (not (member child (sheet-children sheet))) errorp)
(error 'sheet-is-not-child)))
(defmethod sheet-disown-child :after ((sheet basic-sheet) (child sheet) &key (errorp t))
(declare (ignore errorp))
(note-sheet-disowned child)
(when (sheet-grafted-p sheet)
(note-sheet-degrafted child)))
(defmethod sheet-siblings ((sheet basic-sheet))
(when (not (sheet-parent sheet))
(error 'sheet-is-not-child))
(remove sheet (sheet-children (sheet-parent sheet))))
(defmethod sheet-enabled-children ((sheet basic-sheet))
(delete-if-not #'sheet-enabled-p (copy-list (sheet-children sheet))))
(defmethod sheet-ancestor-p ((sheet basic-sheet)
(putative-ancestor sheet))
(or (eq sheet putative-ancestor)
(and (sheet-parent sheet)
(sheet-ancestor-p (sheet-parent sheet) putative-ancestor))))
(defmethod raise-sheet ((sheet basic-sheet))
(error 'sheet-is-not-child))
(defmethod bury-sheet ((sheet basic-sheet))
(error 'sheet-is-not-child))
(define-condition sheet-ordering-underspecified (error) ())
(defmethod reorder-sheets ((sheet basic-sheet) new-ordering)
(when (set-difference (sheet-children sheet) new-ordering)
(error 'sheet-ordering-underspecified))
(when (set-difference new-ordering (sheet-children sheet))
(error 'sheet-is-not-child))
(setf (sheet-children sheet) new-ordering)
sheet)
(defmethod sheet-viewable-p ((sheet basic-sheet))
(and (sheet-parent sheet)
(sheet-viewable-p (sheet-parent sheet))
(sheet-enabled-p sheet)))
(defmethod sheet-occluding-sheets ((sheet basic-sheet) (child sheet))
(labels ((fun (l)
(cond ((eq (car l) child) '())
((and (sheet-enabled-p (car l))
(region-intersects-region-p
(sheet-region (car l)) (sheet-region child)))
(cons (car l) (fun (cdr l))))
(t (fun (cdr l))))))
(fun (sheet-children sheet))))
(defmethod map-over-sheets (function (sheet basic-sheet))
(funcall function sheet)
(mapc #'(lambda (child) (map-over-sheets function child))
(sheet-children sheet))
nil)
(defmethod (setf sheet-enabled-p) :after (enabled-p (sheet basic-sheet))
(if enabled-p
(note-sheet-enabled sheet)
(note-sheet-disabled sheet)))
(defmethod sheet-transformation ((sheet basic-sheet))
(error "Attempting to get the TRANSFORMATION of a SHEET that doesn't contain one"))
(defmethod (setf sheet-transformation) (transformation (sheet basic-sheet))
(declare (ignore transformation))
(error "Attempting to set the TRANSFORMATION of a SHEET that doesn't contain one"))
(defmethod move-sheet ((sheet basic-sheet) x y)
(let ((transform (sheet-transformation sheet)))
(multiple-value-bind (old-x old-y)
(transform-position transform 0 0)
(setf (sheet-transformation sheet)
(compose-translation-with-transformation
transform (- x old-x) (- y old-y))))))
(defmethod resize-sheet ((sheet basic-sheet) width height)
(setf (sheet-region sheet)
(make-bounding-rectangle 0 0 width height)))
(defmethod move-and-resize-sheet ((sheet basic-sheet) x y width height)
(move-sheet sheet x y)
(resize-sheet sheet width height))
(defmethod map-sheet-position-to-parent ((sheet basic-sheet) x y)
(declare (ignore x y))
(error "Sheet has no parent"))
(defmethod map-sheet-position-to-child ((sheet basic-sheet) x y)
(declare (ignore x y))
(error "Sheet has no parent"))
(defmethod map-sheet-rectangle*-to-parent ((sheet basic-sheet) x1 y1 x2 y2)
(declare (ignore x1 y1 x2 y2))
(error "Sheet has no parent"))
(defmethod map-sheet-rectangle*-to-child ((sheet basic-sheet) x1 y1 x2 y2)
(declare (ignore x1 y1 x2 y2))
(error "Sheet has no parent"))
(defmethod map-over-sheets-containing-position (function (sheet basic-sheet) x y)
(map-over-sheets #'(lambda (child)
(multiple-value-bind (tx ty) (map-sheet-position-to-child child x y)
(when (region-contains-position-p (sheet-region child) tx ty)
(funcall function child))))
sheet))
(defmethod map-over-sheets-overlapping-region (function (sheet basic-sheet) region)
(map-over-sheets #'(lambda (child)
(when (region-intersects-region-p
region
(transform-region
(if (eq child sheet)
+identity-transformation+
(sheet-transformation child))
(sheet-region child)))
(funcall function child)))
sheet))
(defmethod child-containing-position ((sheet basic-sheet) x y)
(loop for child in (sheet-children sheet)
do (multiple-value-bind (tx ty) (map-sheet-position-to-child child x y)
(if (and (sheet-enabled-p child)
(region-contains-position-p (sheet-region child) tx ty))
(return child)))))
(defmethod children-overlapping-region ((sheet basic-sheet) (region region))
(loop for child in (sheet-children sheet)
if (and (sheet-enabled-p child)
(region-intersects-region-p
region
(transform-region (sheet-transformation child)
(sheet-region child))))
collect child))
(defmethod children-overlapping-rectangle* ((sheet basic-sheet) x1 y1 x2 y2)
(children-overlapping-region sheet (make-rectangle* x1 y1 x2 y2)))
(defmethod sheet-delta-transformation ((sheet basic-sheet) (ancestor (eql nil)))
(cond ((sheet-parent sheet)
(compose-transformations (sheet-transformation sheet)
(sheet-delta-transformation
(sheet-parent sheet) ancestor)))
(t +identity-transformation+)))
(define-condition sheet-is-not-ancestor (error) ())
(defmethod sheet-delta-transformation ((sheet basic-sheet) (ancestor sheet))
(cond ((eq sheet ancestor) +identity-transformation+)
((sheet-parent sheet)
(compose-transformations (sheet-transformation sheet)
(sheet-delta-transformation
(sheet-parent sheet) ancestor)))
(t (error 'sheet-is-not-ancestor))))
(defmethod sheet-allocated-region ((sheet basic-sheet) (child sheet))
(reduce #'region-difference
(mapcar #'(lambda (child)
(transform-region (sheet-transformation child)
(sheet-region child)))
(cons child (sheet-occluding-sheets sheet child)))))
(defmethod sheet-direct-mirror ((sheet basic-sheet))
nil)
(defmethod sheet-mirrored-ancestor ((sheet basic-sheet))
(if (sheet-parent sheet)
(sheet-mirrored-ancestor (sheet-parent sheet))))
(defmethod sheet-mirror ((sheet basic-sheet))
(let ((mirrored-ancestor (sheet-mirrored-ancestor sheet)))
(if mirrored-ancestor
(sheet-direct-mirror mirrored-ancestor))))
(defmethod graft ((sheet basic-sheet))
nil)
(defmethod note-sheet-grafted ((sheet basic-sheet))
(mapc #'note-sheet-grafted (sheet-children sheet)))
(defmethod note-sheet-degrafted ((sheet basic-sheet))
(mapc #'note-sheet-degrafted (sheet-children sheet)))
(defmethod note-sheet-adopted ((sheet basic-sheet))
(declare (ignorable sheet))
nil)
(defmethod note-sheet-disowned ((sheet basic-sheet))
(declare (ignorable sheet))
nil)
(defmethod note-sheet-enabled ((sheet basic-sheet))
(declare (ignorable sheet))
nil)
(defmethod note-sheet-disabled ((sheet basic-sheet))
(declare (ignorable sheet))
nil)
(defmethod note-sheet-region-changed ((sheet basic-sheet))
nil) ;have to change
(defmethod note-sheet-transformation-changed ((sheet basic-sheet))
nil)
(defmethod sheet-native-transformation ((sheet basic-sheet))
(with-slots (native-transformation) sheet
(unless native-transformation
(setf native-transformation
(let ((parent (sheet-parent sheet)))
(if parent
(compose-transformations
(sheet-native-transformation parent)
(sheet-transformation sheet))
+identity-transformation+))))
native-transformation))
(defmethod sheet-native-region ((sheet basic-sheet))
(with-slots (native-region) sheet
(unless native-region
(let ((this-native-region (transform-region
(sheet-native-transformation sheet)
(sheet-region sheet)))
(parent (sheet-parent sheet)))
(setf native-region (if parent
(region-intersection this-native-region
(sheet-native-region
parent))
this-native-region))))
native-region))
(defmethod sheet-device-transformation ((sheet basic-sheet))
(with-slots (device-transformation) sheet
(unless device-transformation
(setf device-transformation
(let ((medium (sheet-medium sheet)))
(compose-transformations
(sheet-native-transformation sheet)
(if medium
(medium-transformation medium)
+identity-transformation+)))))
device-transformation))
(defmethod sheet-device-region ((sheet basic-sheet))
(with-slots (device-region) sheet
(unless device-region
(setf device-region
(let ((medium (sheet-medium sheet)))
(region-intersection
(sheet-native-region sheet)
(if medium
(transform-region
(sheet-device-transformation sheet)
(medium-clipping-region medium))
+everywhere+)))))
device-region))
(defmethod invalidate-cached-transformations ((sheet basic-sheet))
(with-slots (native-transformation device-transformation) sheet
(setf native-transformation nil
device-transformation nil))
(loop for child in (sheet-children sheet)
do (invalidate-cached-transformations child)))
(defmethod invalidate-cached-regions ((sheet basic-sheet))
(with-slots (native-region device-region) sheet
(setf native-region nil
device-region nil))
(loop for child in (sheet-children sheet)
do (invalidate-cached-regions child)))
(defmethod (setf sheet-transformation) :after (transformation (sheet basic-sheet))
(declare (ignore transformation))
(note-sheet-transformation-changed sheet)
(invalidate-cached-transformations sheet)
(invalidate-cached-regions sheet))
(defmethod (setf sheet-region) :after (region (sheet basic-sheet))
(declare (ignore region))
(note-sheet-region-changed sheet)
(invalidate-cached-regions sheet))
(defmethod (setf sheet-pointer-cursor) :after (cursor (sheet basic-sheet))
(set-sheet-pointer-cursor (port sheet) sheet cursor))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;
;;; sheet parent mixin
(defclass sheet-parent-mixin ()
((parent :initform nil :accessor sheet-parent)))
(define-condition sheet-already-has-parent (error) ())
(define-condition sheet-is-ancestor (error) ())
(defmethod sheet-adopt-child :before (sheet (child sheet-parent-mixin))
(when (sheet-parent child) (error 'sheet-already-has-parent))
(when (sheet-ancestor-p sheet child) (error 'sheet-is-ancestor)))
(defmethod sheet-adopt-child :after (sheet (child sheet-parent-mixin))
(setf (sheet-parent child) sheet))
(defmethod sheet-disown-child :after (sheet
(child sheet-parent-mixin)
&key (errorp t))
(declare (ignore sheet errorp))
(setf (sheet-parent child) nil))
(defmethod raise-sheet ((sheet sheet-parent-mixin))
(when (sheet-parent sheet)
(raise-sheet-internal sheet (sheet-parent sheet)))
(when (sheet-direct-mirror sheet)
(raise-mirror (port sheet) sheet)))
(defmethod bury-sheet ((sheet sheet-parent-mixin))
(when (sheet-parent sheet)
(bury-sheet-internal sheet (sheet-parent sheet)))
(when (sheet-direct-mirror sheet)
(bury-mirror (port sheet) sheet)))
(defmethod graft ((sheet sheet-parent-mixin))
(and (sheet-parent sheet) (graft (sheet-parent sheet))))
(defmethod (setf sheet-transformation) :after (newvalue (sheet sheet-parent-mixin))
(declare (ignore newvalue))
#+nil(note-sheet-transformation-changed sheet))
(defmethod map-sheet-position-to-parent ((sheet sheet-parent-mixin) x y)
(transform-position (sheet-transformation sheet) x y))
(defmethod map-sheet-position-to-child ((sheet sheet-parent-mixin) x y)
(untransform-position (sheet-transformation sheet) x y))
(defmethod map-sheet-rectangle*-to-parent ((sheet sheet-parent-mixin) x1 y1 x2 y2)
(transform-rectangle* (sheet-transformation sheet) x1 y1 x2 y2))
(defmethod map-sheet-rectangle*-to-child ((sheet sheet-parent-mixin) x1 y1 x2 y2)
(untransform-rectangle* (sheet-transformation sheet) x1 y1 x2 y2))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;
;;; sheet leaf mixin
(defclass sheet-leaf-mixin () ())
(defmethod sheet-children ((sheet sheet-leaf-mixin))
nil)
(defmethod sheet-adopt-child ((sheet sheet-leaf-mixin) (child sheet))
(error "Leaf sheet attempting to adopt a child"))
(defmethod sheet-disown-child ((sheet sheet-leaf-mixin) (child sheet) &key (errorp t))
(declare (ignorable errorp))
(error "Leaf sheet attempting to disown a child"))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;
;;; sheet single child mixin
(defclass sheet-single-child-mixin ()
((child :initform nil :accessor sheet-child)))
(defmethod sheet-children ((sheet sheet-single-child-mixin))
(and (sheet-child sheet) (list (sheet-child sheet))))
(define-condition sheet-supports-only-one-child (error) ())
(defmethod sheet-adopt-child :before ((sheet sheet-single-child-mixin)
(child sheet-parent-mixin))
(when (sheet-child sheet)
(error 'sheet-supports-only-one-child)))
(defmethod sheet-adopt-child ((sheet sheet-single-child-mixin)
(child sheet-parent-mixin))
(setf (sheet-child sheet) child))
(defmethod sheet-disown-child ((sheet sheet-single-child-mixin)
(child sheet-parent-mixin)
&key (errorp t))
(declare (ignore errorp))
(setf (sheet-child sheet) nil))
(defmethod raise-sheet-internal (sheet (parent sheet-single-child-mixin))
(declare (ignorable sheet parent))
(values))
(defmethod bury-sheet-internal (sheet (parent sheet-single-child-mixin))
(declare (ignorable sheet parent))
(values))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;
;;; sheet multiple child mixin
(defclass sheet-multiple-child-mixin ()
((children :initform nil :initarg :children :accessor sheet-children)))
(defmethod sheet-adopt-child ((sheet sheet-multiple-child-mixin)
(child sheet-parent-mixin))
(push child (sheet-children sheet)))
(defmethod sheet-disown-child ((sheet sheet-multiple-child-mixin)
(child sheet-parent-mixin)
&key (errorp t))
(declare (ignore errorp))
(setf (sheet-children sheet) (delete child (sheet-children sheet))))
(defmethod raise-sheet-internal (sheet (parent sheet-multiple-child-mixin))
(setf (sheet-children parent)
(cons sheet (delete sheet (sheet-children parent)))))
(defmethod bury-sheet-internal (sheet (parent sheet-multiple-child-mixin))
(setf (sheet-children parent)
(append (delete sheet (sheet-children parent)) (list sheet))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;
;;; sheet geometry classes
(defclass sheet-identity-transformation-mixin ()
())
(defmethod sheet-transformation ((sheet sheet-identity-transformation-mixin))
+identity-transformation+)
(defclass sheet-transformation-mixin ()
((transformation :initform +identity-transformation+
:initarg :transformation
:accessor sheet-transformation)))
(defclass sheet-translation-transformation-mixin (sheet-transformation-mixin)
())
(defmethod (setf sheet-transformation) :before ((transformation transformation)
(sheet sheet-translation-transformation-mixin))
(if (not (translation-transformation-p transformation))
(error "Attempting to set the SHEET-TRANSFORMATION of a SHEET-TRANSLATION-TRANSFORMATION-MIXIN to a non translation transformation")))
(defclass sheet-y-inverting-transformation-mixin (sheet-transformation-mixin)
()
(:default-initargs :transformation (make-transformation 1 0 0 -1 0 0)))
(defmethod (setf sheet-transformation) :before ((transformation transformation)
(sheet sheet-y-inverting-transformation-mixin))
(if (not (y-inverting-transformation-p transformation))
(error "Attempting to set the SHEET-TRANSFORMATION of a SHEET-Y-INVERTING-TRANSFORMATION-MIXIN to a non Y inverting transformation")))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;
;;; mirrored sheet
;; We assume the following limitations of the host window systems:
;;
;; mirror transformations:
;; . can only be translations
;; . are limited to 16-bit signed integer deltas
;;
;; mirror regions:
;; . can only be axis-aligend rectangles
;; . min-x = min-y = 0
;; . max-x, max-y < 2^16
;;
;; These are the limitations of the X Window System.
;;
(defclass mirrored-sheet-mixin ()
((port :initform nil :initarg :port :accessor port)
(mirror-transformation
:documentation "Our idea of the current mirror transformation. Might not
be correct if a foreign application changes our mirror's geometry."
:initform +identity-transformation+
:accessor %sheet-mirror-transformation)
(mirror-region
:documentation "Our idea of the current mirror region. Might not be
correct if a foreign application changes our mirror's geometry. Also note
that this might be different from the sheet's native region."
:initform nil
:accessor %sheet-mirror-region)))
(defmethod sheet-direct-mirror ((sheet mirrored-sheet-mixin))
(port-lookup-mirror (port sheet) sheet))
(defmethod (setf sheet-direct-mirror) (mirror (sheet mirrored-sheet-mixin))
(port-register-mirror (port sheet) sheet mirror))
(defmethod sheet-mirrored-ancestor ((sheet mirrored-sheet-mixin))
sheet)
(defmethod sheet-mirror ((sheet mirrored-sheet-mixin))
(sheet-direct-mirror sheet))
(defmethod note-sheet-grafted :before ((sheet mirrored-sheet-mixin))
(unless (port sheet)
(error "~S called on sheet ~S, which has no port?!" 'note-sheet-grafted sheet))
(realize-mirror (port sheet) sheet))
(defmethod note-sheet-degrafted :after ((sheet mirrored-sheet-mixin))
(destroy-mirror (port sheet) sheet))
(defmethod (setf sheet-region) :after (region (sheet mirrored-sheet-mixin))
(declare (ignore region))
#+nil(port-set-sheet-region (port sheet) sheet region)
(update-mirror-geometry sheet)
)
(defmethod note-sheet-transformation-changed ((sheet mirrored-sheet-mixin))
(update-mirror-geometry sheet))
(defmethod sheet-native-region ((sheet mirrored-sheet-mixin))
(with-slots (native-region) sheet
(unless native-region
(let ((this-region (transform-region (sheet-native-transformation sheet)
(sheet-region sheet)))
(parent (sheet-parent sheet)))
(setf native-region
(if parent
(region-intersection this-region
(transform-region
(invert-transformation
(%sheet-mirror-transformation sheet))
(sheet-native-region parent)))
this-region))))
native-region))
(defmethod (setf sheet-enabled-p) :after (new-value (sheet mirrored-sheet-mixin))
(when (sheet-direct-mirror sheet) ;only do this if the sheet actually has a mirror
(if new-value
(port-enable-sheet (port sheet) sheet)
(port-disable-sheet (port sheet) sheet))))
;;; Reflecting a Sheet's Geometry to the Mirror
(defmethod sheet-mirror-region ((sheet mirrored-sheet-mixin))
(cond
;; for grafts or top-level-sheet's always read the mirror region from
;; the server, since it is not under our control.
((or (null (sheet-parent sheet))
(null (sheet-parent (sheet-parent sheet))))
(make-rectangle* 0 0 #x10000 #x10000)
#+nil
(make-rectangle* 0 0
(port-mirror-width (port sheet) sheet)
(port-mirror-height (port sheet) sheet)))
(t
;; For other sheets just use the calculated value, saves a round trip.
(or (%sheet-mirror-region sheet)
;; XXX what to do if the sheet has no idea about its region?
;; XXX can we consider calling sheet-mirror-region then an error?
(make-rectangle* 0 0 #x10000 #x10000) ))))
(defmethod sheet-native-transformation ((sheet mirrored-sheet-mixin))
;; XXX hm...
(with-slots (native-transformation) sheet
(unless native-transformation
(setf native-transformation
(compose-transformations
(invert-transformation
(%sheet-mirror-transformation sheet))
(compose-transformations
(sheet-native-transformation (sheet-parent sheet))
(sheet-transformation sheet)))))
native-transformation))
(defmethod invalidate-cached-transformations ((sheet mirrored-sheet-mixin))
(with-slots (native-transformation device-transformation) sheet
(setf ;; native-transformation nil XXX hm...
device-transformation nil))
(loop for child in (sheet-children sheet)
do (invalidate-cached-transformations child)))
(defmethod effective-mirror-region ((sheet mirrored-sheet-mixin))
;; XXX is this really needed, can't we deduce this information more easily?
(let* ((parent (sheet-parent sheet))
(ancestor (and parent (sheet-mirrored-ancestor parent))))
(if ancestor
(region-intersection (sheet-mirror-region sheet)
(untransform-region (%sheet-mirror-transformation sheet)
(effective-mirror-region ancestor)))
(sheet-mirror-region sheet))))
;;; Internal interface for enabling/disabling motion hints
(defgeneric sheet-motion-hints (sheet)
(:documentation "Returns t if motion hints are enabled for this sheet"))
(defmethod sheet-motion-hints ((sheet mirrored-sheet-mixin))
(when (sheet-direct-mirror sheet)
(port-motion-hints (port sheet) sheet)))
(defgeneric (setf sheet-motion-hints) (val sheet))
(defmethod (setf sheet-motion-hints) (val (sheet mirrored-sheet-mixin))
(when (sheet-direct-mirror sheet)
(setf (port-motion-hints (port sheet) sheet) val)))
;;;; Coordinate Swizzling
;; This implements what I call "coordinate swizzling", the illusion that
;; sheets can be arbitrary large. The key idea here is that there is a
;; certain kind freedom in choosing the native transformation. A little
;; diagram to illustrate the involved transformations:
;;
;; NT NT = native transformation
;; sheet ----------------> mirror PNT = parent's NT
;; | | MT = mirror transformation
;; | | T = sheet transformation
;; | |
;; T | | MT
;; | |
;; | |
;; | |
;; v PNT v
;; parent ----------------> parent
;; mirror
;;
;; To setup both the mirror transformation (MR) and the mirror region (MR),
;; we start with the mirror region. The window systems limitations are here:
;; We can only have a certain size and its upper-left corner must be at the
;; origin.
;; Now the parent already has a mirror region (PMR) assigned, which obeys to
;; the very same size restrictions. Since every part of MR outside of (PMR o
;; MT^1) is not visible, the first idea is to just clip it by the visible
;; part:
;; MR_1 = intersection (SR o NT, PMR o MT^-1) [mirror space]
;; Since both NT and MT^-1 are not yet known let us reformulate that region
;; in the parent mirror space:
;; MR_2 = MR_1 o MT [parent mirror space]
;; = intersection (SR o NT, PMR o MT^-1) o MT
;; = intersection (SR o NT o MT, PMR o MT^-1 o MT)
;; = intersection (SR o (T o PNT o MT^-1) o MT, PMR)
;; = intersection (SR o T o PNT, PMR)
;; MR_2 now is a good candidate for a mirror region. Unfortunately it is
;; still in parent mirror space, so we transform it back, yielding MR_3:
;; MR_3 = MR_2 o MT^-1
;; = intersection (SR o T o PNT, PMR) o MT^-1
;; Here the only unknown is the mirror transformation MT, we can still
;; choose any as long as the window system limitations are met for both MR
;; and MT.
;; 1. MT should be a translation, whose delta x and y components are within
;; limits.
;; 2. The size limitation of MR is already met, since MR_3's size is no
;; larger than PMR's size (which mets the limitations). [Remember that MT
;; was defined to be some translation].
;; 3. MR_3's upper left corner should also be at the origin which nicely
;; defines MT^-1: Just choose this upper left corner coordinates as MT's x
;; and y deltas.
;; So we can meet all criteria. The NT can easily be set up by the identity:
;; NT = T o PNT o MT^-1
;;; Notes
;; . when the native transformation changes, we need to:
;; a. Redraw the mirror's contents since the mapping from the sheet space
;; to the mirror space (that is the native transformation) just changed.
;; Translational changes in the native transformation can be catered by
;; blittering, but then have a nice synchronization problem: Suppose
;; a repaint event is underway as we blitter from some region R_1 to
;; region R_2. Say the repaint event's region intersects with R_1. In
;; this case we just blittered pixels which were considered dirty into
;; R_2. Redrawing R_1 now does not repair the defect, since R_2 now also
;; contains dirty pixels. => oops, redraw error.
;;
;; b. Since the above above calculation took the parent's native
;; transformation into account, (and even the naively wanted mirror
;; region depends on the parent's native transformation), we need to
;; redo mirror geometry calculation for any child.
;;
;; c. I imagine more aggressive output records which remember the actual
;; octets which need to be send to the X server. These would contain
;; mirror coordinates and will need to be recalculated, when the native
;; transformation changes.
;; => Changing the native transformation can be expensive, so we want a way
;; to minimize changes to the native transformation.
;;
;; What did we do? We clipped the wanted mirror region, SR o NT, inside the
;; parent's mirror region to meet the window system limitations. We can make
;; this clip region larger as long as we still come up with an mirror
;; region, which meets the limits.
(defun update-mirror-geometry (sheet &key)
"This function reflects the current sheet region and sheet transformation
to the mirror. It also sets up the native transformation. This function is
supposed to be called whenever one of the following happens:
- the sheet's transformation changed
- the sheet's region changed
- the parent's native transformation changed
- the parent's transformation changed
- the parent's mirror region changed
Also if the sheet's native transformation changes the mirror's contents need
to be redrawn, which is achieved by calling PORT-DIRTY-MIRROR-REGION.
Since changing the sheet's native transformation might thus be expensive,
this function tries to minimize changes to it. (although it does not try
very hard)."
(let ((old-native-transformation (%%sheet-native-transformation sheet)))
(cond ((null (sheet-parent sheet))
;; Ugh, we have no parent, this must be the graft, we cannot resize it can we?
nil)
;;
;; Otherwise, the native transformation has to changed or needs to be computed initially
;;
(t
(let* ((parent (sheet-parent sheet))
(sheet-region-in-native-parent
;; this now is the wanted sheet mirror region
(transform-region (sheet-native-transformation parent)
(transform-region (sheet-transformation sheet)
(sheet-region sheet)))))
(when (region-equal sheet-region-in-native-parent +nowhere+)
;; hmm
(setf (%sheet-mirror-transformation sheet) (make-translation-transformation -5 -5))
(setf (%sheet-mirror-region sheet) (make-rectangle* 0 0 1 1))
(when (sheet-direct-mirror sheet)
(port-set-mirror-region (port sheet) (sheet-direct-mirror sheet)
(%sheet-mirror-region sheet))
(port-set-mirror-transformation (port sheet)
(sheet-direct-mirror sheet)
(%sheet-mirror-transformation sheet)))
(return-from update-mirror-geometry))
;; mx1 .. my2 are is now the wanted mirror region in the parent
;; coordinate system.
(with-bounding-rectangle* (mx1 my1 mx2 my2) sheet-region-in-native-parent
(let (;; pw, ph is the width/height of the parent
(pw (bounding-rectangle-width (sheet-mirror-region parent)))
(ph (bounding-rectangle-height (sheet-mirror-region parent))))
(labels ((choose (MT)
;; -> fits-p mirror-region
(multiple-value-bind (x1 y1) (transform-position MT 0 0)
(let ((x2 (if (<= mx2 pw)
mx2
(floor (+ pw (min mx2 (+ #x8000 x1) #x8000)) 2)))
(y2 (if (<= my2 ph)
my2
(floor (+ ph (min my2 (+ #x8000 y1) #x8000)) 2))))
(when (and (< (- x2 x1) #x8000)
(or (<= (max (- pw #x8000) mx1) x1 0) (coordinate= x1 mx1))
(< (- y2 y1) #x8000)
(or (<= (max (- pw #x8000) my1) y1 0) (coordinate= y1 my1))
(> (round (- x2 x1)) 0)
(> (round (- y2 y1)) 0))
(values t (make-rectangle* 0 0 (round (- x2 x1)) (round (- y2 y1)))))))))
;;
;; Try reusing the native transformation:
;;
(when old-native-transformation
(let ((MT (compose-transformations
(compose-transformations
(sheet-native-transformation (sheet-parent sheet))
(sheet-transformation sheet))
(invert-transformation old-native-transformation))))
(multiple-value-bind (fits-p MR) (choose MT)
(when fits-p
(setf (%sheet-mirror-region sheet) MR)
(setf (%sheet-mirror-transformation sheet) MT)
(when (sheet-direct-mirror sheet)
(let ((port (port sheet))
(mirror (sheet-direct-mirror sheet)))
(port-set-mirror-region port mirror MR)
(port-set-mirror-transformation port mirror MT)))
(return-from update-mirror-geometry nil) ))))
;;
;; Try reusing the mirror transformation:
;;
'
(let ((MT (%sheet-mirror-transformation sheet)))
(when MT
(multiple-value-bind (fits-p MR) (choose MT)
(when fits-p
(let ((native-transformation
;; NT = T o PNT o -MT
(compose-transformations
(invert-transformation MT)
(compose-transformations
(sheet-native-transformation (sheet-parent sheet))
(sheet-transformation sheet)))))
;; finally reflect the change to the host window system
(setf (%sheet-mirror-region sheet) MR)
(setf (%sheet-mirror-transformation sheet) MT)
(when (sheet-direct-mirror sheet)
(let ((port (port sheet))
(mirror (sheet-direct-mirror sheet)))
(port-set-mirror-region port mirror MR)
(port-set-mirror-transformation port mirror MT)))
;; update the native transformation if neccessary.
(unless (and old-native-transformation
(transformation-equal native-transformation old-native-transformation))
(invalidate-cached-transformations sheet)
(%%set-sheet-native-transformation native-transformation sheet)
(when old-native-transformation
(care-for-new-native-transformation
sheet old-native-transformation native-transformation))))
(return-from update-mirror-geometry nil)
))))
;; Otherwise just choose
;; Conditions to be met:
;; x2 < #x8000 + x1
;; x1 in [max(pw - #x8000, mx1), 0] u {mx1}
;; x2 in [pw, min (#x8000, mx2)] u {mx2}
;;
;; It can still happend, that we cannot meet the
;; window system limitations => the sheet is
;; unvisible.
(let* ((x1 (if (>= mx1 0) (round mx1) (floor (max (- pw #x8000) mx1) 2)))
(y1 (if (>= my1 0) (round my1) (floor (max (- ph #x8000) my1) 2)))
(x2 (if (<= mx2 pw) mx2 (floor (+ pw (min mx2 (- #x8000 x1))) 2)))
(y2 (if (<= my2 ph) my2 (floor (+ ph (min my2 (- #x8000 y1))) 2)))
(MT (make-translation-transformation x1 y1))
(MR (make-rectangle* 0 0 (round (- x2 x1)) (round (- y2 y1))))
(native-transformation
;; NT = T o PNT o -MT
(compose-transformations
(invert-transformation MT)
(compose-transformations
(sheet-native-transformation (sheet-parent sheet))
(sheet-transformation sheet))))
(old-native-transformation
(%%sheet-native-transformation sheet)))
(cond ((and (> (round (- x2 x1)) 0)
(> (round (- y2 y1)) 0))
;; finally reflect the change to the host window system
(setf (%sheet-mirror-region sheet) MR)
(setf (%sheet-mirror-transformation sheet) MT)
(when (sheet-direct-mirror sheet)
(let ((port (port sheet))
(mirror (sheet-direct-mirror sheet)))
(port-set-mirror-region port mirror MR)
(port-set-mirror-transformation port mirror MT)))
;; update the native transformation if neccessary.
(unless (and old-native-transformation
(transformation-equal native-transformation old-native-transformation))
(invalidate-cached-transformations sheet)
(%%set-sheet-native-transformation native-transformation sheet)
(when old-native-transformation
(care-for-new-native-transformation
sheet old-native-transformation native-transformation))))
(t
(setf (%sheet-mirror-transformation sheet) (make-translation-transformation -5 -5))