MODULE WMDiff; (** AUTHOR "Ingmar Nebel"; PURPOSE "The difference is in the file text-file!"; *)

IMPORT
	Modules, WM := WMWindowManager, WMRestorable, WMStringGrids, WMGrids, WMGraphics, WMRectangles,
	WMComponents, WMStandardComponents, DiffLib, Streams, Commands, Strings, WMEvents, Raster,
	WMEditors, WMTextView, Texts, Files, UTF8Strings, WMMessages, WMScrollableComponents;

CONST
	Width = 1024;
	Height = 768;

	ShowLineNumbers = TRUE;

	DiffStateEmpty = 0;
	DiffStateCommon = 1;
	DiffStateDifferent = 2;

	ScrollbarWidth = 15;
	OverviewWidth = 10;

	DarkGreen = 000C000FFH;
	DarkRed = LONGINT(0C00000FFH);

	DiffColorLeft = LONGINT(0FF000030H);
	DiffColorRight = 00000FF30H;
	EmptyLineColor = WMGraphics.White;

	LineNbrBgColor = LONGINT(0E0E0FFFFH);
	BgColor = LONGINT(0CCCCCCFFH);

	MouseWheelMultiplier = 3;


	NonDefaultFont = FALSE;

	(* Font to be used when NonDefaultFont = TRUE *)
	FontName = "Oberon";
	FontSize = 10;
	FontStyle = {};

