MODULE XMLComponents;
IMPORT
Files, DynamicStrings, XMLObjects, XML, CSS2, CSS2Properties, CSS2Scanner, CSS2Parser,
WMWindowManager, Gfx;
TYPE
String* = XML.String;
StateMessage* = OBJECT
VAR state-: BOOLEAN;
PROCEDURE & InitStateMessage*(state: BOOLEAN);
BEGIN
SELF.state := state
END InitStateMessage;
END StateMessage;
ValueMessage* = OBJECT
VAR value*: LONGINT;
END ValueMessage;
MouseMessage* = OBJECT
VAR
x*, y*: LONGINT;
keys*: SET
END MouseMessage;
KeyMessage* = OBJECT
VAR
ch*: CHAR;
keySym*: LONGINT;
flags*: SET
END KeyMessage;
Listener* = PROCEDURE {DELEGATE} (sender, data: ANY);
VAR
Unassigned: Listener;
TYPE
ListenerEntry = OBJECT
VAR
listener: Listener
END ListenerEntry;
EventDispatcher* = OBJECT
VAR
listeners: XMLObjects.Collection;
sender: ANY;
PROCEDURE & Init*(sender: ANY);
VAR arrColl: XMLObjects.ArrayCollection;
BEGIN
NEW(arrColl); listeners := arrColl;
SELF.sender := sender
END Init;
PROCEDURE AddListener*(listener: Listener);
VAR entry: ListenerEntry;
BEGIN
NEW(entry);
entry.listener := listener;
listeners.Add(entry)
END AddListener;
PROCEDURE Dispatch*(data: ANY);
VAR entries: XMLObjects.Enumerator; entry: ANY;
BEGIN
IF listeners.GetNumberOfElements() > 0 THEN
entries := listeners.GetEnumerator();
WHILE entries.HasMoreElements() DO
entry := entries.GetNext();
entry(ListenerEntry).listener(sender, data)
END
END
END Dispatch;
END EventDispatcher;
PropChangerEntry = OBJECT
VAR
listenedComponent: CSS2Component;
event: String;
state: BOOLEAN
END PropChangerEntry;
PropertyChanger* = OBJECT
VAR
entries: XMLObjects.Collection;
changingComponent: CSS2Component;
changingProperties: CSS2.RuleSet;
oldState: BOOLEAN;
PROCEDURE & Init*;
VAR arrColl: XMLObjects.ArrayCollection;
BEGIN
NEW(arrColl); entries := arrColl; NEW(arrColl);
oldState := FALSE
END Init;
PROCEDURE Copy*(): PropertyChanger;
VAR newPropChanger: PropertyChanger; enum: XMLObjects.Enumerator;
BEGIN
NEW(newPropChanger);
enum := entries.GetEnumerator();
WHILE enum.HasMoreElements() DO
newPropChanger.entries.Add(enum.GetNext());
END;
newPropChanger.changingComponent := changingComponent;
newPropChanger.changingProperties := changingProperties;
newPropChanger.oldState := oldState;
RETURN newPropChanger
END Copy;
PROCEDURE AddListenedComponent*(comp: CSS2Component; VAR event: ARRAY OF CHAR);
VAR newPropChangerEntry: PropChangerEntry;
BEGIN
NEW(newPropChangerEntry);
newPropChangerEntry.listenedComponent := comp;
newPropChangerEntry.event := NewString(event);
newPropChangerEntry.state := FALSE;
entries.Add(newPropChangerEntry)
END AddListenedComponent;
PROCEDURE SetChangingComponent*(comp: CSS2Component; ruleSet: CSS2.RuleSet);
VAR enum: XMLObjects.Enumerator; entry: ANY;
BEGIN
changingComponent := comp;
changingProperties := ruleSet;
enum := entries.GetEnumerator();
WHILE enum.HasMoreElements() DO
entry := enum.GetNext();
entry(PropChangerEntry).listenedComponent.AddEventListener(StatusChanged, entry(PropChangerEntry).event^)
END
END SetChangingComponent;
PROCEDURE StatusChanged(sender, data: ANY);
VAR enum: XMLObjects.Enumerator; entry: ANY; found: BOOLEAN;
BEGIN
IF (sender # NIL) & (sender IS CSS2Component) & (data # NIL) & (data IS StateMessage) THEN
enum := entries.GetEnumerator(); found := FALSE;
WHILE enum.HasMoreElements() & ~found DO
entry := enum.GetNext();
found := entry(PropChangerEntry).listenedComponent = sender
END;
IF found & (entry(PropChangerEntry).state # data(StateMessage).state) THEN
entry(PropChangerEntry).state := data(StateMessage).state;
IF AllStatesSet() # oldState THEN ChangeProperties() END
END
END
END StatusChanged;
PROCEDURE AllStatesSet(): BOOLEAN;
VAR enum: XMLObjects.Enumerator; propChangerEntry: ANY; state: BOOLEAN;
BEGIN
enum := entries.GetEnumerator(); state := TRUE;
WHILE enum.HasMoreElements() & state DO
propChangerEntry := enum.GetNext();
state := propChangerEntry(PropChangerEntry).state
END;
RETURN state
END AllStatesSet;
PROCEDURE ChangeProperties;
VAR declarations: XMLObjects.Enumerator; declaration: ANY; s: String; oldRuleSet: CSS2.RuleSet;
BEGIN
NEW(oldRuleSet);
declarations := changingProperties.GetDeclarations();
WHILE declarations.HasMoreElements() DO
declaration := declarations.GetNext();
s := declaration(CSS2.Declaration).GetProperty();
oldRuleSet.AddDeclaration(changingComponent.properties.GetValue(s^));
changingComponent.properties.SetValue(declaration(CSS2.Declaration))
END;
changingProperties := oldRuleSet;
oldState := ~oldState;
changingComponent.PropertiesChanged();
changingComponent.Invalidate()
END ChangeProperties;
END PropertyChanger;
Component* = OBJECT (XML.Element)
VAR
locks: LONGINT;
PROCEDURE AddAttribute(attribute: XML.Attribute);
BEGIN
ConnectAttribute(attribute);
AddAttribute^(attribute)
END AddAttribute;
PROCEDURE LockUpdate*;
BEGIN
INC(locks)
END LockUpdate;
PROCEDURE UnlockUpdate*;
BEGIN
IF locks > 0 THEN DEC(locks) END
END UnlockUpdate;
PROCEDURE IsLocked*(): BOOLEAN;
BEGIN
RETURN locks > 0
END IsLocked;
PROCEDURE GetNumberOfEvents*(): LONGINT;
BEGIN
RETURN 0
END GetNumberOfEvents;
PROCEDURE GetEventName*(i: LONGINT): String;
BEGIN
RETURN NIL
END GetEventName;
PROCEDURE AddEventListener*(listener: Listener; event: ARRAY OF CHAR);
END AddEventListener;
PROCEDURE GetNumberOfListeners*(): LONGINT;
BEGIN
RETURN 0
END GetNumberOfListeners;
PROCEDURE GetListenerName*(i: LONGINT): String;
BEGIN
RETURN NIL
END GetListenerName;
PROCEDURE GetListener*(name: ARRAY OF CHAR): Listener;
BEGIN
RETURN Unassigned
END GetListener;
PROCEDURE Connect*;
VAR enum: XMLObjects.Enumerator; p: ANY;
BEGIN
enum := GetAttributes();
WHILE enum.HasMoreElements() DO
p := enum.GetNext();
ConnectAttribute(p(XML.Attribute))
END;
IF GetNumberOfContents() > 0 THEN
enum := GetContents();
WHILE enum.HasMoreElements() DO
p := enum.GetNext();
IF p IS Component THEN p(Component).Connect() END
END
END
END Connect;
PROCEDURE Finalize*;
VAR contents: XMLObjects.Enumerator; c: ANY;
BEGIN
contents := GetContents();
WHILE contents.HasMoreElements() DO
c := contents.GetNext();
IF c IS Component THEN c(Component).Finalize() END
END
END Finalize;
PROCEDURE ConnectAttribute(attribute: XML.Attribute);
VAR listener: Listener; s1, event: String;
BEGIN
s1 := attribute.GetName();
IF s1 # NIL THEN
listener := GetListener(s1^);
IF listener # Unassigned THEN
s1 := attribute.GetValue();
event := ExtractEvent(s1^);
RegisterListener(listener, event^, s1^, 0, DynamicStrings.StringLength(s1^))
END
END;
END ConnectAttribute;
PROCEDURE RegisterListener(listener: Listener; VAR event, path: ARRAY OF CHAR; offset, len: LONGINT);
VAR elem: XML.Element; retComp: Component; s1, s2: String; i: LONGINT;
contents: XMLObjects.Enumerator; content: ANY; quoteChar: CHAR; attr: XML.Attribute;
PROCEDURE SkipWhiteSpace;
BEGIN
WHILE (offset < len) & ((path[offset] = 20X) OR (path[offset] = 9X)) DO INC(offset) END
END SkipWhiteSpace;
BEGIN
SkipWhiteSpace();
IF offset < len THEN
CASE path[offset] OF
| ':': AddEventListener(listener, event)
| '/':
IF offset = 0 THEN
elem := GetRoot();
IF elem IS Component THEN elem(Component).RegisterListener(listener, event, path, offset + 1, len) END
ELSE
contents := GetContents();
WHILE (contents.HasMoreElements()) & (retComp = NIL) DO
content := contents.GetNext();
IF content IS Component THEN content(Component).RegisterListener(listener, event, path, offset + 1, len) END
END
END
| '.':
IF (offset + 1 < len) & (path[offset + 1] = '/') THEN
RegisterListener(listener, event, path, offset + 1, len)
ELSIF (offset + 2 < len) & (path[offset + 1] = '.') & (path[offset + 2] = '/') THEN
elem := GetParent();
IF (elem # NIL) & (elem IS Component) THEN
elem(Component).RegisterListener(listener, event, path, offset + 2, len)
END
END
| '[':
INC(offset); SkipWhiteSpace(); NEW(s1, len - offset + 2); i := 0;
WHILE (i < len - offset) & (path[offset + i] # '=') DO
s1[i] := path[offset + i]; INC(i)
END;
s1[i + 1] := 0X; INC(offset, i + 1); SkipWhiteSpace();
attr := GetAttribute(s1^);
IF attr # NIL THEN
quoteChar := path[offset]; INC(offset);
IF (quoteChar = "'") OR (quoteChar = '"') THEN
NEW(s1, len - offset + 2); i := 0;
WHILE (i < len - offset) & (path[offset + i] # quoteChar) DO
s1[i] := path[offset + i]; INC(i)
END;
s1[i + 1] := 0X; INC(offset, i);
s2 := attr.GetValue();
IF (s1^ = s2^) & (offset + 1 < len) & (path[offset + 1] = ']') THEN
RegisterListener(listener, event, path, offset + 2, len)
END
END
END
| '#':
INC(offset); NEW(s1, len - offset + 2); i := 0;
WHILE (i < len - offset) & (path[offset + i] # '/') & (path[offset + 1] # '.') & (path[offset + 1] # '[')
& (path[offset + 1] # '#') DO
s1[i] := path[offset + i]; INC(i)
END;
s1[i + 1] := 0X; INC(offset, i);
s2 := GetId();
IF (s2 # NIL) & (s1^ = s2^) THEN RegisterListener(listener, event, path, offset, len) END
| '*':
IF offset + 1 < len THEN RegisterListener(listener, event, path, offset + 1, len) END
ELSE
NEW(s1, len - offset + 2); i := 0;
WHILE (i < len - offset) & (path[offset + i] # '/') & (path[offset + i] # '.') & (path[offset + i] # '[')
& (path[offset + i] # '#') & (path[offset + i] # ':')DO
s1[i] := path[offset + i]; INC(i)
END;
s1[i + 1] := 0X; INC(offset, i);
s2 := GetName();
IF s1^ = s2^ THEN RegisterListener(listener, event, path, offset, len) END
END
ELSE
AddEventListener(listener, event)
END
END RegisterListener;
END Component;
CSS2Component* = OBJECT (Component)
VAR
properties-: CSS2Properties.AllMediaProperties;
PROCEDURE &Init*;
BEGIN
Init^();
NEW(properties);
END Init;
PROCEDURE AddContent(c: XML.Content);
BEGIN
IF c IS CSS2Component THEN c(CSS2Component).properties.SetParent(properties) END;
AddContent^(c)
END AddContent;
PROCEDURE GetProperties*(): CSS2Properties.AllMediaProperties;
BEGIN
RETURN properties
END GetProperties;
PROCEDURE SetProperties*(p: CSS2Properties.AllMediaProperties);
BEGIN
properties := p
END SetProperties;
PROCEDURE SetProperty*(name, value: ARRAY OF CHAR);
VAR ruleSet: CSS2.RuleSet; declarations: XMLObjects.Enumerator; declaration: ANY;
BEGIN
ruleSet := GenerateRuleSet(name, value);
IF ruleSet # NIL THEN
declarations := ruleSet.GetDeclarations();
IF declarations.HasMoreElements() THEN
declaration := declarations.GetNext();
properties.SetValue(declaration(CSS2.Declaration))
END
END
END SetProperty;
PROCEDURE SetPropertyOnEvent*(name, value, event: ARRAY OF CHAR);
VAR ruleSet: CSS2.RuleSet; propertyChanger: PropertyChanger;
BEGIN
ruleSet := GenerateRuleSet(name, value);
IF ruleSet # NIL THEN
NEW(propertyChanger);
propertyChanger.AddListenedComponent(SELF, event);
propertyChanger.SetChangingComponent(SELF, ruleSet)
END
END SetPropertyOnEvent;
PROCEDURE GenerateRuleSet(name, value: ARRAY OF CHAR): CSS2.RuleSet;
VAR scanner: CSS2Scanner.Scanner; parser: CSS2Parser.Parser; file: Files.File; w: Files.Writer;
styleSheet: CSS2.StyleSheet; ruleSets: XMLObjects.Enumerator; ruleSet: ANY;
BEGIN
file := Files.New("");
Files.OpenWriter(w, file, 0);
w.Char('{');
w.Bytes(name, 0, DynamicStrings.StringLength(name));
w.Char(':');
w.Bytes(value, 0, DynamicStrings.StringLength(value));
w.Char('}');
w.Update;
NEW(scanner, file); NEW(parser, scanner); parser.reportError := NoReportError;
styleSheet := parser.Parse();
ruleSets := styleSheet.GetRuleSets();
IF ruleSets.HasMoreElements() THEN ruleSet := ruleSets.GetNext(); RETURN ruleSet(CSS2.RuleSet)
ELSE RETURN NIL
END
END GenerateRuleSet;
PROCEDURE PropertiesChanged*;
VAR contents: XMLObjects.Enumerator; c: ANY;
BEGIN
properties.ComputeValues();
IF GetNumberOfContents() > 0 THEN
contents := GetContents();
WHILE contents.HasMoreElements() DO
c := contents.GetNext();
IF c IS CSS2Component THEN c(CSS2Component).PropertiesChanged() END
END
END
END PropertiesChanged;
PROCEDURE Invalidate*;
END Invalidate;
END CSS2Component;
Box* = OBJECT
VAR
x*, y*, w*, h*: LONGINT;
PROCEDURE InBox*(x, y: LONGINT): BOOLEAN;
BEGIN
RETURN (SELF.x <= x) & (x < SELF.x + w) & (SELF.y <= y) & (y < SELF.y + h)
END InBox;
PROCEDURE IsEmpty*(): BOOLEAN;
BEGIN
RETURN (w = 0) OR (h = 0)
END IsEmpty;
PROCEDURE SetBox*(box: Box);
BEGIN
x := box.x; y := box.y; w := box.w; h := box.h
END SetBox;
PROCEDURE SetRect*(x, y, w, h: LONGINT);
BEGIN
SELF.x := x; SELF.y := y; SELF.w := w; SELF.h := h
END SetRect;
PROCEDURE Intersect*(box: Box): BOOLEAN;
BEGIN
RETURN (x < box.x + box.w) & (box.x < x + w) & (y < box.y + box.h) & (box.y < y + h)
END Intersect;
PROCEDURE IntersectRect*(x, y, w, h: LONGINT): BOOLEAN;
BEGIN
RETURN (SELF.x < x + w) & (x < SELF.x + SELF.w) & (SELF.y < y + h) & (y < SELF.y + SELF.h)
END IntersectRect;
PROCEDURE Clip*(box: Box);
BEGIN
ClipRect(box.x, box.y, box.w, box.h)
END Clip;
PROCEDURE ClipRect*(x, y, w, h: LONGINT);
VAR ur: LONGINT;
BEGIN
ur := Min(SELF.x + SELF.w, x + w); SELF.x := Max(SELF.x, x); SELF.w := Max(0, ur - SELF.x);
ur := Min(SELF.y + SELF.h, y + h); SELF.y := Max(SELF.y, y); SELF.h := Max(0, ur - SELF.y)
END ClipRect;
PROCEDURE Extend*(box: Box);
BEGIN
ExtendRect(box.x, box.y, box.w, box.h)
END Extend;
PROCEDURE ExtendRect*(x, y, w, h: LONGINT);
VAR ur: LONGINT;
BEGIN
ur := Max(SELF.x + SELF.w, x + w); SELF.x := Min(SELF.x, x); SELF.w := ur - SELF.x;
ur := Max(SELF.y + SELF.h, y + h); SELF.y := Min(SELF.y, y); SELF.h := ur - SELF.y
END ExtendRect;
END Box;
VisualComponent* = OBJECT (CSS2Component)
VAR
pointerMoveListeners, hoverListeners, activeListeners, focusListeners: EventDispatcher;
bounds, borderBox, paddingBox, contentBox, invalidBox: Box;
inlineBoxes, textBoxes: XMLObjects.Collection;
pointerOwner, focusOwner: VisualComponent;
isHovered*, hasFocus*, isActive*, isLink*, isVisited*, dragable*: BOOLEAN;
lastMouseKeys-: SET;
lastX-, lastY-: LONGINT;
trueStateMsg, falseStateMsg: StateMessage;
PROCEDURE &Init*;
VAR vprop: CSS2Properties.VisualProperties; arrColl: XMLObjects.ArrayCollection;
BEGIN
Init^();
NEW(vprop);
properties := vprop;
NEW(bounds); bounds.x := 0; bounds.y := 0; bounds.w := 0; bounds.h := 0;
NEW(borderBox); NEW(paddingBox); NEW(contentBox); NEW(invalidBox);
NEW(arrColl); textBoxes := arrColl;
NEW(arrColl); inlineBoxes := arrColl;
pointerOwner := SELF;
NEW(trueStateMsg, TRUE); NEW(falseStateMsg, FALSE);
NEW(pointerMoveListeners, SELF); NEW(hoverListeners, SELF); NEW(activeListeners, SELF); NEW(focusListeners, SELF)
END Init;
PROCEDURE AddContent(c: XML.Content);
BEGIN
IF c IS CSS2Component THEN
c(VisualComponent).properties.SetParent(properties);
END;
AddContent^(c)
END AddContent;
PROCEDURE SetProperties(p: CSS2Properties.AllMediaProperties);
BEGIN
IF (p # NIL) & (p IS CSS2Properties.VisualProperties) THEN
SetProperties^(p)
END
END SetProperties;
PROCEDURE GetBounds*(): Box;
VAR box: Box;
BEGIN
NEW(box); box.x := GetX(); box.y := GetY(); box.w := GetWidth(); box.h := GetHeight(); RETURN box
END GetBounds;
PROCEDURE SetBounds*(bounds: Box);
BEGIN
IF bounds # NIL THEN SELF.bounds.SetBox(bounds) END
END SetBounds;
PROCEDURE GetWidth*(): LONGINT;
BEGIN
RETURN bounds.w
END GetWidth;
PROCEDURE SetWidth*(w: LONGINT);
VAR vp: CSS2Properties.VisualProperties;
BEGIN
vp := properties(CSS2Properties.VisualProperties);
vp.width.computed := w - (vp.margin.left.computed + vp.borderWidth.left.computed + vp.padding.left.computed
+ vp.padding.right.computed + vp.borderWidth.right.computed + vp.margin.right.computed);
ComputeWidths()
END SetWidth;
PROCEDURE GetHeight*(): LONGINT;
BEGIN
RETURN bounds.h
END GetHeight;
PROCEDURE SetHeight*(h: LONGINT);
VAR vp: CSS2Properties.VisualProperties;
BEGIN
vp := properties(CSS2Properties.VisualProperties);
vp.height.computed := h - (vp.margin.top.computed + vp.borderWidth.top.computed + vp.padding.top.computed
+ vp.padding.bottom.computed + vp.borderWidth.bottom.computed + vp.margin.bottom.computed);
ComputeHeights()
END SetHeight;
PROCEDURE GetX*(): LONGINT;
BEGIN
RETURN bounds.x
END GetX;
PROCEDURE SetX*(x: LONGINT);
BEGIN
lastX := lastX - (x - bounds.x); bounds.x := x;
IF ~IsHit(lastX, lastY) THEN PointerUp(lastX, lastY, lastMouseKeys) END
END SetX;
PROCEDURE GetY*(): LONGINT;
BEGIN
RETURN bounds.y
END GetY;
PROCEDURE SetY*(y: LONGINT);
BEGIN
lastY := lastY - (y - bounds.y); bounds.y := y;
IF ~IsHit(lastX, lastY) THEN PointerUp(lastX, lastY, lastMouseKeys) END
END SetY;
PROCEDURE GetContentBox*(): Box;
VAR box: Box;
BEGIN
NEW(box); box.SetBox(contentBox); RETURN box
END GetContentBox;
PROCEDURE SetContentBox*(contentBox: Box);
BEGIN
SELF.contentBox.SetBox(contentBox)
END SetContentBox;
PROCEDURE GetContentWidth*(): LONGINT;
BEGIN
RETURN contentBox.w
END GetContentWidth;
PROCEDURE SetContentWidth*(w: LONGINT);
BEGIN
properties(CSS2Properties.VisualProperties).width.computed := w;
ComputeWidths()
END SetContentWidth;
PROCEDURE GetContentHeight*(): LONGINT;
BEGIN
RETURN contentBox.h
END GetContentHeight;
PROCEDURE SetContentHeight*(h: LONGINT);
BEGIN
properties(CSS2Properties.VisualProperties).height.computed := h;
ComputeHeights()
END SetContentHeight;
PROCEDURE GetContentX*(): LONGINT;
BEGIN
RETURN contentBox.x
END GetContentX;
PROCEDURE GetContentY*(): LONGINT;
BEGIN
RETURN contentBox.y
END GetContentY;
PROCEDURE GetBorderBox*(): Box;
VAR box: Box;
BEGIN
NEW(box); box.SetBox(borderBox); RETURN box
END GetBorderBox;
PROCEDURE GetBorderWidth*(): LONGINT;
BEGIN
RETURN borderBox.w
END GetBorderWidth;
PROCEDURE GetBorderHeight*(): LONGINT;
BEGIN
RETURN borderBox.h
END GetBorderHeight;
PROCEDURE GetBorderX*(): LONGINT;
BEGIN
RETURN borderBox.x
END GetBorderX;
PROCEDURE GetBorderY*(): LONGINT;
BEGIN
RETURN borderBox.y
END GetBorderY;
PROCEDURE GetPaddingBox*(): Box;
VAR box: Box;
BEGIN
NEW(box); box.SetBox(paddingBox); RETURN box
END GetPaddingBox;
PROCEDURE GetPaddingWidth*(): LONGINT;
BEGIN
RETURN paddingBox.w
END GetPaddingWidth;
PROCEDURE GetPaddingHeight*(): LONGINT;
BEGIN
RETURN paddingBox.h
END GetPaddingHeight;
PROCEDURE GetPaddingX*(): LONGINT;
BEGIN
RETURN paddingBox.x
END GetPaddingX;
PROCEDURE GetPaddingY*(): LONGINT;
BEGIN
RETURN paddingBox.y
END GetPaddingY;
PROCEDURE ComputeWidths*;
VAR vp: CSS2Properties.VisualProperties;
BEGIN
vp := properties(CSS2Properties.VisualProperties);
bounds.w := ENTIER(0.5 + vp.margin.left.computed + vp.borderWidth.left.computed + vp.padding.left.computed
+ vp.width.computed + vp.padding.right.computed + vp.borderWidth.right.computed + vp.margin.right.computed);
borderBox.x := ENTIER(0.5 + vp.margin.left.computed);
borderBox.w := ENTIER(0.5 + vp.borderWidth.left.computed + vp.padding.left.computed + vp.width.computed
+ vp.padding.right.computed + vp.borderWidth.right.computed);
paddingBox.x := ENTIER(0.5 + vp.margin.left.computed + vp.borderWidth.left.computed);
paddingBox.w := ENTIER(0.5 + vp.padding.left.computed + vp.width.computed + vp.padding.right.computed);
contentBox.x := ENTIER(0.5 + vp.margin.left.computed + vp.borderWidth.left.computed + vp.padding.left.computed);
contentBox.w := ENTIER(0.5 + vp.width.computed);
IF ~IsHit(lastX, lastY) THEN PointerUp(lastX, lastY, lastMouseKeys) END
END ComputeWidths;
PROCEDURE ComputeHeights*;
VAR vp: CSS2Properties.VisualProperties;
BEGIN
vp := properties(CSS2Properties.VisualProperties);
bounds.h := ENTIER(0.5 + vp.margin.top.computed + vp.borderWidth.top.computed + vp.padding.top.computed
+ vp.height.computed + vp.padding.bottom.computed + vp.borderWidth.bottom.computed
+ vp.margin.bottom.computed);
borderBox.y := ENTIER(0.5 + vp.margin.top.computed);
borderBox.h := ENTIER(0.5 + vp.borderWidth.top.computed + vp.padding.top.computed + vp.height.computed
+ vp.padding.bottom.computed + vp.borderWidth.bottom.computed);
paddingBox.y := ENTIER(0.5 + vp.margin.top.computed + vp.borderWidth.top.computed);
paddingBox.h := ENTIER(0.5 + vp.padding.top.computed + vp.height.computed + vp.padding.bottom.computed);
contentBox.y := ENTIER(0.5 + vp.margin.top.computed + vp.borderWidth.top.computed + vp.padding.top.computed);
contentBox.h := ENTIER(0.5 + vp.height.computed);
IF ~IsHit(lastX, lastY) THEN PointerUp(lastX, lastY, lastMouseKeys) END
END ComputeHeights;
PROCEDURE GetNumberOfEvents(): LONGINT;
BEGIN
RETURN GetNumberOfEvents^() + 4
END GetNumberOfEvents;
PROCEDURE GetEventName(i: LONGINT): String;
VAR s: String;
BEGIN
CASE i - GetNumberOfEvents^() OF
| 0: s := NewString("hover")
| 1: s := NewString("active")
| 2: s := NewString("focus")
| 3: RETURN NewString("pointer-move")
ELSE
END;
RETURN s
END GetEventName;
PROCEDURE AddEventListener(listener: Listener; event: ARRAY OF CHAR);
BEGIN
IF event = "hover" THEN hoverListeners.AddListener(listener)
ELSIF event = "active" THEN activeListeners.AddListener(listener)
ELSIF event = "focus" THEN focusListeners.AddListener(listener)
ELSIF event = "pointer-move" THEN pointerMoveListeners.AddListener(listener)
ELSE AddEventListener^(listener, event)
END
END AddEventListener;
PROCEDURE UnlockUpdate;
BEGIN
UnlockUpdate^();
IF ~IsLocked() & ~invalidBox.IsEmpty() THEN
InvalidateRange(invalidBox.x, invalidBox.y, invalidBox.w, invalidBox.h);
invalidBox.w := 0; invalidBox.h := 0
END
END UnlockUpdate;
PROCEDURE SetActive*(isActive: BOOLEAN);
BEGIN
IF isActive # SELF.isActive THEN
LockUpdate();
IF isActive THEN activeListeners.Dispatch(trueStateMsg) ELSE activeListeners.Dispatch(falseStateMsg) END;
UnlockUpdate();
SELF.isActive := isActive
END
END SetActive;
PROCEDURE IsActive(): BOOLEAN;
BEGIN
RETURN isActive
END IsActive;
PROCEDURE SetPointer*(pointerInfo: WMWindowManager.PointerInfo);
VAR parent: XML.Element;
BEGIN
parent := GetParent();
IF (parent # NIL) & (parent IS VisualComponent) THEN
parent(VisualComponent).SetPointer(pointerInfo)
END
END SetPointer;
PROCEDURE GetPointerOwner*(): VisualComponent;
BEGIN
RETURN pointerOwner
END GetPointerOwner;
PROCEDURE IsHit*(x, y: LONGINT): BOOLEAN;
VAR contents: XMLObjects.Enumerator; content: ANY;
BEGIN
IF ((properties(CSS2Properties.VisualProperties).overflow.computed = CSS2Properties.Visible) OR contentBox.InBox(x, y))
& (properties(CSS2Properties.VisualProperties).visibility.computed = CSS2Properties.Visible) THEN
IF GetNumberOfContents() > 0 THEN
contents := GetContents();
WHILE contents.HasMoreElements() DO
content := contents.GetNext();
IF (content IS VisualComponent)
& content(VisualComponent).IsHit(x - content(VisualComponent).GetX(), y - content(VisualComponent).GetY())
THEN
RETURN TRUE
END
END
END
END;
RETURN FALSE
END IsHit;
PROCEDURE PositionOwner*(x, y: LONGINT): VisualComponent;
VAR contents: XMLObjects.Enumerator; content: ANY; po: VisualComponent;
BEGIN
IF (properties(CSS2Properties.VisualProperties).overflow.computed = CSS2Properties.Visible) OR contentBox.InBox(x, y) THEN
IF GetNumberOfContents() > 0 THEN
contents := GetContents();
WHILE contents.HasMoreElements() DO
content := contents.GetNext();
IF content IS VisualComponent THEN
WITH content: VisualComponent DO
IF content.IsHit(x - content.GetX(), y - content.GetY()) THEN
po := content
END
END
END
END
END
END;
IF po # NIL THEN RETURN po ELSE
RETURN SELF
END
END PositionOwner;
PROCEDURE PointerLeave*;
BEGIN
IF (pointerOwner = SELF) THEN
IF isActive THEN PointerUp(lastX, lastY, lastMouseKeys) END;
IF isHovered THEN hoverListeners.Dispatch(falseStateMsg); isHovered := FALSE END
ELSIF pointerOwner # SELF THEN
pointerOwner.PointerLeave()
END
END PointerLeave;
PROCEDURE PointerDown*(x, y: LONGINT; keys: SET);
BEGIN
IF dragable & (keys = {0}) THEN lastX := x; lastY := y END;
SetActive(IsHit(x, y))
END PointerDown;
PROCEDURE PointerMove*(x, y: LONGINT; keys: SET);
BEGIN
lastX := x; lastY := y;
LockUpdate();
IF IsHit(x, y) THEN
IF ~isHovered THEN
hoverListeners.Dispatch(trueStateMsg);
isHovered := TRUE;
IF keys # {} THEN SetActive(TRUE) END;
SetPointer(properties(CSS2Properties.VisualProperties).cursor.computed)
END
ELSE
IF isHovered THEN
IF keys # {} THEN
SetActive(FALSE)
END;
hoverListeners.Dispatch(falseStateMsg);
isHovered := FALSE
END
END;
IF dragable & (keys = {0}) THEN
END;
pointerMoveListeners.Dispatch(NIL);
UnlockUpdate()
END PointerMove;
PROCEDURE PointerUp*(x, y: LONGINT; keys: SET);
BEGIN
SetActive(FALSE)
END PointerUp;
PROCEDURE KeyPressed*(ch: CHAR; flags: SET; VAR keySym: LONGINT);
END KeyPressed;
PROCEDURE FocusReceived*;
BEGIN
focusListeners.Dispatch(trueStateMsg)
END FocusReceived;
PROCEDURE FocusLost*;
BEGIN
focusListeners.Dispatch(falseStateMsg);
IF focusOwner # NIL THEN
IF focusOwner # SELF THEN focusOwner.FocusLost() END;
focusOwner := NIL
END
END FocusLost;
PROCEDURE ProcessMessage*(m: ANY);
VAR newPointerOwner: VisualComponent; kd, kdp: SHORTINT;
BEGIN
IF m IS MouseMessage THEN
WITH m: MouseMessage DO
IF m.keys = {} THEN
IF lastMouseKeys # {} THEN
IF pointerOwner = SELF THEN
PointerUp(m.x, m.y, {})
ELSE
DEC(m.x, pointerOwner.GetX()); DEC(m.y, pointerOwner.GetY());
pointerOwner.ProcessMessage(m);
END;
lastMouseKeys := {}
ELSE
newPointerOwner := PositionOwner(m.x, m.y);
IF newPointerOwner # pointerOwner THEN
pointerOwner.lastMouseKeys := m.keys;
pointerOwner.PointerLeave(); pointerOwner := newPointerOwner END
END
ELSE
IF lastMouseKeys = {} THEN
IF pointerOwner # focusOwner THEN
IF focusOwner # NIL THEN focusOwner.FocusLost() END;
focusOwner := pointerOwner;
focusOwner.FocusReceived()
END
END
END;
IF pointerOwner = SELF THEN
IF lastMouseKeys # m.keys THEN
kd := 0;
IF 0 IN m.keys THEN INC(kd) END;
IF 1 IN m.keys THEN INC(kd) END;
IF 2 IN m.keys THEN INC(kd) END;
kdp := 0;
IF 0 IN lastMouseKeys THEN INC(kdp) END;
IF 1 IN lastMouseKeys THEN INC(kdp) END;
IF 2 IN lastMouseKeys THEN INC(kdp) END;
IF kd < kdp THEN PointerUp(m.x, m.y, m.keys)
ELSE PointerDown(m.x, m.y, m.keys)
END;
lastMouseKeys := m.keys
ELSE
PointerMove(m.x, m.y, m.keys)
END
ELSE
DEC(m.x, pointerOwner.GetX()); DEC(m.y, pointerOwner.GetY());
pointerOwner.ProcessMessage(m)
END
END
ELSIF m IS KeyMessage THEN
WITH m: KeyMessage DO
IF (focusOwner = SELF) OR (focusOwner = NIL) THEN
KeyPressed(m.ch, m.flags, m.keySym)
ELSIF (focusOwner # SELF) & (focusOwner # NIL) THEN
IF m.keySym >= 0 THEN focusOwner.ProcessMessage(m) END
END
END
END
END ProcessMessage;
PROCEDURE ComputeDimensions*;
VAR contents: XMLObjects.Enumerator; content: ANY;
BEGIN
properties(CSS2Properties.VisualProperties).ComputeDimensions();
contents := GetContents();
WHILE contents.HasMoreElements() DO
content := contents.GetNext();
IF content IS VisualComponent THEN content(VisualComponent).ComputeDimensions() END
END;
ComputeWidths();
ComputeHeights()
END ComputeDimensions;
PROCEDURE Format*;
VAR contents: XMLObjects.Enumerator; content: ANY;
BEGIN
ComputeWidths();
ComputeHeights();
IF GetNumberOfContents() > 0 THEN
contents := GetContents();
WHILE contents.HasMoreElements() DO
content := contents.GetNext();
IF (content IS VisualComponent)
& (content(VisualComponent).properties(CSS2Properties.VisualProperties).
visibility.computed = CSS2Properties.Visible) THEN
content(VisualComponent).Format() END
END
END
END Format;
PROCEDURE Resized*;
VAR parent: XML.Element;
BEGIN
parent := GetParent();
IF (parent # NIL) & (parent IS VisualComponent) THEN parent(VisualComponent).Resized() END;
END Resized;
PROCEDURE PropertiesChanged;
BEGIN
PropertiesChanged^();
IF properties(CSS2Properties.VisualProperties).dimensionChanged THEN Resized() END
END PropertiesChanged;
PROCEDURE InvalidateRange*(x, y, w, h: LONGINT);
VAR parent: XML.Element;
BEGIN
IF IsLocked() THEN
IF invalidBox.IsEmpty() THEN invalidBox.SetRect(x, y, w, h)
ELSE invalidBox.ExtendRect(x, y, w, h)
END
ELSE
parent := GetParent();
IF (parent # NIL) THEN
parent(VisualComponent).InvalidateRange(bounds.x + x, bounds.y + y, w, h)
END
END
END InvalidateRange;
PROCEDURE Invalidate;
BEGIN
Invalidate^();
InvalidateRange(0, 0, bounds.w, bounds.h)
END Invalidate;
PROCEDURE Draw*(ctxt: Gfx.Context);
VAR enum: XMLObjects.Enumerator; p: ANY; state1, state2: Gfx.State; llx, lly, urx, ury: REAL;
BEGIN
IF properties(CSS2Properties.VisualProperties).overflow.computed = CSS2Properties.Hidden THEN
Gfx.Save(ctxt, {Gfx.clip}, state1);
Gfx.DrawRect(ctxt, contentBox.x, contentBox.y, contentBox.x + contentBox.w, contentBox.y + contentBox.h,
{Gfx.Clip})
END;
Gfx.GetClipRect(ctxt, llx, lly, urx, ury);
IF (0 < urx) & (llx < bounds.w) & (0 < ury) & (lly < bounds.h) & (GetNumberOfContents() > 0) THEN
enum := GetContents();
WHILE enum.HasMoreElements() DO
p := enum.GetNext();
IF p IS VisualComponent THEN
WITH p: VisualComponent DO
IF (p.GetX() < urx) & (llx < p.GetX() + p.GetWidth()) & (p.GetY() < ury) & (lly < p.GetY() + p.GetHeight())
& (p.properties(CSS2Properties.VisualProperties).visibility.computed = CSS2Properties.Visible) THEN
Gfx.Save(ctxt, {Gfx.ctm, Gfx.clip}, state2);
Gfx.Translate(ctxt, p.GetX(), p.GetY());
p.Draw(ctxt);
Gfx.Restore(ctxt, state2)
END
END
END
END
END;
IF properties(CSS2Properties.VisualProperties).overflow.computed = CSS2Properties.Hidden THEN
Gfx.Restore(ctxt, state1)
END
END Draw;
END VisualComponent;
PROCEDURE NoReportError(pos, line, row: LONGINT; msg: ARRAY OF CHAR);
END NoReportError;
PROCEDURE NewString*(value: ARRAY OF CHAR): String;
VAR s: String;
BEGIN
NEW(s, DynamicStrings.StringLength(value) + 1);
COPY(value, s^);
RETURN s
END NewString;
PROCEDURE Min*(a, b: LONGINT): LONGINT;
BEGIN
IF a > b THEN RETURN b ELSE RETURN a END
END Min;
PROCEDURE Max*(a, b: LONGINT): LONGINT;
BEGIN
IF a < b THEN RETURN b ELSE RETURN a END
END Max;
PROCEDURE ExtractEvent(VAR string: ARRAY OF CHAR): String;
VAR i, j, len: LONGINT; event: String;
BEGIN
len := DynamicStrings.StringLength(string);
i := len - 1;
WHILE (i >= 0) & (string[i] # ':') DO DEC(i) END; j := 0;
IF i >= 0 THEN
NEW(event, len - i);
FOR i := i + 1 TO len - 1 DO event[j] := string[i]; INC(j) END
ELSE
NEW(event, 1)
END;
event[j] := 0X;
RETURN event
END ExtractEvent;
END XMLComponents.