MODULE WinMechanisms;
(**
	project	= "BlackBox"
	organization	= "www.oberon.ch"
	contributors	= "Oberon microsystems"
	version	= "System/Rsrc/About"
	copyright	= "System/Rsrc/About"
	license	= "Docu/BB-License"
	changes	= "
	- 20070131, bh, Unicode support
	- 20151204, center #91, bug fix in HostMechanisms.PopUpAndSelect
	"
	issues	= "
	- ...
	"

**)

	IMPORT COM, 
		WinApi, WinOle, OleData, 
		Services, Ports, Stores, Views, Controllers, Properties, 
		Dialog, Mechanisms, Containers, Documents, Windows, 
		HostPorts := WinPorts;


	CONST
		handleSize = Ports.point * 11 DIV 2;
		clipMiddleHandle = handleSize DIV 2 + 2 * Ports.point;

		escape = -2;
		
		fixed = 31;	(* controller option *)


	TYPE
		IDropSource = POINTER TO RECORD (WinOle.IDropSource)
			key: SET
		END;
		
		IDropTarget = POINTER TO RECORD (WinOle.IDropTarget)
			win: Windows.Window;
			wnd: WinApi.HWND;
			x, y: INTEGER;
			key, effect: SET;
			source: Views.Frame;
			srcX, srcY: INTEGER;
			type: Stores.TypeName;
			isSingle: BOOLEAN;
			w, h, rx, ry: INTEGER
		END;
		
		Hook = POINTER TO RECORD (Mechanisms.Hook) END;
		
	
	VAR
		sourceFrame: Views.Frame;	(* source of drag & drop *)
		sourceX, sourceY: INTEGER;
		targetFrame: Views.Frame;	(* target of drag & drop *)
		targetX, targetY: INTEGER;
		dropView: Views.View;	(* view to drop *)
		isSingleton: BOOLEAN;
		dropW, dropH: INTEGER;
		relX, relY: INTEGER;
		
	
	(** focus borders **)

	PROCEDURE Fixed (host: Views.Frame; v: Views.View): BOOLEAN;
		VAR sp: Properties.ResizePref; c: Containers.Controller;
	BEGIN
		c := host.view(Containers.View).ThisController();
		IF c.opts * {Containers.noCaret, Documents.pageWidth..Documents.winHeight, fixed} # {} THEN
			RETURN TRUE
		END;
		sp.fixed := FALSE; Views.HandlePropMsg(v, sp); RETURN sp.fixed
	END Fixed;

	PROCEDURE PaintFocusBorder (f: Views.Frame; focus: Views.View; l, t, r, b: INTEGER);
		VAR u, s,  w, h,  mx, my,  l0, t0, r0, b0: INTEGER;
		
		PROCEDURE PaintHandle (x, y: INTEGER; l, t, r, b: BOOLEAN);
		BEGIN
			IF l THEN f.DrawRect(x - u, y, x, y + s, Ports.fill, Ports.background) END;
			IF t THEN f.DrawRect(x, y - u, x + s, y, Ports.fill, Ports.background) END;
			IF r THEN f.DrawRect(x + s, y, x + s + u, y + s, Ports.fill, Ports.background) END;
			IF b THEN f.DrawRect(x, y + s, x + s, y + s + u, Ports.fill, Ports.background) END;
			f.DrawRect(x, y, x + s, y + s, Ports.fill, Ports.defaultColor)
		END PaintHandle;
		
	BEGIN
		f.rider.GetRect(l0, t0, r0, b0);
		s := (handleSize - f.dot) DIV f.unit;
		f.rider.SetRect(l0 - s, t0 - s, r0 + s, b0 + s);
		u := f.dot; s := s * f.unit;
		w := r - l; h := b - t;
		f.DrawRect(l, t - s, r, t, Ports.fill, Ports.background);
		f.DrawRect(l, b, r, b + s, Ports.fill, Ports.background);
		f.DrawRect(l - s, t - s, l, b + s, Ports.fill, Ports.background);
		f.DrawRect(r, t - s, r + s, b + s, Ports.fill, Ports.background);
		DEC(s, u);
		f.MarkRect(l, t - s, r, t, Ports.fill, HostPorts.focusPat, Ports.show);
		f.MarkRect(l, b, r, b + s, Ports.fill, HostPorts.focusPat, Ports.show);
		f.MarkRect(l - s, t - s, l, b + s, Ports.fill, HostPorts.focusPat, Ports.show);
		f.MarkRect(r, t - s, r + s, b + s, Ports.fill, HostPorts.focusPat, Ports.show);
		IF ~Fixed(f, focus) THEN
			PaintHandle(l - s, t - s, FALSE, FALSE, TRUE, TRUE);
			PaintHandle(r, t - s, TRUE, FALSE, FALSE, TRUE);
			PaintHandle(l - s, b, FALSE, TRUE, TRUE, FALSE);
			PaintHandle(r, b, TRUE, TRUE, FALSE, FALSE);
			IF w > 2 * clipMiddleHandle THEN
				mx := (l + r - s) DIV 2;
				PaintHandle(mx, t - s, TRUE, FALSE, TRUE, FALSE);
				PaintHandle(mx, b, TRUE, FALSE, TRUE, FALSE)
			END;
			IF h > 2 * clipMiddleHandle THEN
				my := (t + b - s) DIV 2;
				PaintHandle(l - s, my, FALSE, TRUE, FALSE, TRUE);
				PaintHandle(r, my, FALSE, TRUE, FALSE, TRUE)
			END
		END;
		f.DrawRect(l - u, t - u, r + u, b + u, u, Ports.defaultColor);
		f.rider.SetRect(l0, t0, r0, b0)
	END PaintFocusBorder;

	PROCEDURE (hook: Hook) FocusBorderCursor* (f: Views.Frame; view: Views.View; l, t, r, b: INTEGER;
									x, y: INTEGER): INTEGER;
		VAR s, u, w, h, mx, my, cursor: INTEGER;
		
		PROCEDURE CheckHandle (x0, y0: INTEGER; c: INTEGER);
		BEGIN
			IF (x >= x0 - u) & (x <= x0 + s) & (y >= y0 - u) & (y <= y0 + s) THEN
				cursor := c
			END
		END CheckHandle;
		
	BEGIN
		u := f.dot; s := handleSize - 2 * u;
		IF (x < l - s) OR (x > r + s) OR (y < t - s) OR (y > b + s) THEN
			cursor := Mechanisms.outside
		ELSE
			cursor := Mechanisms.inside
		END;
		w := r - l; h := b - t;
		IF ~Fixed(f, view) THEN
			CheckHandle(l - s, t - s, HostPorts.resizeLCursor);
			CheckHandle(r, t - s, HostPorts.resizeRCursor);
			CheckHandle(l - s, b, HostPorts.resizeRCursor);
			CheckHandle(r, b, HostPorts.resizeLCursor);
			IF w > 2 * clipMiddleHandle THEN
				mx := (l + r - s) DIV 2;
				CheckHandle(mx, t - s, HostPorts.resizeVCursor);
				CheckHandle(mx, b, HostPorts.resizeVCursor)
			END;
			IF h > 2 * clipMiddleHandle THEN
				my := (t + b - s) DIV 2;
				CheckHandle(l - s, my, HostPorts.resizeHCursor);
				CheckHandle(r, my, HostPorts.resizeHCursor)
			END
		END;
		RETURN cursor
	END FocusBorderCursor;

	PROCEDURE RestoreBorderArea (f: Views.Frame; l, t, r, b: INTEGER);
	(* restore area under destructive border mark *)
		VAR g: Views.RootFrame; s, dx, dy: INTEGER;
	BEGIN
		g := Views.RootOf(f);
		dx := f.gx - g.gx; dy := f.gy - g.gy;
		s := (handleSize - f.dot) DIV f.unit * f.unit;
		INC(l, dx); INC(t, dy); INC(r, dx); INC(b, dy);
