Difference between revisions of "Cocoa Internals/Text Controls"

From Free Pascal wiki
Jump to navigationJump to search
m
Line 6: Line 6:
 
* It doesn't like custom fonts (other than default) to be assigned to itself  (see [https://bugs.freepascal.org/view.php?id=33626 #33626])
 
* It doesn't like custom fonts (other than default) to be assigned to itself  (see [https://bugs.freepascal.org/view.php?id=33626 #33626])
 
[[image:cocoa combobox issue.png]]
 
[[image:cocoa combobox issue.png]]
 +
 +
TMemo widgetset is implemented over NSTextView and NSScrollView
 +
 +
==TMemo (NSTextView)==
 +
By default NSTextView is designed to be constantly word-wrapped. Disabling word-wrapping could be quite complicated from a start due to odd-default values chosen by Apple, as well as complex (yet flexible) Text Layout system.
 +
* NSTextView is a "cocoa" control, however it's not drawing the text by it's own it's also using:
 +
* NSTextContainer. Both NSTextContainer and NSTextView settings influence on how the text is rendered in the end.
 +
 +
The example shows, of creating NSTextView that automatically resizes itself horizontally.
 +
However NSTextView doesn't provide its own scrollbars, thus no scrollbars would be seen.
 +
<source lang="delphi">
 +
procedure TForm1.FormShow(Sender: TObject);
 +
var
 +
  txt : NSTextView;
 +
begin
 +
  txt := NSTextView.alloc.initWithFrame(NSMakeRect(10,ClientHeight-10-50,50,50));
 +
 +
  txt.setFont(NSFont.systemFontOfSize(NSFont.systemFontSizeForControlSize(NSRegularControlSize)));
 +
 +
  // making the maximum size - maximum!
 +
  // 10000000 is a "constant" could be found in Apple documentation
 +
  txt.setMaxSize( NSMakeSize(10000000, 10000000));
 +
  // preventing textContainer from following the width of NSTextView
 +
  txt.textContainer.setWidthTracksTextView(false);
 +
  // making TextContainer large enough.
 +
  txt.textContainer.setContainerSize ( NSMakeSize( 10000000, 1024));
 +
  // making NSTextView to resize automatically to the text boundries (max width)
 +
  txt.setHorizontallyResizable(true);
 +
 +
  NSView(Self.Handle).addSubView(txt);
 +
end;  </source>
 +
 +
The next step is actually to embed NSTextView into ScrollView (as a documentView).
 +
 +
===Inserting into Scroll View===
 +
The process is straight-forward - allocate scroll view, use NSTextView as it's document view
 +
<source lang="delphi">
 +
procedure TForm1.FormShow(Sender: TObject);
 +
var
 +
  txt : NSTextView;
 +
  sc  : NSScrollView;
 +
begin
 +
  txt := NSTextView.alloc.initWithFrame(NSMakeRect(10,ClientHeight-10-50,50,50));
 +
 +
  txt.setMaxSize( NSMakeSize(10000000, 10000000));
 +
  txt.textContainer.setWidthTracksTextView(false);
 +
  txt.textContainer.setContainerSize ( NSMakeSize( 10000000, 1024));
 +
  txt.setHorizontallyResizable(true);
 +
 +
  // allocating scroll view and placing NSTextView inside
 +
  sc := NSScrollView.alloc.initWithFrame(NSMakeRect(10,ClientHeight-10-150,150,150));
 +
  sc.setHasVerticalScroller(true);
 +
  sc.setHasHorizontalScroller(true);
 +
  sc.setAutohidesScrollers(true);
 +
  sc.setDocumentView(txt);
 +
 +
  NSView(Self.Handle).addSubView(sc);
 +
end; 
 +
</source>
 +
Now text-view and scrollbars would act as expected.
 +
===Line padding oddity===
 +
By default, there's 5 pixels padding between border of NSTextView and left-side of the text.
 +
 +
It's possible to decrease it, however, setting it to zero is causing a very weird and unpleasant glitch.
 +
<source lang="delphi">var
 +
  txt : NSTextView;
 +
  sc  : NSScrollView;
 +
begin
 +
  txt := NSTextView.alloc.initWithFrame(NSMakeRect(10,ClientHeight-10-50,50,50));
 +
 +
  txt.setMaxSize( NSMakeSize(10000000, 10000000));
 +
  txt.textContainer.setWidthTracksTextView(false);
 +
  txt.textContainer.setContainerSize ( NSMakeSize( 10000000, 1024));
 +
  txt.setHorizontallyResizable(true);
 +
 +
  // setting this value to 0 is causing the problem.
 +
  txt.textContainer.setLineFragmentPadding(0);
 +
 +
  sc := NSScrollView.alloc.initWithFrame(NSMakeRect(10,ClientHeight-10-150,150,150));
 +
  sc.setHasVerticalScroller(true);
 +
  sc.setHasHorizontalScroller(true);
 +
  sc.setAutohidesScrollers(true);
 +
  sc.setDocumentView(txt);
 +
 +
  NSView(Self.Handle).addSubView(sc);
 +
end; 
 +
</source>
 +
Now. Everytime a new line break is added '''to the end of the text''' the view scrolls to the top (while having cursor at the bottom).
 +
 +
Either by pressing Enter on keyboard having cursor at the end of the text. Or by inserting a line break at the end of the text programmatically.
 +
 +
 +
