Module: win32-duim Synopsis: Win32 common controls 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 /// Some magic Win32 constants //---*** All of the following should be computed define constant $progress-bar-min-width :: = 50; define constant $progress-bar-height :: = 18; define constant $tab-control-border :: = 2; // in dialog units define constant $slider-page-size :: = 4; define constant $slider-min-length :: = 50; // in pixels define constant $slider-min-breadth :: = 30; // in pixels define constant $list-view-minimum-visible-lines :: = 3; define constant $list-view-default-visible-lines :: = 10; define constant $list-view-extra-height :: = 2; define constant $tree-view-minimum-visible-lines :: = 3; define constant $tree-view-default-visible-lines :: = 10; define constant $tree-view-extra-height :: = 2; define constant $status-bar-border :: = 1; // in pixels define constant $status-bar-spacing :: = 4; // in pixels define constant $spin-box-spacing :: = 0; define constant $up-down-control-width :: = 12; /// Win32 common controls define constant $KERNEL32 = "KERNEL32"; define constant $CREATEACTCTX = "CreateActCtxA"; define constant $ACTIVATEACTCTX = "ActivateActCtx"; //---*** NOTE: Be sure to change this whenever the DLL name is changed (e.g., when released) define constant $ME = "DXWDUIM"; define sealed method initialize-common-controls (_port :: ) => () unless (_port.%common-controls-initialized?) let kernel32 :: = GetModuleHandle($KERNEL32); if (~null-handle?(kernel32)) let fCreateActCtx :: = GetProcAddress(kernel32, $CREATEACTCTX); let fActivateActCtx :: = GetProcAddress(kernel32, $ACTIVATEACTCTX); // If support for activation contexts is present, establish and activate a context // which enables the use of Windows XP visual styles in common controls using the // manifest embedded as a resource in the DUIM DLL... if (~null-pointer?(fCreateActCtx) & ~null-pointer?(fActivateActCtx)) with-stack-structure (act :: ) with-stack-structure (moduleName :: , size: $MAX-PATH + 1) GetModuleFileName(null-handle(), moduleName, $MAX-PATH); act.cbSize-value := safe-size-of(); act.dwFlags-value := logior($ACTCTX-FLAG-RESOURCE-NAME-VALID, $ACTCTX-FLAG-HMODULE-VALID); act.lpSource-value := moduleName; act.lpResourceName-value := MAKEINTRESOURCE(2); act.hModule-value := GetModuleHandle($ME); let hActCtx :: = CreateActCtx(fCreateActCtx, act); if (~null-handle?(hActCtx)) //---*** Should we save the activation handle & cookie and deactivate on exit? ActivateActCtx(fActivateActCtx, hActCtx); end; end; end; end; end; InitCommonControls(); _port.%common-controls-initialized? := #t end end method initialize-common-controls; define open abstract class () end class ; define sealed method do-make-mirror (_port :: , gadget :: ) => (mirror :: ) initialize-common-controls(_port); next-method() end method do-make-mirror; /// Progress controls define sealed class (, , ) end class ; define method class-for-make-pane (framem :: , class == , #key) => (class :: , options :: false-or()) values(, #f) end method class-for-make-pane; define sealed domain make (singleton()); define sealed domain initialize (); define sealed method make-gadget-control (gadget :: , parent :: , options :: , #key x, y, width, height) => (handle :: ) let handle :: = CreateWindowEx(gadget-extended-options(gadget), $PROGRESS-CLASS, "", options, x, y, width, height, parent, $null-hMenu, application-instance-handle(), $NULL-VOID); check-result("CreateWindowEx (PROGRESS_CLASS)", handle); handle end method make-gadget-control; define sealed method note-mirror-created (gadget :: , mirror :: ) => () next-method(); update-progress-bar(gadget) end method note-mirror-created; define sealed method do-compose-space (gadget :: , #key width, height) => (space-requirement :: ) let height = $progress-bar-height; let min-width = $progress-bar-min-width; let width = max(min-width, width | 0); make(, width: width, min-width: min-width, max-width: $fill, height: height) end method do-compose-space; /* //---*** Why does this method not work? define sealed method do-compose-space (gadget :: , #key width, height) => (space-requirement :: ) let min-width = $progress-bar-min-width; let max-width = $fill; // Status bars look nice if they have the same height as a text field let min-height = $progress-bar-min-height; let max-height = $fill; let width = constrain-size(width | min-width, min-width, max-width); let height = constrain-size(height | $progress-bar-height, min-height, max-height); make(, width: width, min-width: min-width, max-width: max-width, height: height, min-height: height, max-height: height) end method do-compose-space; */ define sealed method update-progress-bar (gadget :: ) => () let mirror = sheet-direct-mirror(gadget); when (mirror) let handle = window-handle(mirror); let _port = port(gadget); let value = gadget-value(gadget); let progress-range = gadget-value-range(gadget); let first-value = progress-range[0]; let range-increment = if (size(progress-range) <= 1) 1 else progress-range[1] - first-value end; let pos = floor/(value - first-value, range-increment); let min-value = 0; let max-value = max(min-value, size(progress-range) - 1); SendMessage(handle, $PBM-SETRANGE, 0, MAKELPARAM(min-value, max-value)); SendMessage(handle, $PBM-SETPOS, pos, 0) end end method update-progress-bar; define sealed method note-gadget-value-changed (gadget :: ) => () next-method(); update-progress-bar(gadget) end method note-gadget-value-changed; /// Sliders define sealed class (, , ) end class ; define method class-for-make-pane (framem :: , class == , #key) => (class :: , options :: false-or()) values(, #f) end method class-for-make-pane; define sealed domain make (singleton()); define sealed domain initialize (); define sealed method make-gadget-control (gadget :: , parent :: , options :: , #key x, y, width, height) => (handle :: ) let ticks? = slider-tick-marks(gadget); let handle :: = CreateWindowEx(gadget-extended-options(gadget, default-border?: #f), $TRACKBAR-CLASS, "", %logior(options, if (sheet-tab-stop?(gadget)) %logior($WS-GROUP, $WS-TABSTOP) else 0 end, select (gadget-orientation(gadget)) #"horizontal" => $TBS-HORZ; #"vertical" => $TBS-VERT; end, if (ticks?) $TBS-AUTOTICKS else 0 end), x, y, width, height, parent, $null-hMenu, application-instance-handle(), $NULL-VOID); check-result("CreateWindowEx (TRACKBAR_CLASS)", handle); handle end method make-gadget-control; define sealed method note-mirror-created (gadget :: , mirror :: ) => () next-method(); update-slider-mirror(gadget) end method note-mirror-created; define sealed method do-compose-space (gadget :: , #key width, height) => (space-requirement :: ) select (gadget-orientation(gadget)) #"horizontal" => let min-width = $slider-min-length; let min-height = $slider-min-breadth; let width = constrain-size(width | min-width, min-width, $fill); make(, width: width, height: min-height, min-width: min-width, min-height: min-height, max-width: $fill, max-height: min-height); #"vertical" => let min-width = $slider-min-breadth; let min-height = $slider-min-length; let height = constrain-size(height | min-height, min-height, $fill); make(, width: min-width, height: height, min-width: min-width, min-height: min-height, max-width: min-width, max-height: $fill); end end method do-compose-space; //--- Maybe this should be in DUIM-Gadgets? define sealed method range-gadget-adjusted-contents (gadget :: ) => (position :: , min :: , max :: ) let value = gadget-value(gadget); let value-range = gadget-value-range(gadget); let first-value = value-range[0]; let range-increment = if (size(value-range) <= 1) 1 else value-range[1] - first-value end; let pos = floor/(value - first-value, range-increment); let min = 0; let max = size(value-range) - 1; values(pos, min, max) end method range-gadget-adjusted-contents; define sealed method update-slider-mirror (gadget :: ) => () let handle :: false-or() = window-handle(gadget); when (handle) let (pos, min, max) = range-gadget-adjusted-contents(gadget); SendMessage(handle, $TBM-SETRANGE, $true, MAKELONG(min, max)); SendMessage(handle, $TBM-SETPOS, $true, pos); let tick-marks = slider-tick-marks(gadget); when (tick-marks) SendMessage(handle, $TBM-SETTICFREQ, tick-marks, pos) end end end method update-slider-mirror; define sealed method note-gadget-value-changed (gadget :: ) => () next-method(); update-slider-mirror(gadget) end method note-gadget-value-changed; define sealed method note-gadget-value-range-changed (gadget :: ) => () next-method(); update-slider-mirror(gadget) end method note-gadget-value-range-changed; define sealed method handle-scrolling (gadget :: , scroll-code :: , position :: ) => (handled? :: ) block (return) let value-range = gadget-value-range(gadget); select (scroll-code) $TB-THUMBTRACK => let (min-pos, max-pos) = values(0, size(value-range) - 1); when (position >= min-pos & position <= max-pos) distribute-value-changing-callback(gadget, value-range[position]) end; $TB-THUMBPOSITION => let (min-pos, max-pos) = values(0, size(value-range) - 1); when (position >= min-pos & position <= max-pos) distribute-value-changed-callback(gadget, value-range[position]) end; $TB-PAGEUP => handle-slider-increment(gadget, - $slider-page-size); $TB-PAGEDOWN => handle-slider-increment(gadget, $slider-page-size); $TB-LINEUP => handle-slider-increment(gadget, -1); $TB-LINEDOWN => handle-slider-increment(gadget, 1); $TB-BOTTOM => let position = size(value-range) - 1; distribute-value-changed-callback(gadget, value-range[position]); $TB-TOP => let position = 0; distribute-value-changed-callback(gadget, value-range[position]); otherwise => return(#f) end; #t end end method handle-scrolling; define sealed method handle-slider-increment (gadget :: , increment :: ) => () let old-value = gadget-value(gadget); let value-range = gadget-value-range(gadget); let pos = position(value-range, old-value, test: \=); let last-pos = size(value-range) - 1; let pos = max(0, min(pos + increment, last-pos)); when (pos ~= last-pos) let value = value-range[pos]; distribute-value-changed-callback(gadget, value) end end method handle-slider-increment; /// Tool bars //---*** Someday we should do these for real! define sealed class (, ) //--- The way we do this separator stuff is just loathsome... sealed slot tool-bar-decoration :: ; sealed slot %separator :: false-or() = #f; end class ; define method class-for-make-pane (framem :: , class == , #key) => (class :: , options :: false-or()) values(, #f) end method class-for-make-pane; define sealed domain make (singleton()); define sealed domain initialize (); define method initialize (gadget :: , #key frame-manager: framem) => () next-method(); let framem = framem | port-default-frame-manager(default-port()); with-frame-manager (framem) gadget.%separator := make(); tool-bar-decoration(gadget) := vertically (spacing: 2) gadget.%separator; gadget end end end method initialize; // When we map a tool-bar, we also map its enclosing decoration. // This manages to avoid an infinite loop because Silica is careful // not to map any sheet that is already mapped. define method note-sheet-mapped (gadget :: ) => () next-method(); when (sheet-direct-mirror(gadget.%separator)) sheet-mapped?(tool-bar-decoration(gadget)) := #t end end method note-sheet-mapped; // Ditto, for unmapping define method note-sheet-unmapped (gadget :: ) => () next-method(); when (sheet-direct-mirror(gadget.%separator)) sheet-mapped?(tool-bar-decoration(gadget)) := #f end end method note-sheet-unmapped; /// Status bars define sealed class (, , ) sealed slot status-bar-simple? :: = #f, setter: %simple?-setter; slot status-bar-simple-text :: = ""; keyword border: = $status-bar-border; keyword spacing: = $status-bar-spacing; keyword y-alignment: = #"center"; end class ; define method class-for-make-pane (framem :: , class == , #key) => (class :: , options :: false-or()) values(, #f) end method class-for-make-pane; define sealed domain make (singleton()); define sealed domain initialize (); define sealed method make-gadget-control (gadget :: , parent :: , options :: , #key x, y, width, height) => (handle :: ) let handle :: = CreateWindowEx(gadget-extended-options(gadget, default-border?: #f), $STATUSCLASSNAME, "", %logior(options, $SBARS-SIZEGRIP), 0, 0, 0, 0, parent, $null-hMenu, application-instance-handle(), $NULL-VOID); check-result("CreateWindowEx (STATUSCLASSNAME)", handle); handle end method make-gadget-control; define sealed method do-compose-space (gadget :: , #key width, height) => (space-requirement :: ) // We want a little extra width to keep the final field from being // obscured by the resize grip let extra-width :: = GetSystemMetrics($SM-CXVSCROLL); let space-req = next-method(gadget, width: width & (width - extra-width), height: height); space-requirement+(gadget, space-req, width: extra-width, min-width: extra-width, max-width: extra-width) end method do-compose-space; //---*** We should be more careful that the height is set up right, taking //---*** into account borders etc. define sealed method do-allocate-space (gadget :: , width :: , height :: ) => () let extra-width :: = GetSystemMetrics($SM-CXVSCROLL); // Do the usual allocation on the child sheets, but don't let them // use the extra space we need for the resize grip next-method(gadget, width - extra-width, height); let handle = window-handle(gadget); let children = sheet-children(gadget); // The idea here is to allocate a new part at the end if the final // field is not a label, otherwise we stretch out the final label let final-child = last(children); let final-label? = instance?(final-child,