{$mode objfpc}
{$modeswitch objectivec1}
// Incompatible with -Mmacpas

// LineNumbersDemoStandalone by Ingemar Ragnemalm 2010
// This demo is the result of quite a bit of testing with extensions of NSTextView
// to make it a bit more flexible. The result is just a little sidebar with line numbers,
// so how can that be hard? There are quite a few more customizations that you would like
// to make, like split views and if possible even code folding. But the system for text
// is quite complex and I am not the only one who finds it hard. But now at least this
// part works, which means that I am at the same level as what TXN can do.

// This version is standalone, no dependency of other units except the standard ones.
// There is also a version using TransSkel, as well as demos using TrandSkel with TransEdit,
// which hides all this complexity.

// Note: I do not know enough Cocoa to make this code perfect on first try. I am sure there are
// unnecessary calls as well as unreleased objects. Please let me know if you find such mistakes.

program LineNumbersDemoStandalone;
uses
	ctypes, MacOSAll, CocoaAll;

var
	pool: NSAutoreleasePool;
	NSApp: NSApplication;

const
	left_margin_width = 5; // Extra click margin
	kBarWidth = 25; // Size of bar

type
	MyTextView = objcclass;
	
	LineNumbersView = objcclass(NSView)
		textView: MyTextView;
		public
		procedure drawRect (rect: NSRect); override;
		function isFlipped: Boolean; message 'isFlipped'; override;
	  end;

type
	MyTextView = objcclass(NSTextView)
		theLineNumbersView: LineNumbersView; // Ruler replacement
		public
		function textContainerOrigin: NSPoint; override;
		procedure drawRect (arect: NSRect); override;
		procedure createSizeView(parentView: NSView); message 'createSizeView:';
		procedure updateMargin; message 'updateMargin';
		procedure windowWillClose (note: NSNotification); message 'windowWillClose:';
	end;

// Simple subclass of NSTextContainer to modify the left edge
// Here used only to get extra space for easier clicks to the left
type
	MyTextViewContainer = objcclass(NSTextContainer)
	public
		function lineFragmentRectForProposedRect_sweepDirection_movementDirection_remainingRect(proposedRect: NSRect; sweepDirection: NSLineSweepDirection; movementDirection: NSLineMovementDirection; remainingRect: NSRectPointer): NSRect; override;
	end;

// To modify the left margin.
function MyTextViewContainer.lineFragmentRectForProposedRect_sweepDirection_movementDirection_remainingRect(proposedRect: NSRect; sweepDirection: NSLineSweepDirection; movementDirection: NSLineMovementDirection; remainingRect: NSRectPointer): NSRect;
begin
    proposedRect.origin.x := left_margin_width;
    result := inherited lineFragmentRectForProposedRect_sweepDirection_movementDirection_remainingRect(proposedRect, sweepDirection, movementDirection, remainingRect);
end;

// This routine is based on "LineNumbering" by Koen van der Drift.
//procedure Draw(theView: NSView; cgContext: CGContextRef; viewRect: CGRect; textView: NSTextView);
procedure LineNumbersView.drawRect (rect: NSRect);
var
	start_index, end_index: Longint;
	glyphRange: NSRange;
	visRect: NSRect;

	lineRange: NSRange;
	scrollPos: Real;
	
	lineRect: NSRect;
	index, lineNumber: Longint;
	labelText: NSString;
	
	xform: NSAffineTransform;
begin
//	UseNSViewContext(theView);

    NSColor.redColor.set_;
    NSBezierPath.fillRect(rect);

// Translate for scroll position
	scrollPos := textView.enclosingScrollView.contentView.bounds.origin.y;
	xform := NSAffineTransform.transform;
	xform.translateXBy_yBy(0.0, -scrollPos);
	xform.concat;

// Get the visible part of the text
	visRect := textView.enclosingScrollView.documentVisibleRect;
	glyphRange := textView.layoutManager.glyphRangeForBoundingRect_inTextContainer(visRect, textView.textContainer);
	textView.layoutManager.glyphRangeForBoundingRect_inTextContainer(visRect, textView.textContainer);
	start_index := glyphRange.location;
	end_index := glyphRange.location + glyphRange.length;
	
	index := 0;
	lineNumber := 1;

// Skip all lines that are visible at the top of the text view (if any)
	while (index < start_index) do
	begin
		lineRect := textView.layoutManager.lineFragmentRectForGlyphAtIndex_effectiveRange(index, @lineRange);
		index := NSMaxRange( lineRange );
		lineNumber += 1;
	end;