The only way to avoid the problem is to have Fragment padding set to a value greater than zero.
 +
 +
==See Also==
 +
*[[Cocoa Internals]]
 +
*[https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/TextStorageLayer/Tasks/TrackingSize.html Tracking the Size of a Text View] - Apple's official documentation explains how text size is  tracked between NSViewText and NSViewContainer
 +
*[https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/TextUILayer/Tasks/TextInScrollView.html Putting an NSTextView Object in an NSScrollView] - Apple's official documentation on how to place NSTextView into a scrollview.
 +
*[https://stackoverflow.com/questions/3174140/how-to-disable-word-wrap-of-nstextview  How to Disable Word-Wrap of NSTextview] - stackoverflow discussion of the problem.
  
 
[[Category:Cocoa]]
 
[[Category:Cocoa]]

Revision as of 14:31, 13 June 2019

ComboBox

Non-readonly ComboBox is implemented via NSComboBox type. Readonly combobox is implemented via NSPopupButton

NSComboBox features

  • It doesn't like to be 29px or taller in height. The height is fixed to 26 pixels
  • It doesn't like custom fonts (other than default) to be assigned to itself (see #33626)

cocoa combobox issue.png

TMemo widgetset is implemented over NSTextView and NSScrollView

TMemo (NSTextView)

By default NSTextView is designed to be constantly word-wrapped. Disabling word-wrapping could be quite complicated from a start due to odd-default values chosen by Apple, as well as complex (yet flexible) Text Layout system.

  • NSTextView is a "cocoa" control, however it's not drawing the text by it's own it's also using:
  • NSTextContainer. Both NSTextContainer and NSTextView settings influence on how the text is rendered in the end.

The example shows, of creating NSTextView that automatically resizes itself horizontally. However NSTextView doesn't provide its own scrollbars, thus no scrollbars would be seen.

procedure TForm1.FormShow(Sender: TObject);
var
  txt : NSTextView;
begin
  txt := NSTextView.alloc.initWithFrame(NSMakeRect(10,ClientHeight-10-50,50,50));

  txt.setFont(NSFont.systemFontOfSize(NSFont.systemFontSizeForControlSize(NSRegularControlSize)));

  // making the maximum size - maximum!
  // 10000000 is a "constant" could be found in Apple documentation
  txt.setMaxSize( NSMakeSize(10000000, 10000000));
  // preventing textContainer from following the width of NSTextView
  txt.textContainer.setWidthTracksTextView(false);
  // making TextContainer large enough.
  txt.textContainer.setContainerSize ( NSMakeSize( 10000000, 1024));
  // making NSTextView to resize automatically to the text boundries (max width)
  txt.setHorizontallyResizable(true);

  NSView(Self.Handle).addSubView(txt);
end;

The next step is actually to embed NSTextView into ScrollView (as a documentView).

Inserting into Scroll View

The process is straight-forward - allocate scroll view, use NSTextView as it's document view

procedure TForm1.FormShow(Sender: TObject);
var
  txt : NSTextView;
  sc  : NSScrollView;
begin
  txt := NSTextView.alloc.initWithFrame(NSMakeRect(10,ClientHeight-10-50,50,50));

  txt.setMaxSize( NSMakeSize(10000000, 10000000));
  txt.textContainer.setWidthTracksTextView(false);
  txt.textContainer.setContainerSize ( NSMakeSize( 10000000, 1024));
  txt.setHorizontallyResizable(true);

  // allocating scroll view and placing NSTextView inside
  sc := NSScrollView.alloc.initWithFrame(NSMakeRect(10,ClientHeight-10-150,150,150));
  sc.setHasVerticalScroller(true);
  sc.setHasHorizontalScroller(true);
  sc.setAutohidesScrollers(true);
  sc.setDocumentView(txt);

  NSView(Self.Handle).addSubView(sc);
end;

Now text-view and scrollbars would act as expected.

Line padding oddity

By default, there's 5 pixels padding between border of NSTextView and left-side of the text.

It's possible to decrease it, however, setting it to zero is causing a very weird and unpleasant glitch.

var
  txt : NSTextView;
  sc  : NSScrollView;
begin
  txt := NSTextView.alloc.initWithFrame(NSMakeRect(10,ClientHeight-10-50,50,50));

  txt.setMaxSize( NSMakeSize(10000000, 10000000));
  txt.textContainer.setWidthTracksTextView(false);
  txt.textContainer.setContainerSize ( NSMakeSize( 10000000, 1024));
  txt.setHorizontallyResizable(true);

  // setting this value to 0 is causing the problem.
  txt.textContainer.setLineFragmentPadding(0);

  sc := NSScrollView.alloc.initWithFrame(NSMakeRect(10,ClientHeight-10-150,150,150));
  sc.setHasVerticalScroller(true);
  sc.setHasHorizontalScroller(true);
  sc.setAutohidesScrollers(true);
  sc.setDocumentView(txt);

  NSView(Self.Handle).addSubView(sc);
end;

Now. Everytime a new line break is added to the end of the text the view scrolls to the top (while having cursor at the bottom).

Either by pressing Enter on keyboard having cursor at the end of the text. Or by inserting a line break at the end of the text programmatically.


The only way to avoid the problem is to have Fragment padding set to a value greater than zero.

See Also