(*
		Views.UpdateRoot(g, l - s, t - s, r + s, b + s, FALSE);
*)
		Views.ValidateRoot(g);
		Views.RestoreRoot(g, l - s, t - s, r + s, b + s);
(*
		Views.RestoreRoot(g, l - s, t - s, r + s, t);
		Views.RestoreRoot(g, l - s, t, l, b);
		Views.RestoreRoot(g, r, t, r + s, b);
		Views.RestoreRoot(g, l - s, b, r + s, b + s)
*)
	END RestoreBorderArea;
	
	PROCEDURE (hook: Hook) MarkFocusBorder* (
		host: Views.Frame; focus: Views.View; l, t, r, b: INTEGER; show: BOOLEAN
	);
	BEGIN
		IF focus # NIL THEN
			IF show THEN
				PaintFocusBorder(host, focus, l, t, r, b)
			ELSE
				RestoreBorderArea(host, l, t, r, b)
			END
		END
	END MarkFocusBorder;
	

	(** selection borders **)

	PROCEDURE PaintSelBorder (f: Views.Frame; view: Views.View; l, t, r, b: INTEGER);
		VAR u, d, w, h,  mx, my, l0, t0, r0, b0: INTEGER; sizeable: BOOLEAN;
		
		PROCEDURE PaintHandle (x, y: INTEGER);
			VAR s: INTEGER; ci, co: Ports.Color;
		BEGIN
			DEC(x, d); DEC(y, d); s := d * 2 + u;
			IF sizeable THEN ci := HostPorts.selBackground; co := HostPorts.selTextCol
			ELSE ci := HostPorts.selTextCol; co := HostPorts.selBackground
			END;
			f.DrawRect(x, y, x + s, y + s, Ports.fill, co);
			INC(x, u); INC(y, u); DEC(s, 2 * u);
			f.DrawRect(x, y, x + s, y + s, Ports.fill, ci);