// Draw all visible lines
	index := start_index;
	while index < end_index do
	begin
		lineRect := textView.layoutManager.lineFragmentRectForGlyphAtIndex_effectiveRange(index, @lineRange);
		index := NSMaxRange( lineRange );
		
		labelText := NSString.stringWithFormat(NSSTR('%d'), lineNumber);
		labelText.drawInRect_withAttributes(lineRect, nil);
		
		lineNumber += 1;
    end;
    
// Draw "extra" line number
	lineRect := textView.layoutManager.extraLineFragmentRect;

	labelText := NSString.stringWithFormat(NSSTR('%d'), lineNumber);
	labelText.drawInRect_withAttributes(lineRect, nil);
end;

function LineNumbersView.isFlipped: Boolean;
begin
	result := true;
end;

// A textview with an extra view connected (theLineNumbersView)
// intended as a kind of ruler.

	procedure MyTextView.updateMargin;
	begin
		theLineNumbersView.setNeedsDisplay_(true);
	end;

	procedure MyTextView.createSizeView(parentView: NSView);
	var
		viewRect: NSRect;
	begin
		viewRect.origin.x := 0;
		viewRect.origin.y := 20;
		viewRect.size.width := kBarWidth;
		viewRect.size.height := bounds.size.height;
		theLineNumbersView := LineNumbersView(LineNumbersView.alloc);
		theLineNumbersView.initWithFrame(viewRect);
		
		theLineNumbersView.textView := self;

		parentView.addSubview(theLineNumbersView);
		theLineNumbersView.setAutoresizingMask(NSViewHeightSizable); // Funkar inte
	end;

	function MyTextView.textContainerOrigin: NSPoint;
	begin
		result := inherited textContainerOrigin;
		result.x += left_margin_width;
	end;

	procedure MyTextView.drawRect (arect: NSRect);
	begin
		inherited drawRect(arect);
		WriteLn('PING');
		
		theLineNumbersView.setNeedsDisplay_(true);
	end;

	procedure MyTextView.windowWillClose (note: NSNotification);
	begin
		NSApp.terminate(NSApp);
	end;

// Builds a scrollable text view plus custom container view and theLineNumbersView
function TestSkelNewTextView(parentView: NSView; x,y,w,h: Real; initialText: NSString): MyTextView;
var
	CFMessage: NSString;
	textViewRect, scrollViewRect: NSRect;
	textView: MyTextView;
	scrollView: NSScrollView;
	
	// Lilla extramarginalen:
	myContainer: MyTextViewContainer;
	frame: NSRect;
const
	LargeNumberForText = 1.0e7;
begin
	CFMessage := initialText;
	scrollViewRect.origin.x := x;
	scrollViewRect.origin.y := y;
	scrollViewRect.size.width := w;
	scrollViewRect.size.height := h;
	
	scrollView := NSScrollView.alloc;
	scrollView.initWithFrame(scrollViewRect);
	scrollView.setHasVerticalScroller(true);
	scrollView.setAutoresizingMask(NSViewWidthSizable or NSViewHeightSizable);
	scrollView.contentView.setAutoresizesSubviews(true);
	
	textViewRect.size := scrollView.contentSize;
	
	textView := MyTextView(MyTextView.alloc);
	textView.initWithFrame(textViewRect);
	textView.insertText(id(CFMessage)); // Funkar!
	textView.setAutoresizingMask({NSViewWidthSizable or} NSViewHeightSizable);

// Create my modified TextViewContainer
	frame := NSMakeRect(0, 0, scrollView.contentSize.width, scrollView.contentSize.height);
	myContainer := MyTextViewContainer(MyTextViewContainer.allocWithZone_(textView.zone)).
							initWithContainerSize(NSMakeSize(frame.size.width, 100000));
	myContainer.setWidthTracksTextView(true);
	myContainer.setHeightTracksTextView(false);
// This controls the inset of our text away from the margin.
	textView.replaceTextContainer(myContainer);
	myContainer.release;