TYPE

	NoWheelGrid* = OBJECT(WMStringGrids.StringGrid);

		PROCEDURE WheelMove*(dz : LONGINT); (* do nothing *) END WheelMove;

		PROCEDURE &Init*;
		BEGIN
			Init^;
			SetNameAsString(StrNoWheelGrid);
		END Init;

	END NoWheelGrid;


	DiffState= OBJECT
	VAR state:LONGINT;
	END DiffState;

	DiffView = OBJECT (WMComponents.VisualComponent)
	VAR
		filenameEdit: WMEditors.Editor;
		lineNbr, grid: NoWheelGrid;
		colWidths : WMGrids.Spacings;
		commonLines, diffLines: LONGINT;
		diffColor, maxLineWidth, minLineWidth: LONGINT;
		curLine: LONGINT;
		font : WMGraphics.Font;
		onPropertyChange, onResized: WMEvents.EventSource;

		PROCEDURE CreateFilenamePanel() : WMStandardComponents.Panel;
		VAR panel : WMStandardComponents.Panel; label: WMStandardComponents.Label;
		BEGIN
			NEW(panel); panel.bounds.SetHeight(20); panel.alignment.Set(WMComponents.AlignTop);
			panel.fillColor.Set(WMGraphics.White);

			NEW(label); label.bounds.SetWidth(60); label.alignment.Set(WMComponents.AlignLeft);
			label.alignH.Set(WMComponents.AlignLeft);
			label.SetCaption("Filename:");
			label.textColor.Set(WMGraphics.Black);
			label.fillColor.Set(BgColor);

			NEW(filenameEdit); filenameEdit.alignment.Set(WMComponents.AlignClient);
			filenameEdit.multiLine.Set(FALSE);
			filenameEdit.fillColor.Set(LONGINT(0FFFFFFFFH));
			filenameEdit.tv.borders.Set(WMRectangles.MakeRect(3,3,1,1));
			filenameEdit.tv.showBorder.Set(TRUE);
			filenameEdit.tv.SetExtDragOverHandler(FileNameEditDragOver);
			filenameEdit.tv.SetExtDragDroppedHandler(FileNameEditDragDropped);

			panel.AddContent(label);
			panel.AddContent(filenameEdit);

			RETURN panel;
		END CreateFilenamePanel;

		PROCEDURE CreateGrid() : NoWheelGrid;
		VAR grid : NoWheelGrid;
		BEGIN
			NEW(grid);
			grid.alignment.Set(WMComponents.AlignLeft);
			grid.clTextDefault.Set(WMGraphics.Black);
			grid.SetSelectionMode(WMGrids.GridSelectNone);
			grid.showScrollY.Set(FALSE);
			grid.showScrollX.Set(FALSE);
			grid.adjustFocusPosition.Set(FALSE);
			grid.defaultRowHeight.Set(15);
			grid.cellDist.Set(0);

			IF NonDefaultFont THEN
				grid.SetFont(WMGraphics.GetFont(FontName, FontSize, FontStyle));
			END;

			grid.model.Acquire;
			grid.model.SetNofCols(1);
			grid.model.Release;

			RETURN grid;
		END CreateGrid;

		PROCEDURE &Init*;
		VAR scrollPanel : WMScrollableComponents.ScrollableContainer; VAR dx, dy : LONGINT;
		BEGIN
			Init^;
			SetNameAsString(StrDiffView);

			NEW(onPropertyChange, SELF, NIL, NIL, NIL);
			NEW(onResized, SELF, NIL, NIL, NIL);

			lineNbr := CreateGrid();
			IF ~ShowLineNumbers THEN lineNbr.visible.Set(FALSE); END;

			grid := CreateGrid();

			NEW(colWidths, 1); colWidths[0] := 256;

			AddContent(CreateFilenamePanel());
			AddContent(lineNbr);

			NEW(scrollPanel); scrollPanel.alignment.Set(WMComponents.AlignClient);
			scrollPanel.AddContent(grid);
			AddContent(scrollPanel);

			font := grid.GetFont();
			font.GetStringSize("0000", dx, dy); INC(dx, 4);

			lineNbr.bounds.SetWidth(dx);

			onPropertyChange.Call(NIL);
		END Init;

		PROCEDURE ToggleLineNumbers;
		BEGIN {EXCLUSIVE}
			lineNbr.visible.Set(~lineNbr.visible.Get());
			Invalidate;
		END ToggleLineNumbers;

		PROCEDURE SetNofLines(nofLines: LONGINT);
		BEGIN
			lineNbr.model.Acquire;
			grid.model.Acquire;
			lineNbr.model.SetNofRows(nofLines);
			grid.model.SetNofRows(nofLines);
			grid.model.Release;
			lineNbr.model.Release;
		END SetNofLines;

		PROCEDURE FileNameEditDragOver(x, y : LONGINT; dragInfo : WM.DragInfo; VAR handled : BOOLEAN);
		BEGIN
			filenameEdit.tv.SelectAll;
			filenameEdit.tv.SetFocus;
			handled := TRUE;
		END FileNameEditDragOver;

		PROCEDURE FileNameEditDragDropped(x, y : LONGINT; dragInfo : WM.DragInfo; VAR handled : BOOLEAN);
		VAR dropTarget : WMTextView.TextDropTarget; p : Texts.TextPosition;
		BEGIN
			filenameEdit.SetAsString("");
			filenameEdit.text.AcquireRead;
			NEW(p, filenameEdit.text); p.SetPosition(0);
			NEW(dropTarget, filenameEdit.text, p);
			filenameEdit.text.ReleaseRead;
			IF ~filenameEdit.tv.hasFocus & ~filenameEdit.tv.alwaysShowCursor.Get() THEN filenameEdit.tv.cursor.SetVisible(FALSE) END;
			dragInfo.data := dropTarget;
			ConfirmDrag(TRUE, dragInfo);
			handled := TRUE;
		END FileNameEditDragDropped;

		PROCEDURE FillLines(nofRows, nofLines: LONGINT);
		VAR i: LONGINT;
		BEGIN
			grid.model.Acquire;
			FOR i := nofRows TO nofLines DO
				lineNbr.model.InsertEmptyRow(i);
				grid.model.InsertEmptyRow(i);
			END;
			grid.model.Release;
		END FillLines;

		PROCEDURE InsertLine (pos, line: LONGINT; string: Strings.String; out : Streams.Writer);
		VAR intStr: ARRAY 32 OF CHAR; nofRows: LONGINT; diffState : DiffState; lineWidth, dy: LONGINT;
		BEGIN
			font.GetStringSize(string^, lineWidth, dy); INC(lineWidth,4);
			maxLineWidth := Strings.Max(lineWidth, maxLineWidth);
			lineNbr.model.Acquire;
			grid.model.Acquire;
			nofRows := grid.model.GetNofRows();
			IF nofRows <= curLine THEN
				FillLines(nofRows, curLine)
			END;
			(* line number *)
			Strings.IntToStr(line, intStr);
			lineNbr.model.SetCellText(0, curLine, Strings.NewString(intStr));
			lineNbr.model.SetTextAlign(0, curLine, WMGraphics.AlignCenter);
			lineNbr.model.SetCellColors(0, curLine, LineNbrBgColor, WMGraphics.Black);

			grid.model.SetCellText (0, curLine, string);
			grid.model.SetCellColors(0, curLine, diffColor, WMGraphics.Black);

			NEW(diffState); diffState.state := DiffStateDifferent;
			grid.model.SetCellData(0, curLine, diffState);
			grid.model.Release;
			lineNbr.model.Release;
			INC(curLine); INC(diffLines);
		END InsertLine;

		PROCEDURE InsertEmptyLine;
		VAR nofRows: LONGINT;
		BEGIN
			lineNbr.model.Acquire;
			grid.model.Acquire;
			nofRows  := grid.model.GetNofRows();
			IF nofRows <= curLine THEN
				FillLines(nofRows, curLine)
			END;
			lineNbr.model.SetCellText(0, curLine, NIL);
			lineNbr.model.SetCellColors(0, curLine, LineNbrBgColor, WMGraphics.Black);
			grid.model.SetCellText(0, curLine, NIL);
			grid.model.SetCellData(0, curLine, NIL);
			grid.model.SetCellColors(0, curLine, EmptyLineColor, WMGraphics.Black);
			grid.model.Release;
			lineNbr.model.Release;
			INC(curLine);
		END InsertEmptyLine;

		PROCEDURE InsertCommonLine (pos, line: LONGINT; string: Strings.String; out : Streams.Writer);
		VAR intStr: ARRAY 32 OF CHAR; nofRows, lineWidth, dy: LONGINT;
		BEGIN
			font.GetStringSize(string^, lineWidth, dy); INC(lineWidth, 4);
			maxLineWidth := Strings.Max(lineWidth, maxLineWidth);
			lineNbr.model.Acquire;
			grid.model.Acquire;
			nofRows := grid.model.GetNofRows();
			IF nofRows <= curLine THEN
				FillLines(nofRows, curLine)
			END;

			Strings.IntToStr(line, intStr);
			lineNbr.model.SetCellText(0, curLine, Strings.NewString(intStr));
			lineNbr.model.SetTextAlign(0, curLine, WMGraphics.AlignCenter);
			lineNbr.model.SetCellColors(0, curLine, LineNbrBgColor, WMGraphics.AlignCenter);

			grid.model.SetCellText (0, curLine, string);
			grid.model.SetCellData(0, curLine, NIL);
			grid.model.Release;
			lineNbr.model.Release;
			INC (commonLines); INC(curLine);
		END InsertCommonLine;

		PROCEDURE SetFileName(fileName: ARRAY OF CHAR);
		BEGIN
			filenameEdit.SetAsString(fileName);
		END SetFileName;

		PROCEDURE GetFileName(): Strings.String;
		VAR string, fileName: Files.FileName; count: LONGINT; sr: Streams.StringReader;
		BEGIN
			filenameEdit.GetAsString(string);
			count := UTF8Strings.UTF8toASCII(string, " ", fileName);
			(* parse filename *)
			NEW(sr, LEN(fileName)); sr.Set(fileName);
			sr.SkipWhitespace; sr.String(fileName);
			RETURN Strings.NewString(fileName);
		END GetFileName;

		PROCEDURE Setup;
		BEGIN
			filenameEdit.tv.defaultTextColor.Set(WMGraphics.Black);
			maxLineWidth := minLineWidth; commonLines  := 0; diffLines := 0; curLine := 0;
			SetNofLines(1);
		END Setup;

		PROCEDURE Update;
		BEGIN
			IF diffLines > 0 THEN
				filenameEdit.tv.defaultTextColor.Set(DarkRed);
			ELSE
				filenameEdit.tv.defaultTextColor.Set(DarkGreen);
			END;
			colWidths[0] := maxLineWidth;
			grid.SetColSpacings(colWidths);
			grid.bounds.SetWidth(maxLineWidth);
			SetNofLines(curLine);
			Invalidate;
		END Update;

		PROCEDURE Resized*;
		BEGIN
			Resized^;
			onResized.Call(NIL);
		END Resized;

	END DiffView;