(*			
			f.DrawRect(x, y, x + s, y + s, Ports.fill, ci);
			f.DrawRect(x, y, x + s, y + s, 0, co)
*)
		END PaintHandle;
		
	BEGIN
		d := (handleSize - f.dot) DIV f.unit DIV 2;
		f.rider.GetRect(l0, t0, r0, b0);
		f.rider.SetRect(l0 - d - 1, t0 - d - 1, r0 + d + 1, b0 + d + 1);
		d := d * f.unit; u := f.dot;
		w := r - l; h := b - t; sizeable := ~Fixed(f, view);
		DEC(l, u); DEC(t, u);
(*
		f.SaveRect(l - d, t - d, r + u + d, b + u + d, res);
*)
		f.DrawRect(l, t, r + u, b + u, u, HostPorts.selBackground);
		IF f.front THEN
			IF (w > clipMiddleHandle) & (h > clipMiddleHandle) THEN
				PaintHandle(l, t);
				PaintHandle(r, t);
				PaintHandle(l, b);
				PaintHandle(r, b);
				IF w > 2 * clipMiddleHandle THEN
					mx := (l + r) DIV 2;
					PaintHandle(mx, t);
					PaintHandle(mx, b)
				END;
				IF h > 2 * clipMiddleHandle THEN
					my := (t + b) DIV 2;
					PaintHandle(l, my);
					PaintHandle(r, my)
				END
			ELSIF sizeable THEN
				PaintHandle(r, b)
			END
		END;
		f.rider.SetRect(l0, t0, r0, b0)
	END PaintSelBorder;

	PROCEDURE (hook: Hook) SelBorderCursor* (f: Views.Frame; view: Views.View; l, t, r, b: INTEGER;
									x, y: INTEGER): INTEGER;
		VAR d, u, w, h, mx, my, cursor: INTEGER;
		
		PROCEDURE CheckHandle (x0, y0: INTEGER; c: INTEGER);
		BEGIN
			IF (x >= x0 - d) & (x <= x0 + d) & (y >= y0 - d) & (y <= y0 + d) THEN
				cursor := c
			END
		END CheckHandle;
		
	BEGIN
		IF (x < l) OR (x > r) OR (y < t) OR (y > b) THEN cursor := Mechanisms.outside
		ELSE cursor := Mechanisms.inside
		END;
		IF (view # NIL) & ~Fixed(f, view) THEN
			d := (handleSize - f.dot) DIV f.unit DIV 2 * f.unit;
			w := r - l; h := b - t; u := f.dot;
			DEC(l, u); DEC(t, u);
			IF (w > clipMiddleHandle) & (h > clipMiddleHandle) THEN
				CheckHandle(l, t, HostPorts.resizeLCursor);
				CheckHandle(r, t, HostPorts.resizeRCursor);
				CheckHandle(l, b, HostPorts.resizeRCursor);
				CheckHandle(r, b, HostPorts.resizeLCursor);
				IF w > 2 * clipMiddleHandle THEN
					mx := (l + r) DIV 2;
					CheckHandle(mx, t, HostPorts.resizeVCursor);
					CheckHandle(mx, b, HostPorts.resizeVCursor)
				END;
				IF h > 2 * clipMiddleHandle THEN
					my := (t + b) DIV 2;
					CheckHandle(l, my, HostPorts.resizeHCursor);
					CheckHandle(r, my, HostPorts.resizeHCursor)
				END
			ELSE
				CheckHandle(r, b, HostPorts.resizeLCursor)
			END
		END;
		RETURN cursor
	END SelBorderCursor;

	PROCEDURE RestoreViewArea (f: Views.Frame; l, t, r, b: INTEGER);
	(* restore area under destructive selection mark *)
		VAR g: Views.RootFrame; d, dx, dy: INTEGER;
	BEGIN
(*
		d := (handleSize - f.dot) DIV f.unit DIV 2 * f.unit + f.dot;
		f.RestoreRect(l - d, t - d, r + d, b + d, TRUE)
*)
		g := Views.RootOf(f);
		dx := f.gx - g.gx; dy := f.gy - g.gy;
		d := (handleSize - f.dot) DIV f.unit DIV 2 * f.unit + f.dot;
		INC(l, dx); INC(t, dy); INC(r, dx); INC(b, dy);
		Views.ValidateRoot(g);
		Views.RestoreRoot(g, l - d, t - d, r + d, b + d)
	END RestoreViewArea;

	PROCEDURE (hook: Hook) MarkSingletonBorder* (
		host: Views.Frame; view: Views.View; l, t, r, b: INTEGER; show: BOOLEAN
	);
	BEGIN
		IF view # NIL THEN
			IF show THEN
				PaintSelBorder(host, view, l, t, r, b)
			ELSE
				RestoreViewArea(host, l, t, r, b)
			END
		END
(*		
		IF view # NIL THEN InvertSelBorder(host, view, l, t, r, b, show) END
*)
	END MarkSingletonBorder;


(*
	PROCEDURE MarkBorder* (host: Ports.Frame; view: Stores.Store; l, t, r, b: INTEGER);
		VAR s: INTEGER;
	BEGIN
		IF view # NIL THEN 
			s := markBorderSize * host.dot;
			host.MarkRect(l - s, t - s, r + s, b + s, s, Ports.dim50, Ports.show)
		END
	END MarkBorder;
*)

	PROCEDURE (hook: Hook) TrackToResize* (host: Views.Frame; view: Views.View;
										minW, maxW, minH, maxH: INTEGER;
										VAR l, t, r, b: INTEGER; VAR op: INTEGER;
										VAR x, y: INTEGER; VAR buttons: SET);
		VAR isDown: BOOLEAN; m: SET; p: Properties.SizePref;
			x1, y1,  dx, dy,  dl, dt, dr, db,  l0, t0, r0, b0,  l1, t1, r1, b1,  w, h,  dw, dh: INTEGER;
	BEGIN
		l0 := l; t0 := t; r0 := r; b0 := b;  dl := 0; dt := 0; dr := 0; db := 0;
		x1 := (l + r) DIV 2; y1 := (t + b) DIV 2;
		IF (r - l <= 2 * clipMiddleHandle) OR (ABS(x - x1) > handleSize DIV 2) THEN
			IF x < x1 THEN dl := 1 ELSE dr := 1 END
		END;
		IF (b - t <= 2 * clipMiddleHandle) OR (ABS(y - y1) > handleSize DIV 2) THEN
			IF y < y1 THEN dt := 1 ELSE db := 1 END
		END;
		IF (Controllers.extend IN buttons) & (dl # dr) THEN dl := 1; dr := 1 END;
		IF (Controllers.extend IN buttons) & (dt # db) THEN dt := 1; db := 1 END;
		host.MarkRect(l, t, r, b, 0, Ports.dim50, Ports.show);
		REPEAT
			host.Input(x1, y1, m, isDown);
			IF x1 < host.l THEN x1 := host.l ELSIF x1 > host.r THEN x1 := host.r END;
			IF y1 < host.t THEN y1 := host.t ELSIF y1 > host.b THEN y1 := host.b END;
			dx := x1 - x; dy := y1 - y;
			l1 := l0 + dl * dx; t1 := t0 + dt * dy; r1 := r0 + dr * dx; b1 := b0 + db * dy;
			w := r1 - l1; h := b1 - t1;
			IF (w > 0) & (h > 0) THEN
				p.fixedH := (dl = 0) & (dr = 0); p.fixedW := (dt = 0) & (db = 0);
				p.w := w; p.h := h; Views.HandlePropMsg(view, p); w := p.w; h := p.h;
				IF w < minW THEN w := minW ELSIF w > maxW THEN w := maxW END;
				IF h < minH THEN h := minH ELSIF h > maxH THEN h := maxH END;
				dw := w - (r1 - l1); dh := h - (b1 - t1);
				DEC(l1, dl * dw); DEC(t1, dt * dh);
				IF (dl + dr = 0) & (dw # 0) THEN INC(r1, dw) ELSE INC(r1, dr * dw) END;
				IF (dt + db = 0) & (dh # 0) THEN INC(b1, dh) ELSE INC(b1, db * dh) END;
				IF (l1 # l) OR (t1 # t) OR (r1 # r) OR (b1 # b) THEN
					host.MarkRect(l, t, r, b, 0, Ports.dim50, Ports.hide);
					l := l1; t := t1; r := r1; b := b1;
					host.MarkRect(l, t, r, b, 0, Ports.dim50, Ports.show)
				END
			END
		UNTIL ~isDown;
		host.MarkRect(l, t, r, b, 0, Ports.dim50, Ports.hide);
		x := x1; y := y1; buttons := {};
		IF (l # l0) OR (t # t0) OR (r # r0) OR (b # b0) THEN op := Mechanisms.resize
		ELSE op := Mechanisms.cancelResize
		END
	END TrackToResize;

	
	(* IDropSource *)
	
	PROCEDURE (this: IDropSource) QueryContinueDrag (
		escapePressed: WinApi.BOOL; keyState: SET
	): COM.RESULT;
	BEGIN
		IF this.key = {} THEN
			this.key := keyState * (WinApi.MK_LBUTTON + WinApi.MK_MBUTTON + WinApi.MK_RBUTTON)
		END;
		IF escapePressed # 0 THEN RETURN WinApi.DRAGDROP_S_CANCEL
		ELSIF keyState * this.key = {} THEN RETURN WinApi.DRAGDROP_S_DROP
		ELSE RETURN WinApi.S_OK
		END
	END QueryContinueDrag;
	
	PROCEDURE (this: IDropSource) GiveFeedback (effect: SET): COM.RESULT;
	BEGIN
		RETURN WinApi.DRAGDROP_S_USEDEFAULTCURSORS
	END GiveFeedback;
	

	(* IDropTarget *)

	PROCEDURE InstallDropTarget* (wnd: WinApi.HWND; win: Windows.Window);
		VAR drop: IDropTarget; res: COM.RESULT;
	BEGIN
		NEW(drop); drop.win := win; drop.wnd := wnd;
		res := WinOle.RegisterDragDrop(wnd, drop)
	END InstallDropTarget;
	
	PROCEDURE RemoveDropTarget* (wnd: WinApi.HWND);
		VAR res: COM.RESULT;
	BEGIN
		res := WinOle.RevokeDragDrop(wnd)
	END RemoveDropTarget;
	
	PROCEDURE PollDrop (d: IDropTarget; show: BOOLEAN);
		VAR msg: Controllers.PollDropMsg; w, h: INTEGER;
	BEGIN
		(* x, y in port coordinates of w *)
		d.win.port.GetSize(w, h);
		msg.x := d.x * d.win.port.unit;
		msg.y := d.y * d.win.port.unit;
		(* msg.x, msg.y in coordinates of target root frame *)
		msg.source := d.source; msg.sourceX := d.srcX; msg.sourceY := d.srcY;
		msg.mark := Controllers.mark; msg.show := show;
		msg.type := d.type; msg.isSingle := d.isSingle;
		msg.w := d.w; msg.h := d.h; msg.rx := d.rx; msg.ry := d.ry;
		msg.dest := NIL;
		d.win.ForwardCtrlMsg(msg);
		(* msg.x, msg.y in coordinates of target frame *)
		targetFrame := msg.dest; targetX := msg.x; targetY := msg.y
	END PollDrop;

	PROCEDURE Drop (d: IDropTarget; v: Views.View; w, h: INTEGER; isSingle: BOOLEAN);
		VAR msg: Controllers.DropMsg; pw, ph: INTEGER;
	BEGIN
		(* x, y in port coordinates of w *)
		d.win.port.GetSize(pw, ph);
		msg.x := d.x * d.win.port.unit;
		msg.y := d.y * d.win.port.unit;
		(* msg.x, msg.y in coordinates of target root frame *)
		msg.source := d.source; msg.sourceX := d.srcX; msg.sourceY := d.srcY;
		msg.view := v; msg.isSingle := isSingle;
		msg.w := w; msg.h := h; msg.rx := d.rx; msg.ry := d.ry;
		d.win.ForwardCtrlMsg(msg)
	END Drop;

	PROCEDURE AppendMenu (menu: WinApi.HANDLE; id: INTEGER; name: Dialog.String);
		VAR res: INTEGER;
	BEGIN
		Dialog.MapString(name, name);
		res := WinApi.AppendMenuW(menu, {}, id, name)
	END AppendMenu;

	PROCEDURE ShowPopup (f: Views.Frame; x, y: INTEGER; VAR effect: SET);
		VAR res: INTEGER; menu, wnd: WinApi.HANDLE; msg: WinApi.MSG; pt: WinApi.POINT;
	BEGIN
		menu := WinApi.CreatePopupMenu();
		AppendMenu(menu, 32000, "#System:MoveHere");
		AppendMenu(menu, 32001, "#System:CopyHere");
		AppendMenu(menu, 32002, "#System:LinkHere");
		IF effect * WinOle.DROPEFFECT_MOVE = {} THEN
			res := WinApi.EnableMenuItem(menu, 32000, WinApi.MF_GRAYED)
		END;
		IF effect * WinOle.DROPEFFECT_COPY = {} THEN
			res := WinApi.EnableMenuItem(menu, 32001, WinApi.MF_GRAYED)
		END;
		IF effect * WinOle.DROPEFFECT_LINK = {} THEN
			res := WinApi.EnableMenuItem(menu, 32002, WinApi.MF_GRAYED)
		END;
		res := WinApi.AppendMenuW(menu, WinApi.MF_SEPARATOR, 0, NIL);
		AppendMenu(menu, 32003, "#System:Cancel");
		wnd := f.rider(HostPorts.Rider).port.wnd;
		pt.x := (x + f.gx) DIV f.unit; pt.y := (y + f.gy) DIV f.unit;
		res := WinApi.ClientToScreen(wnd, pt);
		res := WinApi.TrackPopupMenu(menu, {1}, pt.x, pt.y, 0, wnd, NIL);
		res := WinApi.DestroyMenu(menu);
		effect := WinOle.DROPEFFECT_NONE;
		IF WinApi.PeekMessageW(msg, wnd, WinApi.WM_COMMAND, WinApi.WM_COMMAND, 1) # 0 THEN
			IF msg.wParam = 32000 THEN effect := WinOle.DROPEFFECT_MOVE
			ELSIF msg.wParam = 32001 THEN effect := WinOle.DROPEFFECT_COPY
			ELSIF msg.wParam = 32002 THEN effect := WinOle.DROPEFFECT_LINK
			END
		END
	END ShowPopup;
	
	PROCEDURE Effect (mask, keyState: SET): SET;
		VAR effect: SET;
	BEGIN
		IF (mask * WinOle.DROPEFFECT_LINK # {})
			& (((WinApi.MK_SHIFT + WinApi.MK_CONTROL) - keyState = {})
				OR (mask * (WinOle.DROPEFFECT_MOVE + WinOle.DROPEFFECT_COPY) = {})) THEN
					effect := WinOle.DROPEFFECT_LINK
		ELSIF (mask * WinOle.DROPEFFECT_COPY # {})
			& ((keyState * WinApi.MK_CONTROL # {})
				OR ((WinApi.MK_LBUTTON + WinApi.MK_RBUTTON) - keyState = {})
				OR (mask * WinOle.DROPEFFECT_MOVE = {})) THEN effect := WinOle.DROPEFFECT_COPY
		ELSIF mask * WinOle.DROPEFFECT_MOVE # {} THEN effect := WinOle.DROPEFFECT_MOVE
		ELSE effect := WinOle.DROPEFFECT_NONE
		END;
		RETURN effect
	END Effect;
	
	PROCEDURE (this: IDropTarget) DragEnter (dataObj: WinOle.IDataObject; keyState: SET; pt: WinApi.POINT;
																VAR effect: SET): COM.RESULT;
		VAR res: INTEGER; s: BOOLEAN;
	BEGIN
		IF sourceFrame # NIL THEN
			this.source := sourceFrame;
			this.srcX := sourceX; this.srcY := sourceY;
			Services.GetTypeName(dropView, this.type);
			this.isSingle := isSingleton;
			this.w := dropW; this.h := dropH;
			this.rx := relX; this.ry := relY
		ELSE
			OleData.GetDataType(dataObj, this.type, this.w, this.h, this.rx, this.ry, this.isSingle);
			this.source := NIL
		END;
		IF (this.type # "") & (this.win # NIL) THEN
			res := WinApi.ScreenToClient(this.wnd, pt);
			this.x := pt.x; this.y := pt.y; this.key := keyState;
			PollDrop(this, Controllers.show)
		END;
		IF (this.type # "") & (this.win = NIL) OR (targetFrame # NIL) THEN
			effect := Effect(effect, keyState)
		ELSE effect := WinOle.DROPEFFECT_NONE
		END;
		 this.effect := effect;
		RETURN WinApi.S_OK
	END DragEnter;
	
	PROCEDURE (this: IDropTarget) DragOver (keyState: SET; pt: WinApi.POINT; VAR effect: SET): COM.RESULT;
		VAR res: INTEGER;
	BEGIN
		IF (this.type # "") & (this.win # NIL) THEN
			res := WinApi.ScreenToClient(this.wnd, pt);
			IF (pt.x # this.x) OR (pt.y # this.y) THEN
				PollDrop(this, Controllers.hide);
				this.x := pt.x; this.y := pt.y;
				PollDrop(this, Controllers.show)
			END
		END;
		IF (this.type # "") & (this.win = NIL) OR (targetFrame # NIL) THEN
			effect := Effect(effect, keyState)
		ELSE effect := WinOle.DROPEFFECT_NONE
		END;
		this.effect := effect;
		RETURN WinApi.S_OK
	END DragOver;
	
	PROCEDURE (this: IDropTarget) DragLeave (): COM.RESULT;
	BEGIN
		IF (this.type # "") & (this.win # NIL) THEN
			PollDrop(this, Controllers.hide)
		END;
		targetFrame := NIL; this.source := NIL;
		RETURN WinApi.S_OK
	END DragLeave;
	
	PROCEDURE (this: IDropTarget) Drop (dataObj: WinOle.IDataObject; keyState: SET; pt: WinApi.POINT;
														VAR effect: SET): COM.RESULT;
		VAR res, w, h: INTEGER; v: Views.View; s: BOOLEAN;
			c: Containers.Controller; m: Containers.Model; p: Properties.BoundsPref;
	BEGIN
		IF this.effect # WinOle.DROPEFFECT_NONE THEN
			IF this.win # NIL THEN
				res := WinApi.ScreenToClient(this.wnd, pt);
				PollDrop(this, Controllers.hide);
				IF targetFrame # NIL THEN
					Windows.dir.Select(this.win, Windows.eager);
					IF WinApi.MK_LBUTTON * this.key = {} THEN	(* nonstandard drag *)
						ShowPopup(targetFrame, targetX, targetY, effect)
					ELSE effect := this.effect
					END;
					IF (effect # WinOle.DROPEFFECT_NONE) & (sourceFrame = NIL) THEN
						IF Services.Is(targetFrame.view, "TextViews.View") THEN
							OleData.GetTextDataView(dataObj, v, w, h, s)
						ELSE
							OleData.GetDataView(dataObj, "", v, w, h, s)
						END;
						IF v # NIL THEN Drop(this, v, w, h, s)
						ELSE effect := WinOle.DROPEFFECT_NONE
						END
					END
				ELSE effect := WinOle.DROPEFFECT_NONE
				END
			ELSE	(* drop to new window *)
				effect := this.effect;
				IF sourceFrame # NIL THEN
					w := dropW; h := dropH;
					IF isSingleton THEN
						v := Views.CopyOf(dropView, Views.deep)
					ELSE
						c := dropView(Containers.View).ThisController();
						m := c.SelectionCopy();
						v := Views.CopyWithNewModel(dropView, m);
						p.w := w; p.h := h; Views.HandlePropMsg(v, p); w := p.w; h := p.h
					END
				ELSE OleData.GetDataView(dataObj, "", v, w, h, s)
				END;
				IF v # NIL THEN Views.OpenView(Documents.dir.New(v, w, h));
				Views.BeginModification(Views.notUndoable, v);
				Views.EndModification(Views.notUndoable, v)
				ELSE effect := WinOle.DROPEFFECT_NONE
				END
			END
		ELSE effect := WinOle.DROPEFFECT_NONE
		END;
		RETURN WinApi.S_OK
	END Drop;
	
	
	(* drag & drop *)

	PROCEDURE (hook: Hook) TrackToDrop* (f: Views.Frame; view: Views.View;
									isSingle: BOOLEAN; w, h, rx, ry: INTEGER;
									VAR dest: Views.Frame; VAR destX, destY: INTEGER; VAR op: INTEGER;
									VAR x, y: INTEGER; VAR buttons: SET);
		VAR res: COM.RESULT; mask, mode: SET; p: HostPorts.Port; pt: WinApi.POINT;
			dsrc: IDropSource; data: WinOle.IDataObject;
	BEGIN
		sourceFrame := f; sourceX := x; sourceY := y;
		dropView := view; isSingleton := isSingle;
		dropW := w; dropH := h; relX := rx; relY := ry;
		data := OleData.ViewDropData(view, w, h, rx, ry, isSingle, ~isSingle);
		NEW(dsrc); dsrc.key := {};
		mask := WinOle.DROPEFFECT_COPY;
		IF op # Mechanisms.copy THEN mask := mask + WinOle.DROPEFFECT_MOVE END;
		res := WinOle.DoDragDrop(data, dsrc, mask, mode);
		op := Mechanisms.cancelDrop;
		IF res = WinApi.DRAGDROP_S_DROP THEN
			IF mode * WinOle.DROPEFFECT_MOVE # {} THEN op := Mechanisms.move
			ELSIF mode * WinOle.DROPEFFECT_COPY # {} THEN op := Mechanisms.copy
			END
		END;
		IF targetFrame # NIL THEN	(* reconstruct final mouse coordinates in f *)
			p := targetFrame.rider(HostPorts.Rider).port;
			pt.x := (targetX + targetFrame.gx) DIV p.unit;
			pt.y := (targetY + targetFrame.gy) DIV p.unit;
			res := WinApi.ClientToScreen(p.wnd, pt);
			p := f.rider(HostPorts.Rider).port;
			res := WinApi.ScreenToClient(p.wnd, pt);
			x := pt.x * p.unit - f.gx;
			y := pt.y * p.unit - f.gy
		END;
		dest := targetFrame; destX := targetX; destY := targetY;
		sourceFrame := NIL; targetFrame := NIL; dropView := NIL
	END TrackToDrop;

	PROCEDURE PickMode (f, dest: Views.Frame; x, y: INTEGER): INTEGER;
		VAR mode, cursor: INTEGER;
	BEGIN
		IF WinApi.GetAsyncKeyState(1BH) < 0 THEN mode := escape; cursor := Ports.arrowCursor
(*
		ELSIF Home(f, x, y) THEN mode := Mechanisms.cancelPick; cursor := Ports.arrowCursor
*)
		ELSIF dest = NIL THEN mode := Mechanisms.cancelPick; cursor := HostPorts.stopCursor
		ELSE
			cursor := HostPorts.pickCursor;
			IF Services.SameType(dest.view, f.view) THEN
				mode := Mechanisms.pick
			ELSE mode := Mechanisms.pickForeign
			END
		END;
		f.SetCursor(cursor);
		RETURN mode
	END PickMode;

	PROCEDURE (hook: Hook) TrackToPick* (f: Views.Frame;
									VAR dest: Views.Frame; VAR destX, destY: INTEGER; VAR op: INTEGER;
									VAR x, y: INTEGER; VAR buttons: SET);
		VAR d, d0: Views.Frame;
			dx, dy,  x0, y0,  x1, y1: INTEGER; isDown: BOOLEAN; m: SET;
	BEGIN
		x0 := x; y0 := y;
		Properties.PollPick(x, y, f, x0, y0, Properties.mark, Properties.show, dest, destX, destY);
		(* MarkTarget(dest, dest # f); *)
		op := PickMode(f, dest, x, y);
		REPEAT
			(* CheckWindow(TRUE); *)
			f.Input(x1, y1, m, isDown);
			IF (x1 # x) OR (y1 # y) THEN
				Properties.PollPick(x1, y1, f, x0, y0, Properties.noMark, Properties.show, d, dx, dy);
				IF (d # dest) OR (dx # destX) OR (dy # destY) THEN
					d0 := dest;
					(* MarkTarget(dest, (dest # f) & (d # d0)); *)
					Properties.PollPick(x, y, f, x0, y0, Properties.mark, Properties.hide, dest, destX, destY);
					x := x1; y := y1;
					Properties.PollPick(x, y, f, x0, y0, Properties.mark, Properties.show, d, dx, dy);
					dest := d; destX := dx; destY := dy;
					(* MarkTarget(dest, (dest # f) (* ~home *) & (d # d0)); *)
				ELSE
					Properties.PollPick(x, y, f, x0, y0, Properties.mark, Properties.hide, d, dx, dy);
					x := x1; y := y1;
					Properties.PollPick(x, y, f, x0, y0, Properties.mark, Properties.show, d, dx, dy)
				END
			END;
			op := PickMode(f, dest, x, y)
		UNTIL ~isDown OR (op = escape);
		Properties.PollPick(x, y, f, x0, y0, Properties.mark, Properties.hide, d, dx, dy);
		IF op = escape THEN
			REPEAT f.Input(x, y, m, isDown) UNTIL ~isDown;
			op := Mechanisms.cancelPick
		END;
		(* MarkTarget(dest, dest # f); *)
		(* CheckWindow(FALSE) *)
		buttons := {}
	END TrackToPick;


	PROCEDURE (hook: Hook) PopUpAndSelect* (f: Views.Frame;
										n, this: INTEGER;
										string: ARRAY OF ARRAY OF CHAR;
										enabled, checked: ARRAY OF BOOLEAN;
										VAR i: INTEGER;
										VAR x, y: INTEGER; VAR buttons: SET);
		VAR res, j: INTEGER; menu, wnd: WinApi.HANDLE; pt: WinApi.POINT;
			t: ARRAY 256 OF CHAR; s: SET;
	BEGIN
		ASSERT(0 < n, 20); ASSERT(n <= LEN(string), 21);
		ASSERT(LEN(enabled) = LEN(string), 22);
		ASSERT(LEN(checked) = LEN(string), 23);
		wnd := f.rider(HostPorts.Rider).port.wnd;
		ASSERT(wnd # 0, 100);
		menu := WinApi.CreatePopupMenu(); j := 0;
		WHILE j < n DO
			IF string[j] = "-" THEN
				res := WinApi.AppendMenuW(menu, WinApi.MF_SEPARATOR, 0, NIL)
			ELSE
				Dialog.MapString(string[j], t);
				res := WinApi.AppendMenuW(menu, {}, 32000 + j, t);
				IF ~enabled[j] THEN
					res := WinApi.EnableMenuItem(menu, 32000 + j, WinApi.MF_GRAYED)
				ELSIF checked[j] THEN
					res := WinApi.CheckMenuItem(menu, 32000 + j, WinApi.MF_CHECKED)
				END
			END;
			INC(j)
		END;
		pt.x := (x + f.gx) DIV f.unit; pt.y := (y + f.gy) DIV f.unit;
		res := WinApi.ClientToScreen(wnd, pt);
		s := {1, 2};	(* track right, center align *)
		res := WinApi.TrackPopupMenu(menu, s, pt.x, pt.y + 2, 0, wnd, NIL);
		res := WinApi.DestroyMenu(menu)
	END PopUpAndSelect;


	PROCEDURE Init*;
		VAR h: Hook;
	BEGIN
		NEW(h); Mechanisms.SetHook(h)
	END Init;

BEGIN
	Init
END WinMechanisms.