// NŒgon av dessa fixar sŒ att vi fŒr obrutna rader med vettig scroll.
	textView.textContainer.setContainerSize(NSMakeSize(LargeNumberForText, LargeNumberForText));
	textView.textContainer.setWidthTracksTextView(false);
	textView.textContainer.setHeightTracksTextView(false);
	textView.setAutoresizingMask(NSViewNotSizable);
	textView.setMaxSize(NSMakeSize(LargeNumberForText, LargeNumberForText));
	textView.setHorizontallyResizable(true);
	textView.setVerticallyResizable(true);
	
	NSNotificationCenter(NSNotificationCenter.defaultCenter).addObserver_selector_name_object(textView,
						sel_registerName(PChar('updateMargin')),
						NSWindowDidUpdateNotification, textView.window);


	textView.createSizeView(parentView);

	parentView.addSubview(scrollView);
	scrollView.addSubview(textView);
	scrollView.setDocumentView(textView);
	
	result := textView;
end;

// Building menu from code directly in Cocoa is messy (but not in TransSkel)
procedure BuildEditMenu;
var
	mainMenu, theMiniMenu: NSMenu;
	menuItem1, menuItem2, menuItem3, menuItem4: NSMenuItem;
	dummyItem: NSMenuItem;
begin
	// Create main menu = menu bar
	mainMenu := NSMenu.alloc;
	mainMenu.initWithTitle(NSString(CFSTR('')));
	NSApp.setMainMenu(mainMenu);
	
	// Create the custom menu
	theMiniMenu := NSMenu.alloc;
	theMiniMenu.initWithTitle(NSString(CFSTR('Edit')));
	
	// Create a menu item
	menuItem1 := NSMenuItem.alloc;
	menuItem1.initWithTitle_action_keyEquivalent(NSString(CFSTR('Cut')), nil, NSString(CFSTR('x')));
	menuItem1.setKeyEquivalentModifierMask(NSCommandKeyMask);
	menuItem1.setAction(sel_registerName(PChar('cut:'))); // What method will handle it?
	theMiniMenu.addItem(menuItem1);
	
	// Create a menu item
	menuItem2 := NSMenuItem.alloc;
	menuItem2.initWithTitle_action_keyEquivalent(NSString(CFSTR('Copy')), nil, NSString(CFSTR('c')));
	menuItem2.setKeyEquivalentModifierMask(NSCommandKeyMask);
	menuItem2.setAction(sel_registerName(PChar('copy:'))); // What method will handle it?
	theMiniMenu.addItem(menuItem2);
	
	// Create a menu item
	menuItem3 := NSMenuItem.alloc;
	menuItem3.initWithTitle_action_keyEquivalent(NSString(CFSTR('Paste')), nil, NSString(CFSTR('v')));
	menuItem3.setKeyEquivalentModifierMask(NSCommandKeyMask);
	menuItem3.setAction(sel_registerName(PChar('cut:'))); // What method will handle it?
	theMiniMenu.addItem(menuItem3);
	
	// Create another menu item with quit
	menuItem4 := NSMenuItem.alloc;
	menuItem4.initWithTitle_action_keyEquivalent(NSString(CFSTR('Quit')), nil, NSString(CFSTR('q')));
	menuItem4.setKeyEquivalentModifierMask(NSCommandKeyMask);
	menuItem4.setAction(sel_registerName(PChar('terminate:')));
	theMiniMenu.addItem(menuItem4);
	
	// Adding a menu is done with a dummy item to connect the menu to its parent
	dummyItem := NSMenuItem.alloc;
	dummyItem.initWithTitle_action_keyEquivalent(NSString(CFSTR('')), nil, NSString(CFSTR('')));
	dummyItem.setSubmenu(theMiniMenu);
	mainMenu.addItem(dummyItem);
end;

	procedure MakeWindow;
	var
		w: NSWindow;
		frame: NSRect = (origin:(x: 10; y: 1000); size:(width: 440; height: 440));
	begin
		frame.origin.y := NSScreen.mainScreen.frame.size.height - 50;
		w := NSWindow.alloc;
		w.initWithContentRect_styleMask_backing_defer(frame,
			NSTitledWindowMask or NSClosableWindowMask or NSMiniaturizableWindowMask or NSResizableWindowMask,
			NSBackingStoreBuffered, LongBool(0));
		w.setTitle(NSString(CFSTR('Line number standalone demo')));

		TestSkelNewTextView(w.contentView, kBarWidth, 20, 400, 400, NSSTR('This is a test text'));		
		w.orderFrontRegardless;
	end;

begin
	pool := NSAutoreleasePool.new;
	NSApp := NSApplication.sharedApplication;

	MakeWindow;
	BuildEditMenu;
	// Main loop
	NSApp.run;
	pool.dealloc; // Free;
end.