TYPE

	Overview= OBJECT(WMComponents.VisualComponent)
	VAR
		view: DiffView;
		img: WMGraphics.Image;
		nofRows, pageStart, pageSize: LONGINT;
		showPage: BOOLEAN;
		diffColor : LONGINT;

		PROCEDURE &New*(view: DiffView);
		BEGIN
			Init;
			SetNameAsString(StrOverview);
			SELF.view:= view;
		END New;

		PROCEDURE SetPage(pageStart, pageSize: LONGINT);
		VAR barHeight: LONGINT;
		BEGIN
			IF nofRows < 1 THEN RETURN; END;
			showPage := TRUE;
			barHeight := bounds.GetHeight()- 2*ScrollbarWidth;
			pageStart := barHeight * pageStart DIV nofRows;
			pageSize := pageSize * barHeight DIV nofRows;
			SELF.pageStart := Strings.Max(pageStart, 0);
			SELF.pageSize := Strings.Min(barHeight-pageStart, Strings.Max(pageSize, 1));
			Invalidate;
		END SetPage;

		PROCEDURE Resized*;
		VAR rect: WMRectangles.Rectangle;
		BEGIN
			Resized^;
			NEW(img);
			rect := GetClientRect();
			INC(rect.t, ScrollbarWidth); DEC(rect.b, ScrollbarWidth);
			IF ~WMRectangles.RectEmpty(rect) THEN
				Raster.Create(img, rect.r-rect.l, rect.b-rect.t, Raster.BGR565);
				Update;
			END;
		END Resized;

		PROCEDURE Setup;
		VAR c: WMGraphics.BufferCanvas; rect: WMRectangles.Rectangle;
		BEGIN
			IF img # NIL THEN
				NEW(c,img);
				rect := WMRectangles.MakeRect(0,0,img.width, img.height);
				c.Fill(rect, LONGINT(0808080FFH), WMGraphics.ModeCopy);
				Invalidate;
			END;
		END Setup;

		PROCEDURE Update;
		VAR pos: LONGINT; fact: REAL; c: WMGraphics.BufferCanvas;
			rect : WMRectangles.Rectangle;
			i: LONGINT;
			data:ANY;
			prev: BOOLEAN;
		BEGIN
			IF img = NIL THEN
				NEW(img);
				Raster.Create(img, bounds.GetWidth(), bounds.GetHeight()- 2* ScrollbarWidth, Raster.BGR565);
			END;
			NEW(c,img);
			rect := WMRectangles.MakeRect(0, 0,img.width, img.height);
			c.Fill(rect, LONGINT(0808080FFH), WMGraphics.ModeCopy);
			view.grid.model.Acquire;
			nofRows:= view.grid.model.GetNofRows();
			IF nofRows > 0 THEN
				fact := img.height / nofRows;
				WMRectangles.SetRect(rect, 0, 0, img.width, 1);
				(* draw different lines in left View *)
				prev := FALSE;
				FOR i := 1 TO nofRows-1 DO
					data := view.grid.model.GetCellData(0, i);
					IF data # NIL THEN
						pos := ENTIER(fact * i + 0.5);
						IF ~prev THEN rect.t := pos; rect.b := pos + 1; prev := TRUE
						ELSE
							rect.b := Strings.Max(rect.b, pos+1);
						END;
					ELSE
						IF prev THEN
							c.Fill(rect, diffColor, WMGraphics.ModeCopy);
							prev := FALSE;
						END;
					END;
				END;
				IF prev THEN
					c.Fill(rect, diffColor, WMGraphics.ModeCopy);
					prev := FALSE;
				END;
			END;
			view.grid.model.Release;
			Invalidate;
		END Update;

		(** Is called before any sub-components are drawn *)
		PROCEDURE DrawBackground*(canvas : WMGraphics.Canvas);
		VAR rect: WMRectangles.Rectangle; (* x0, x1: LONGINT; *)
		BEGIN
			rect := GetClientRect();
			canvas.Fill(rect, LONGINT(0CCCCCCFFH), WMGraphics.ModeCopy);
			IF img # NIL THEN
				canvas.DrawImage(0,ScrollbarWidth,img, WMGraphics.ModeCopy);
				IF showPage THEN
					canvas.Fill(WMRectangles.MakeRect(rect.l, pageStart + ScrollbarWidth, rect.r, pageStart + pageSize + ScrollbarWidth), 040H, WMGraphics.ModeSrcOverDst);
				END
			END;
		END DrawBackground;

	END Overview;

