Module: duim-sheets-internals Synopsis: DUIM sheets Author: Scott McKay, Andy Armstrong Copyright: Original Code is Copyright (c) 1995-2004 Functional Objects, Inc. All rights reserved. License: Functional Objects Library Public License Version 1.0 Dual-license: GNU Lesser General Public License Warranty: Distributed WITHOUT WARRANTY OF ANY KIND /// Mirrored sheets and mirrors define protocol <> () // Mirroring a sheet getter sheet-mirror (sheet :: ) => (mirror); getter mirror-sheet (mirror) => (sheet :: ); setter mirror-sheet-setter (sheet :: , mirror) => (sheet :: ); // Sheet native and device coordinates // The "ground" coordinate space for a sheet, almost always $identity-transform getter sheet-native-transform (sheet :: ) => (transform :: ); setter sheet-native-transform-setter (transform :: , sheet :: ) => (transform :: ); // The clipping region for a sheet in its "ground" coordinate space // usually a box of (0,0, width,height). There's no setter for this // since that gets done by setting the mirror itself. getter sheet-native-region (sheet :: ) => (region :: ); // Returns the edges of the for the sheet's native region // in its "ground" coordinate space getter sheet-native-edges (sheet :: ) => (left :: , top :: , right :: , bottom :: ); // Transform from sheet's coordinate space to the coordinate space of its // first mirrored ancestor. For a directly mirrored sheet, this will // almost always be $identity-transform. getter sheet-device-transform (sheet :: ) => (transform :: ); // The sheet's clipping region expressed in the coordinate space of its // first mirrored ancestor. getter sheet-device-region (sheet :: ) => (region :: ); getter sheet-device-edges (sheet :: ) => (left :: , top :: , right :: , bottom :: ); // Mirror-based repainting // In the X model, a repaint event comes in for every mirrored sheet. // In the Windows model, a repaint event comes in for every mirrored sheet. // In the Mac model, a repaint event comes in only for the top level sheet. // This function should return #t for any sheet that gets automagically // repainted by the native Window system, and should return #f for every // sheet for which 'repaint-sheet' should arrange to call 'handle-repaint' function port-handles-repaint? (port :: , sheet :: ) => (true? :: ); end protocol <>; define protocol <> () // Creating and destroying mirrors function make-mirror (port :: , sheet :: ) => (mirror :: ); function do-make-mirror (port :: , sheet :: ) => (mirror :: ); function destroy-mirror (port :: , sheet :: , mirror :: ) => (); function map-mirror (port :: , sheet :: , mirror :: ) => (); function unmap-mirror (port :: , sheet :: , mirror :: ) => (); function raise-mirror (port :: , sheet :: , mirror :: , #key activate?) => (); function lower-mirror (port :: , sheet :: , mirror :: ) => (); function mirror-visible? (port :: , sheet :: , mirror :: ) => (true? :: ); function mirror-origin (port :: , sheet :: ) => (origin); // Mirror geometry // Returns the region's mirror in the mirror coordinate space function mirror-region (port :: , sheet :: , mirror :: ) => (region :: ); // Returns the coordinates of sheet's mirror's bounding box in the coordinates // of the parent of the mirror, that is (left,top, right,bottom) function mirror-edges (port :: , sheet :: , mirror :: ) => (left :: , top :: , right :: , bottom :: ); // Set the edges of the sheet mirror in the coordinate space of the parent mirror. //--- Should we add 'set-mirror-region' for consistency (and OpenDoc support)? function set-mirror-edges (port :: , sheet :: , mirror :: , left :: , top :: , right :: , bottom :: ) => (); // Sheet <-> mirror synchronization function note-mirror-geometry-changed (port :: , sheet :: , region :: ) => (); function update-mirror-region (port :: , sheet :: , mirror) => (); function update-mirror-transform (port :: , sheet :: , mirror) => (); end protocol <>; /// Mirrored sheets // A sealed protocol... define generic sheet-resource-id (sheet :: ) => (resource-id); define open abstract class () sealed constant slot sheet-resource-id = #f, init-keyword: resource-id:; end class ; define sealed method sheet-resource-id (sheet :: ) => (resource-id) #f end method sheet-resource-id; // Another sealed protocol... define generic sheet-direct-mirror (sheet :: ) => (mirror); define generic sheet-direct-mirror-setter (mirror, sheet :: ) => (mirror); // This must come after in any CPL. See the // comments on 's class definition for why. define open abstract class () sealed slot sheet-direct-mirror = #f; end class ; define sealed method sheet-direct-mirror (sheet :: ) => (mirror) #f end method sheet-direct-mirror; define method do-destroy-sheet (sheet :: ) => () let _port = port(sheet); let mirror = sheet-direct-mirror(sheet); when (_port & mirror) destroy-mirror(_port, sheet, mirror) end; next-method() // off to end method do-destroy-sheet; // This property allows certain mirrored sheets to decline to have // mirrors parented into them define method sheet-mirror-accepts-children? (sheet :: ) => (accepts-children? :: ) logand(sheet-flags(sheet), %mirror_accepts_children) = %mirror_accepts_children end method sheet-mirror-accepts-children?; define method sheet-mirror-accepts-children?-setter (accepts-children? :: , sheet :: ) => (accepts-children? :: ) sheet-flags(sheet) := logior(logand(sheet-flags(sheet), lognot(%mirror_accepts_children)), if (accepts-children?) %mirror_accepts_children else 0 end); accepts-children? end method sheet-mirror-accepts-children?-setter; /// Native transformation, only meaningful on mirrored sheets define method sheet-native-transform (sheet :: ) => (transform :: ) $identity-transform end method sheet-native-transform; define method sheet-native-transform-setter (transform :: , sheet :: ) => (transform :: ) // The default implementation only supports the identity transform, // but back-ends can override this assert(identity-transform?(transform), "The native transform %= is not the identity transform", transform); transform end method sheet-native-transform-setter; /// Device transformation define method sheet-device-transform (sheet :: ) => (transform :: ) sheet-device-parent-transform(sheet) end method sheet-device-transform; define method sheet-device-transform (sheet :: ) => (transform :: ) sheet-cached-device-transform(sheet) | (sheet-cached-device-transform(sheet) := next-method()) end method sheet-device-transform; define method sheet-device-transform (sheet :: ) => (transform :: ) // If this sheet is directly mirrored, use the mirror's native transform if (sheet-direct-mirror(sheet)) sheet-native-transform(sheet) else next-method() end end method sheet-device-transform; define method sheet-device-parent-transform (sheet :: ) => (transform :: ) local method parent-transform (sheet :: ) => (transform :: ) let parent = sheet-parent(sheet); // For mirrored sheets that don't accept children, we just // go up the tree to find the first mirror that will if (sheet-direct-mirror(parent) & ~sheet-mirror-accepts-children?(parent)) compose-transforms(sheet-transform(parent), parent-transform(parent)) else sheet-device-transform(parent) end end method; compose-transforms(sheet-transform(sheet), parent-transform(sheet)) end method sheet-device-parent-transform; /// Native region, only meaningful on mirrored sheets // This conses, but you don't much ask for the native region // of a mirrored sheet... define method sheet-native-region (sheet :: ) => (region :: ) transform-region(sheet-native-transform(sheet), sheet-region(sheet)) end method sheet-native-region; /// Device region define method sheet-device-region (sheet :: ) => (region :: ) let parent = sheet-device-parent(sheet); region-intersection (transform-region(sheet-device-transform(sheet), sheet-region(sheet)), sheet-device-region(parent)) end method sheet-device-region; //--- This assumes that sheet siblings do not overlap //--- This also assumes that sheet regions are implemented by define method sheet-device-region (sheet :: ) => (region :: ) let region = sheet-cached-device-region(sheet); // We decache the device region by setting this slot to #f... case region == $nowhere // it can happen | (region & ~box-invalidated?(region)) => region; region => // Be very careful not to cons. It's worth all this hair // because common operations such as scrolling invalidate // this cache all the time. let (left, top, right, bottom) = box-edges(sheet); let (pleft, ptop, pright, pbottom) = begin let parent = sheet-device-parent(sheet); let region = sheet-device-region(parent); if (region == $nowhere) values(0, 0, 0, 0) else box-edges(region) end end; let (valid?, left, top, right, bottom) = begin let (sleft, stop, sright, sbottom) = transform-box(sheet-device-transform(sheet), left, top, right, bottom); ltrb-intersects-ltrb?(sleft, stop, sright, sbottom, pleft, ptop, pright, pbottom) end; if (valid?) sheet-cached-device-region(sheet) // might cons a new region... := set-box-edges(region, left, top, right, bottom) else sheet-cached-device-region(sheet) := $nowhere end; otherwise => sheet-cached-device-region(sheet) := next-method(); end end method sheet-device-region; define method sheet-device-region (sheet :: ) => (region :: ) sheet-native-region(sheet) end method sheet-device-region; /// Native edges // Returns the edges of the for the sheet's native region define method sheet-native-edges (sheet :: ) => (left :: , top :: , right :: , bottom :: ) let (left, top, right, bottom) = box-edges(sheet); transform-box(sheet-native-transform(sheet), left, top, right, bottom) end method sheet-native-edges; /// Mirror region stuff //--- Note: this doesn't behave like other 'sheet-XXX-edges' functions; //--- it returns the edges as seen from the device parent, not as seen //--- from the sheet itself define method sheet-device-edges (sheet :: ) => (left :: , top :: , right :: , bottom :: ) let (left, top, right, bottom) = box-edges(sheet); transform-box(sheet-device-transform(sheet), left, top, right, bottom) end method sheet-device-edges; define method sheet-device-edges (sheet :: ) => (left :: , top :: , right :: , bottom :: ) let (left, top, right, bottom) = box-edges(sheet); transform-box(sheet-device-parent-transform(sheet), left, top, right, bottom) end method sheet-device-edges; // Returns the region for the sheet mirror. // Default method makes a box from the result of (new) 'mirror-edges' define method mirror-region (_port :: , sheet :: , mirror :: ) => (region :: ) let (left, top, right, bottom) = mirror-edges(_port, sheet, mirror); make-bounding-box(left, top, right, bottom) end method mirror-region; define method mirror-origin (_port :: , sheet :: ) => (origin) #"north-west" end method mirror-origin; /// Finding and making mirrors define method sheet-mirror (sheet :: ) => (mirror :: false-or()) sheet-direct-mirror(sheet) | begin let parent = sheet-device-parent(sheet, error?: #f); parent & sheet-direct-mirror(parent) end end method sheet-mirror; // Returns the ancestor whose mirror will be the parent for this sheet's mirror // This is for things like Windows group boxes, where the children's mirrors are // siblings of the group box's mirror (!) define function sheet-device-parent (sheet :: , #key error? = #t) => (sheet :: false-or()) let parent = sheet-parent(sheet); case ~parent => error? & error("The sheet %= has no mirrored ancestors", sheet); sheet-mirror-accepts-children?(parent) => parent; otherwise => sheet-device-parent(parent, error?: error?) end end function sheet-device-parent; define method make-mirror (_port :: , sheet :: ) => (mirror :: ) let mirror = sheet-direct-mirror(sheet) | (sheet-direct-mirror(sheet) := do-make-mirror(_port, sheet)); // You might think we should update the mirror regions and transforms // here, but that will get taken care of by layout and space allocation mirror end method make-mirror; /// Mirror notifications define method note-sheet-mapped (sheet :: ) => () next-method(); // almost certainly the method on let mirror = sheet-direct-mirror(sheet); assert(mirror, "The sheet %= must be mirrored to map it", sheet); map-mirror(port(sheet), sheet, mirror) end method note-sheet-mapped; define method note-sheet-unmapped (sheet :: ) => () next-method(); // almost certainly the method on let mirror = sheet-direct-mirror(sheet); assert(mirror, "The sheet %= must be mirrored to unmap it", sheet); unmap-mirror(port(sheet), sheet, mirror) end method note-sheet-unmapped; define method do-note-sheet-attached (sheet :: ) => () next-method(); make-mirror(port(sheet), sheet) end method do-note-sheet-attached; define method do-note-sheet-detached (sheet :: ) => () next-method(); let _port = port(sheet); let mirror = sheet-direct-mirror(sheet); when (_port & mirror) destroy-mirror(_port, sheet, mirror) end end method do-note-sheet-detached; // Mirrored sheets will take care of this themselves define method repaint-within-parent (sheet :: , #key clear? = #t) => () ignore(clear?); #f end method repaint-within-parent; /// Mirror geometry notifications define method note-region-changed (sheet :: ) => () next-method(); let mirror = sheet-direct-mirror(sheet); let _port = port(sheet); when (mirror & _port) // can happen before attachment! update-mirror-region(_port, sheet, mirror) end end method note-region-changed; define method note-transform-changed (sheet :: ) => () next-method(); let mirror = sheet-direct-mirror(sheet); let _port = port(sheet); when (mirror & _port) // can happen before attachment! update-mirror-transform(_port, sheet, mirror) end end method note-transform-changed; //--- Maybe this should get done by 'note-transform-changed', //--- but we are trying to keep that function cheap... define method update-all-mirror-positions (sheet :: ) => () do-sheet-tree(method (sheet) when (sheet-direct-mirror(sheet)) note-transform-changed(sheet) end end method, sheet) end method update-all-mirror-positions; // This gets called by 'note-region-changed' on mirrored sheets. // At this point, the sheet's edges are the desired ones, and we // have to update the mirror itself // This default implementation ensures the native (sheet->mirror) // transform is the identity transform define method update-mirror-region (_port :: , sheet :: , mirror) => () // Get the desired new coordinates in parent space let (new-left, new-top, new-right, new-bottom) = sheet-device-edges(sheet); // Get the current mirror's corrdinates let (current-left, current-top, current-right, current-bottom) = mirror-edges(port(sheet), sheet, mirror); when (new-left ~= current-left | new-top ~= current-top | new-right ~= current-right | new-bottom ~= current-bottom) set-mirror-edges(port(sheet), sheet, mirror, new-left, new-top, new-right, new-bottom); sheet-native-transform(sheet) := $identity-transform end end method update-mirror-region; // This gets called by 'note-transform-changed' on mirrored sheets. // At this point, the sheet's position is the desired one, and we // have to update the mirror itself // This default implementation ensures the native (sheet->mirror) // transform is the identity transform define method update-mirror-transform (_port :: , sheet :: , mirror) => () // Get the desired new coordinates in parent space let (new-left, new-top, new-right, new-bottom) = sheet-device-edges(sheet); // Get the current mirror's coordinates let (current-left, current-top, current-right, current-bottom) = mirror-edges(port(sheet), sheet, mirror); ignore(current-right, current-bottom); when (new-left ~= current-left | new-top ~= current-top) let width = new-right - new-left; let height = new-bottom - new-top; set-mirror-edges(port(sheet), sheet, mirror, new-left, new-top, new-left + width, new-top + height); sheet-native-transform(sheet) := $identity-transform end end method update-mirror-transform; // Called from the back-ends, usually via window configuration events define method note-mirror-geometry-changed (_port :: , sheet :: , region :: ) => () let mirror = sheet-mirror(sheet); when (mirror) let (new-left :: , new-top :: , new-right :: , new-bottom :: ) = begin let (left, top, right, bottom) = box-edges(region); untransform-box(sheet-native-transform(sheet), left, top, right, bottom) end; let new-width :: = new-right - new-left; let new-height :: = new-bottom - new-top; transform-distances!(sheet-transform(sheet), new-width, new-height); // This might well cause 'allocate-space' to run, notably when // called on a top-level sheet set-sheet-edges(sheet, new-left, new-top, new-left + new-width, new-top + new-height) end end method note-mirror-geometry-changed; /// Mirror-based repainting // Unmirrored sheets need to have DUIM call 'handle-repaint' on them define method port-handles-repaint? (port :: , sheet :: ) => (true? :: ) #f end method port-handles-repaint?; // For mirrored sheets, back-ends must implement their own methods! define method port-handles-repaint? (port :: , sheet :: ) => (true? :: ) error("The back-end must supply a method for 'port-handles-repaint?'"); end method port-handles-repaint?;