TYPE

	KillerMsg = OBJECT
	END KillerMsg;

	Window = OBJECT (WMComponents.FormWindow)
	VAR
		leftView, rightView: DiffView;
		lblcommonLines, lbldiffLinesLeft, lbldiffLinesRight : WMStandardComponents.Label;
		scrolly : WMStandardComponents.Scrollbar;
		leftOverview, rightOverview: Overview;
		pageSize: LONGINT;

		PROCEDURE &New *(c : WMRestorable.Context);
		VAR vc : WMComponents.VisualComponent;
		BEGIN
			vc := CreateForm();
			Init (vc.bounds.GetWidth(), vc.bounds.GetHeight(), FALSE);
			SetContent (vc);
			SetTitle (WM.NewString("Diff"));
			IF c # NIL THEN
				WMRestorable.AddByContext(SELF, c);
			ELSE
				WM.DefaultAddWindow(SELF);
			END;
			IncCount;
		END New;

		PROCEDURE CreateForm(): WMComponents.VisualComponent;
		VAR
			label: WMStandardComponents.Label;
			panel, status, middlePanel, scrollPanel: WMStandardComponents.Panel;
			diffBtn: WMStandardComponents.Button;
		BEGIN
			NEW(panel);
			panel.bounds.SetExtents (Width, Height); panel.alignment.Set(WMComponents.AlignClient);
			panel.fillColor.Set (WMGraphics.White);

			NEW(status); status.alignment.Set(WMComponents.AlignBottom); status.bounds.SetHeight(20);
			panel.AddContent(status); status.fillColor.Set(BgColor);

			NEW(label); label.bounds.SetWidth(100); label.caption.SetAOC("Common Lines: "); label.alignment.Set(WMComponents.AlignLeft);
			status.AddContent(label);
			NEW(lblcommonLines); lblcommonLines.bounds.SetWidth(100); lblcommonLines.caption.SetAOC("-");
			lblcommonLines.alignment.Set(WMComponents.AlignLeft);
			status.AddContent(lblcommonLines);

			NEW(label); label.bounds.SetWidth(150); label.caption.SetAOC("Different Lines Left File: ");
			label.alignment.Set(WMComponents.AlignLeft);
			status.AddContent(label);
			NEW(lbldiffLinesLeft); lbldiffLinesLeft.bounds.SetWidth(100); lbldiffLinesLeft.caption.SetAOC("-");
			lbldiffLinesLeft.alignment.Set(WMComponents.AlignLeft);
			status.AddContent(lbldiffLinesLeft);

			NEW(label); label.bounds.SetWidth(150); label.caption.SetAOC("Different Lines Right File: ");
			label.alignment.Set(WMComponents.AlignLeft);
			status.AddContent(label);
			NEW(lbldiffLinesRight); lbldiffLinesRight.bounds.SetWidth(100); lbldiffLinesRight.caption.SetAOC("-");
			lbldiffLinesRight.alignment.Set(WMComponents.AlignLeft);
			status.AddContent(lbldiffLinesRight);

			NEW(leftView);
			leftView.bounds.SetWidth ((Width - 2*OverviewWidth - ScrollbarWidth) DIV 2);
			leftView.alignment.Set(WMComponents.AlignLeft);
			leftView.onPropertyChange.Add(OnViewPropertyChange);
			leftView.onResized.Add(OnViewResized);
			leftView.diffColor := DiffColorLeft;
			panel.AddContent (leftView);

			NEW(rightView);
			rightView.bounds.SetWidth ((Width - 2*OverviewWidth - ScrollbarWidth) DIV 2);
			rightView.alignment.Set(WMComponents.AlignLeft);
			rightView.diffColor := DiffColorRight;
			rightView.onPropertyChange.Add(OnViewPropertyChange);
			rightView.onResized.Add(OnViewResized);

			leftView.filenameEdit.onEnter.Add(DiffHandler);
			rightView.filenameEdit.onEnter.Add(DiffHandler);

			NEW(middlePanel);
			middlePanel.bounds.SetWidth (2*OverviewWidth + ScrollbarWidth); middlePanel.alignment.Set(WMComponents.AlignLeft);
			middlePanel.fillColor.Set (LONGINT(0FFFFFFFFH));

			NEW(diffBtn); diffBtn.caption.SetAOC("Diff"); diffBtn.alignment.Set(WMComponents.AlignTop);
			diffBtn.onClick.Add(DiffHandler);
			diffBtn.bounds.SetHeight(20);
			middlePanel.AddContent(diffBtn);

			NEW(scrollPanel); scrollPanel.alignment.Set(WMComponents.AlignClient);
			scrollPanel.fillColor.Set (LONGINT(0FFFFFFFFH));

			NEW(leftOverview, leftView);
			leftOverview.bounds.SetWidth(OverviewWidth); leftOverview.alignment.Set(WMComponents.AlignLeft);
			leftOverview.diffColor := WMGraphics.Red;
			scrollPanel.AddContent(leftOverview);

			NEW(scrolly);
			scrolly.bounds.SetWidth(ScrollbarWidth); scrolly.alignment.Set(WMComponents.AlignLeft);
			scrolly.vertical.Set(TRUE);
			scrolly.onPositionChanged.Add(SELF.Scrolled);
			scrollPanel.AddContent(scrolly);

			NEW(rightOverview, rightView);
			rightOverview.bounds.SetWidth(OverviewWidth); rightOverview.alignment.Set(WMComponents.AlignLeft);
			rightOverview.diffColor := WMGraphics.Blue;
			scrollPanel.AddContent(rightOverview);

			middlePanel.AddContent(scrollPanel);
			panel.AddContent(middlePanel);

			panel.AddContent (rightView);
			RETURN panel
		END CreateForm;

		PROCEDURE Close*;
		BEGIN
			Close^;
			DecCount;
		END Close;

		PROCEDURE Load*(leftFilename,  rightFilename : ARRAY OF CHAR);
		BEGIN
			leftView.SetFileName(leftFilename);
			rightView.SetFileName(rightFilename);
			IF (leftFilename # "") OR  (rightFilename # "") THEN DiffHandler(NIL, NIL) END;
			Resized(Width, Height);
		END Load;

		PROCEDURE Resized*(width, height : LONGINT);
		VAR viewWidth: LONGINT;
		BEGIN
			Resized^(width, height);
			viewWidth := (width - 2*OverviewWidth - ScrollbarWidth) DIV 2;
			leftView.bounds.SetWidth(viewWidth);
			rightView.bounds.SetWidth(viewWidth);
		END Resized;

		PROCEDURE OnViewResized(sender, par: ANY);
		BEGIN
			SetScrollbarProperties;
			Scrolled(NIL, NIL)
		END OnViewResized;

		PROCEDURE Setup(nofLines: LONGINT);
		BEGIN
			leftView.SetNofLines(nofLines + 1);
			rightView.SetNofLines(nofLines + 1);
		END Setup;

		PROCEDURE SetScrollbarProperties;
		BEGIN
			pageSize := leftView.grid.bounds.GetHeight() DIV leftView.grid.defaultRowHeight.Get();
			scrolly.pageSize.Set(pageSize);
			scrolly.min.Set(0); scrolly.max.Set(Strings.Max(leftView.curLine, rightView.curLine) - pageSize);
		END SetScrollbarProperties;

		PROCEDURE Update;
		VAR intStr: ARRAY 32 OF CHAR;
		BEGIN
			leftView.Update;
			rightView.Update;
			Strings.IntToStr(leftView.commonLines, intStr);
			lblcommonLines.caption.SetAOC(intStr);
			Strings.IntToStr(rightView.diffLines, intStr);
			IF rightView.diffLines > 0 THEN
				lbldiffLinesRight.textColor.Set(DarkRed);
			ELSE
				lbldiffLinesRight.textColor.Set(DarkGreen);
			END;
			lbldiffLinesRight.caption.SetAOC(intStr);
			IF leftView.diffLines > 0 THEN
				lbldiffLinesLeft.textColor.Set(DarkRed);
			ELSE
				lbldiffLinesLeft.textColor.Set(DarkGreen);
			END;
			Strings.IntToStr(leftView.diffLines, intStr);
			lbldiffLinesLeft.caption.SetAOC(intStr);
			leftOverview.Update;
			rightOverview.Update;
			SetScrollbarProperties;
			Scrolled(NIL, NIL);
		END Update;

		PROCEDURE DiffHandler(sender, par: ANY);
		VAR leftFile, rightFile: Strings.String;
		BEGIN
			lbldiffLinesLeft.textColor.Set(WMGraphics.Black); lbldiffLinesLeft.caption.SetAOC("-");
			lbldiffLinesRight.textColor.Set(WMGraphics.Black); lblcommonLines.caption.SetAOC("-");
			lbldiffLinesRight.caption.SetAOC("-");
			leftView.Setup; leftOverview.Setup;
			rightView.Setup; rightOverview.Setup;
			leftFile := leftView.GetFileName();
			rightFile := rightView.GetFileName();
			DisableUpdate;
			DiffLib.Diff (leftFile^, rightFile^, Setup, leftView.InsertLine, rightView.InsertLine, leftView.InsertCommonLine, rightView.InsertCommonLine, leftView.InsertEmptyLine, rightView.InsertEmptyLine, NIL);
			EnableUpdate;
			Update;
		END DiffHandler;

		PROCEDURE OnViewPropertyChange(sender, par: ANY);
		VAR ymax: LONGINT;
		BEGIN
			ymax := sender(DiffView).grid.nofRows.Get();
			scrolly.max.Set(ymax);
		END OnViewPropertyChange;

		PROCEDURE Scrolled(sender, data : ANY);
		VAR col, coll, row, rowl : LONGINT;
		BEGIN
			rightView.lineNbr.GetTopPosition(coll, rowl);
			rightView.grid.GetTopPosition(col, row);
			rightView.lineNbr.SetTopPosition(coll,  scrolly.pos.Get(), FALSE);
			rightView.grid.SetTopPosition(col, scrolly.pos.Get(), FALSE);

			leftView.lineNbr.GetTopPosition(coll, rowl);
			leftView.grid.GetTopPosition(col, row);
			leftView.lineNbr.SetTopPosition(coll,  scrolly.pos.Get(), FALSE);
			leftView.grid.SetTopPosition(col, scrolly.pos.Get(), FALSE);
			leftOverview.SetPage(scrolly.pos.Get(), pageSize);
			rightOverview.SetPage(scrolly.pos.Get(), pageSize);
		END Scrolled;

		PROCEDURE HandleKey(ucs : LONGINT; flags : SET; keysym : LONGINT) : BOOLEAN;
		VAR handled : BOOLEAN;
		BEGIN
			handled := TRUE;
			IF (ucs = 0A2H) THEN (* PgUp *)
				scrolly.PageUp(NIL, NIL);
			ELSIF (ucs = 0A3H) THEN (* PgDn *)
				scrolly.PageDown(NIL, NIL);
			ELSIF (ucs = 0A8H) THEN (* Home *)
				scrolly.pos.Set(scrolly.min.Get()); Scrolled(NIL, NIL);
			ELSIF (ucs = 0A9H) THEN (* End *)
				scrolly.pos.Set(scrolly.max.Get()); Scrolled(NIL, NIL);
			ELSIF (ucs = 0A0H) THEN (* Insert *)
				leftView.ToggleLineNumbers;
				rightView.ToggleLineNumbers;
			ELSIF (ucs = 0C1H) THEN (* Cursor Up *)
				scrolly.DecPos(NIL, NIL);
			ELSIF (ucs = 0C2H) THEN (* Cursor Down *)
				scrolly.IncPos(NIL, NIL);
			ELSE
				handled := FALSE;
			END;
			RETURN handled;
		END HandleKey;

		PROCEDURE Handle(VAR m: WMMessages.Message);
		BEGIN
			IF (m.msgType = WMMessages.MsgKey) THEN
				IF ~HandleKey(m.x, m.flags, m.y) THEN
					Handle^(m);
				END;
			ELSIF (m.msgType = WMMessages.MsgExt) & (m.ext # NIL) THEN
				IF (m.ext IS KillerMsg) THEN Close
				ELSE Handle^(m)
				END
			ELSE Handle^(m)
			END
		END Handle;

		PROCEDURE WheelMove*(dz : LONGINT);
		VAR pos : LONGINT;
		BEGIN
			pos := scrolly.pos.Get() + MouseWheelMultiplier * dz;
			IF pos < scrolly.min.Get() THEN pos := scrolly.min.Get(); END;
			IF pos > scrolly.max.Get() THEN pos := scrolly.max.Get(); END;
			scrolly.pos.Set(pos);
			scrolly.onPositionChanged.Call(scrolly.pos)
		END WheelMove;
	END Window;

VAR
	nofWindows : LONGINT;

	StrDiffView, StrOverview, StrNoWheelGrid : Strings.String;

PROCEDURE InitStrings;
BEGIN
	StrDiffView := Strings.NewString("DiffView");
	StrOverview := Strings.NewString("Overview");
	StrNoWheelGrid := Strings.NewString("NoWheelGrid");
END InitStrings;

PROCEDURE Open* (context : Commands.Context); (** [filename1 filename2] ~ *)
VAR left, right : ARRAY 64 OF CHAR; window: Window;
BEGIN
	context.arg.SkipWhitespace; context.arg.String(left);
	context.arg.SkipWhitespace; context.arg.String(right);
	NEW(window, NIL); window.Load(left, right);
END Open;

PROCEDURE IncCount;
BEGIN {EXCLUSIVE}
	INC(nofWindows);
END IncCount;

PROCEDURE DecCount;
BEGIN {EXCLUSIVE}
	DEC(nofWindows);
END DecCount;

PROCEDURE Cleanup;
VAR die : KillerMsg;
	 msg : WMMessages.Message;
	 m : WM.WindowManager;
BEGIN {EXCLUSIVE}
	NEW(die); msg.ext := die; msg.msgType := WMMessages.MsgExt;
	m := WM.GetDefaultManager();
	m.Broadcast(msg);
	AWAIT(nofWindows = 0);
END Cleanup;

BEGIN
	Modules.InstallTermHandler(Cleanup);
	InitStrings;
END WMDiff.

PC.Compile \s DiffLib.Mod XStandardComponents.Mod WMDiff.Mod~

SystemTools.Free WMDiff DiffLib~

Problem: When the overview bar is drawn, it is scaled for speedup. This may lead to the disappearance of marking for line differences.
Therefore documents with different lines are marked additionally by a red filename.

WMDiff.Open ~

WMDiff.Open UsbEhci.Mod UsbEhci.Mod ~

WMDiff.Open AosSetup.Text AosSetup.Text ~

WMDiff.Open WMPerfMon.Mod WMPerfMon.Mod.Bak ~

WMDiff.Open WMDiff.Mod WMDiff.Mod.Bak ~