[squeak-dev] The Trunk: Morphic-mt.1933.mcz

commits at source.squeak.org commits at source.squeak.org
Mon Mar 14 14:52:41 UTC 2022

Marcel Taeumel uploaded a new version of Morphic to project The Trunk:

==================== Summary ====================

Name: Morphic-mt.1933
Author: mt
Time: 14 March 2022, 3:52:34.242786 pm
UUID: bcc808d3-6e90-2449-9f8d-e675490af6eb
Ancestors: Morphic-mt.1932

Adds #numCharactersPerLine: to TextMorph and PluggableTextMorph. Complements Graphics-mt.498 to compute font-based composition rectangle via TextStyle.

Use it, for example, to do line wrapping not only at the widget border but also at the (visual) wrap border in code panes.

(Minor fix in FontImporterTool, too.)

=============== Diff against Morphic-mt.1932 ===============

Item was changed:
  ----- Method: FontImporterTool>>selectedFont: (in category 'accessing') -----
  selectedFont: aTTCFont
  	selectedFont := aTTCFont.
  	self changed: #previewText.
  	self changed: #selectedFontTextStyle.
  	self changed: #filename.
  	self changed: #copyright.
  	self changed: #pointSizeInput.
+ 	"self changed: #lineSpacingInput. -- already via #selectedFontTextStyle"
- 	self changed: #lineSpacingInput.	
  	self changed: #ttExtraScaleInput.
  	self changed: #ttExtraGapInput.
  	self changed: #installButtonColor.
  	self changed: #installButtonLabel.
  	self changed: #installButtonEnabled.
  	self changed: #windowTitle.!

Item was changed:
  ----- Method: NewParagraph>>adjustRightXDownTo: (in category 'private') -----
  adjustRightXDownTo: minWidth
  	| shrink minRight |
  	minRight := container left + minWidth.
  	shrink := container right - (maxRightX max: minRight).
  	lines do: [:line | line paddingWidth: (line paddingWidth - shrink)].
  	containerUnadjusted := container.
+ 	container := container withRight: (maxRightX max: minRight).!
- 	container := container withRight: (maxRightX + self caretWidth max: minRight).!

Item was added:
+ ----- Method: NewParagraph>>asText (in category 'converting') -----
+ asText
+ 	"Answer the receiver's text after being composed. Use #text if you want to retain the layout before composition."
+ 	^ self asTextWithLineBreaks!

Item was added:
+ ----- Method: NewParagraph>>asTextWithLineBreaks (in category 'converting') -----
+ asTextWithLineBreaks
+ 	"Answer a text that has all soft line breaks converted to hard line breaks. Add the current style's default font as a text attribute only if a) the style is not the default and b) the first character has no other font set. See Text >> #asTextMorph."
+ 	| result |
+ 	result := Text streamContents: [:s | lines do: [:textLine |
+ 		| lastChar lastIndex break |
+ 		lastChar := text at: textLine last.
+ 		(break := CharacterSet separators includes: lastChar)
+ 			ifTrue: [lastIndex := textLine last - 1]
+ 			ifFalse: [lastIndex := textLine last].
+ 		"1) Copy text line, which may be due to a soft line break"
+ 		s nextPutAll: (text copyFrom: textLine first to: lastIndex).
+ 		"2) Add a hard line break."
+ 		break ifTrue: [s nextPutAll: (String cr asText
+ 			addAllAttributes: (text attributesAt: textLine last);
+ 			yourself)]]].
+ 	((text fontAt: 1 withDefault: nil) isNil and: [
+ 		textStyle defaultFamilyName ~= TextStyle default defaultFamilyName])
+ 			ifTrue: [result addAttribute: (TextFontReference toFont: textStyle defaultFont)].
+ 	^ result!

Item was changed:
  ScrollPane subclass: #PluggableTextMorph
  	instanceVariableNames: 'textMorph getTextSelector setTextSelector getSelectionSelector hasUnacceptedEdits hasUserEdited askBeforeDiscardingEdits selectionInterval hasEditingConflicts editTextSelector wantsWrapBorder getFontSelector getTextStyleSelector'
+ 	classVariableNames: 'AdornmentCache SimpleFrameAdornments SoftLineWrap SoftLineWrapAtVisualWrapBorder VisualWrapBorder VisualWrapBorderLimit'
- 	classVariableNames: 'AdornmentCache SimpleFrameAdornments SoftLineWrap VisualWrapBorder VisualWrapBorderLimit'
  	poolDictionaries: ''
  	category: 'Morphic-Pluggable Widgets'!

Item was changed:
  ----- Method: PluggableTextMorph class>>softLineWrap (in category 'preferences') -----
+ 	<preference: 'Use soft line wrap at widget border'
+ 		categoryList: #(scrolling editing Accessibility)
+ 		description: 'Wrap text lines to avoid horizontal scrolling in text widgets. This is the default for all kinds of multi-line text fields in a scrollable container, not only for source code.'
- 	<preference: 'Use soft line wrap'
- 		categoryList: #(scrolling editing)
- 		description: 'Wrap text lines to avoid horizontal scrolling.'
  		type: #Boolean>
  	^ SoftLineWrap ifNil: [true]!

Item was added:
+ ----- Method: PluggableTextMorph class>>softLineWrapAtVisualWrapBorder (in category 'preferences') -----
+ softLineWrapAtVisualWrapBorder
+ 	<preference: 'Use soft line wrap at wrap border (in code panes)'
+ 		categoryList: #(scrolling editing Accessibility)
+ 		description: 'Wrap text lines at the (maybe visual) wrap border in code panes. See #visualWrapBorderLimit and #visualWrapBorder.'
+ 		type: #Boolean>
+ 	^ SoftLineWrapAtVisualWrapBorder ifNil: [false]!

Item was added:
+ ----- Method: PluggableTextMorph class>>softLineWrapAtVisualWrapBorder: (in category 'preferences') -----
+ softLineWrapAtVisualWrapBorder: aBooleanOrNil
+ 	aBooleanOrNil == SoftLineWrapAtVisualWrapBorder ifTrue: [^ self].
+ 	SoftLineWrapAtVisualWrapBorder := aBooleanOrNil.
+ 	self updateCodePanes.!

Item was added:
+ ----- Method: PluggableTextMorph class>>updateCodePanes (in category 'preferences') -----
+ updateCodePanes
+ 	self flag: #todo. "mt Only for code panes!!"
+ 	self softLineWrapAtVisualWrapBorder
+ 		ifTrue: [
+ 			PluggableTextMorph allSubInstancesDo: [:m |
+ 				(m styler class = (TextStyler for: #Smalltalk)) ifTrue: [
+ 					m
+ 						wantsWrapBorder: self visualWrapBorder;
+ 						numCharactersPerLine: self visualWrapBorderLimit]]]
+ 		ifFalse: [
+ 			PluggableTextMorph allSubInstancesDo: [:m |
+ 				(m styler class = (TextStyler for: #Smalltalk)) ifTrue: [
+ 					m
+ 						wantsWrapBorder: self visualWrapBorder;
+ 						numCharactersPerLine: nil;
+ 						changed "redraw #visualWrapBorderLimit"]]].!

Item was changed:
  ----- Method: PluggableTextMorph class>>visualWrapBorder (in category 'preferences') -----
+ 	<preference: 'Show wrap border in code panes'
+ 		categoryList: #(editing visuals performance Accessibility)
- 	<preference: 'Show wrap border in code panes.'
- 		categoryList: #(editing visuals performance)
  		description: 'Show a visual border after a specific amount of characters. Makes sense for monospaced fonts.'
  		type: #Boolean>
  	^ VisualWrapBorder ifNil: [false]!

Item was changed:
  ----- Method: PluggableTextMorph class>>visualWrapBorder: (in category 'preferences') -----
+ visualWrapBorder: aBooleanOrNil
- visualWrapBorder: aBoolean
+ 	aBooleanOrNil == VisualWrapBorder ifTrue: [^ self].
+ 	VisualWrapBorder := aBooleanOrNil.
+ 	self updateCodePanes.!
- 	VisualWrapBorder := aBoolean.!

Item was changed:
  ----- Method: PluggableTextMorph class>>visualWrapBorderLimit (in category 'preferences') -----
+ 	<preference: 'Wrap border limit (in code panes)'
+ 		categoryList: #(editing visuals performance Accessibility)
+ 		description: 'In all code panes, indicate an expected wrapping border after a certain amount of characters. It will be a perfect fit for monospaced fonts and average over lines for proportional fonts. This border can be purely visual or also introduce soft line breaks. See #visualWrapBorder and #softLineWrapAtVisualWrapBorder.'
+ 		type: #String "Support negative values and floats">
- 	<preference: 'Wrap border limit'
- 		categoryList: #(editing visuals performance)
- 		description: 'Amount of characters after the border should be drawn.'
- 		type: #Number>
  	^ VisualWrapBorderLimit ifNil: [80]!

Item was changed:
  ----- Method: PluggableTextMorph class>>visualWrapBorderLimit: (in category 'preferences') -----
+ visualWrapBorderLimit: aNumberOrNil
- visualWrapBorderLimit: aNumber
+ 	aNumberOrNil == VisualWrapBorderLimit ifTrue: [^ self].
+ 	VisualWrapBorderLimit := aNumberOrNil ifNotNil: [:num | num asNumber].
+ 	self updateCodePanes.!
- 	VisualWrapBorderLimit := aNumber asInteger.!

Item was changed:
  ----- Method: PluggableTextMorph>>drawWrapBorderOn: (in category 'drawing') -----
  drawWrapBorderOn: aCanvas
+ 	| box offset rect |
- 	| offset rect |
  	self wantsWrapBorder ifFalse: [^ self].
  	textMorph ifNil: [^ self].
+ 	box := textMorph innerBounds.
+ 	textMorph margins ifNotNil: [:m | box := box insetBy: m].
- 	offset := textMorph margins isRectangle
- 		ifTrue: [textMorph margins left]
- 		ifFalse: [textMorph margins isPoint
- 			ifTrue: [textMorph margins x]
- 			ifFalse: [textMorph margins]].
- 	offset := offset + ((textMorph textStyle defaultFont widthOf: $x) * self class visualWrapBorderLimit).
- 	offset > self width ifTrue: [^ self].
+ 	offset := box left + (textMorph textStyle compositionWidthFor: self class
+ visualWrapBorderLimit).
+ 	self numCharactersPerLine ifNotNil: [
+ 		"Respect right margins only if we wrap at that border to not draw over glyphs."
+ 		offset := offset + (textMorph innerBounds right - box right) + self borderWidth].
+ 	offset > scroller width ifTrue: [^ self].
  	rect := scroller topLeft + (offset @ 0) corner: scroller bottomRight.
  		fillRectangle: rect
  		color: self wrapBorderColor.
  		line: rect topLeft
  		to: rect bottomLeft
+ 		width: self borderWidth
- 		width: self borderStyle width
  		color: (self wrapBorderColor muchDarker alpha: 0.5).!

Item was added:
+ ----- Method: PluggableTextMorph>>numCharactersPerLine (in category 'accessing') -----
+ numCharactersPerLine
+ 	^ textMorph numCharactersPerLine!

Item was added:
+ ----- Method: PluggableTextMorph>>numCharactersPerLine: (in category 'accessing') -----
+ numCharactersPerLine: numCharsOrNil
+ 	textMorph numCharactersPerLine: numCharsOrNil.!

Item was changed:
  ----- Method: PluggableTextMorph>>wantsWrapBorder: (in category 'accessing') -----
  wantsWrapBorder: aBoolean
+ 	wantsWrapBorder = aBoolean ifTrue: [^ self].
+ 	wantsWrapBorder := aBoolean.
+ 	self changed.!
- 	wantsWrapBorder := aBoolean.!

Item was changed:
  ----- Method: Text>>asTextMorph (in category '*Morphic-converting') -----
+ 	"Install the receiver in a text morph. Derive an appropriate text style from the receiver's first-character attributes (e.g., to make tabs look correct)."
+ 	^ TextMorph new
+ 		contentsAsIs: self;
+ 		textStyle: (self fontAt: 1 withStyle: TextStyle default)
+ 			asNewTextStyle;
+ 		yourself!
- 	^ TextMorph new contentsAsIs: self!

Item was changed:
  RectangleMorph subclass: #TextMorph
+ 	instanceVariableNames: 'textStyle text wrapFlag paragraph editor container predecessor successor backgroundColor margins readOnly autoFit plainTextOnly numCharactersPerLine'
- 	instanceVariableNames: 'textStyle text wrapFlag paragraph editor container predecessor successor backgroundColor margins readOnly autoFit plainTextOnly'
  	classVariableNames: 'CaretForm DefaultEditorClass'
  	poolDictionaries: ''
  	category: 'Morphic-Basic'!
  !TextMorph commentStamp: 'nice 3/24/2010 07:40' prior: 0!
  TextMorphs support display of text with emphasis.  They also support reasonable text-editing capabilities, as well as embedded hot links, and the ability to embed submorphs in the text.
  Late in life, TextMorph was made a subclass of BorderedMorph to provide border and background color if desired.  In order to keep things compatible, protocols have been redirected so that color (preferably textColor) relates to the text, and backgroundColor relates to the inner fill color.
  Text display is clipped to the innerBounds of the rectangle, and text composition is normally performed within a rectangle which is innerBounds inset by the margins parameter.
  If text has been embedded in another object, one can elect to fill the owner's shape, in which case the text will be laid out in the shape of the owner's shadow image (including any submorphs other than the text).  One can also elect to have the text avoid occlusions, in which case it will avoid the bounds of any sibling morphs that appear in front of it.  It may be necessary to update bounds in order for the text runaround to notice the presence of a new occluding shape.
  The optional autoFitContents property enables the following feature:  if the text contents changes, then the bounds of the morph will be adjusted to fit the minimum rectangle that encloses the text (plus any margins specified).  Similarly, any attempt to change the size of the morph will be resisted if this parameter is set.  Except...
  If the wrapFlag parameter is true, then text will be wrapped at word boundaries based on the composition width (innerBounds insetBy: margins) width.  Thus an attempt to resize the morph in autofit mode, if it changes the width, will cause the text to be recomposed with the new width, and then the bounds will be reset to the minimum enclosing rectangle.  Similarly, if the text contents are changed with the wrapFlag set to true, word wrap will be performed based on the current compostion width, after which the bounds will be set (or not), based on the autoFitcontents property.
  Note that fonts can only be applied to the TextMorph as a whole.  While you can change the size, color, and emphasis of a subsection of the text and have it apply to only that subsection, changing the font changes the font for the entire contents of the TextMorph. 
  Still a TextMorph can be composed of several texts of different fonts
  | font1 font2 t1 t2 tMorph|
  tMorph := TextMorph new.
  font1 := (TextFontReference toFont: (StrikeFont familyName: 'Atlanta' size: 22)).
  font2 := (TextFontReference toFont: (StrikeFont familyName: 'Atlanta' size: 11)).
  t1 := 'this is font1' asText addAttribute: font1.
  t2 := ' and this is font2' asText addAttribute: font2.
  tMorph contents: (t1,t2).
  tMorph openInHand.
  Yet to do:
  Make a comprehensive control for the eyedropper, with border width and color, inner color and text color, and margin widths.!

Item was changed:
  ----- Method: TextMorph>>compositionRectangle (in category 'private') -----
  	| compRect minW minH |
  	compRect := self innerBounds.
  	margins ifNotNil: [compRect := compRect insetBy: margins].
  	minW := self minCompositionWidth.
  	minH := self minCompositionHeight.
  	compRect width < minW ifTrue: [compRect := compRect withWidth: minW].
  	compRect height < minH ifTrue: [compRect := compRect withHeight: minH].
+ 	numCharactersPerLine ifNotNil: [
+ 		| preferredWidth |
+ 		preferredWidth := textStyle compositionWidthFor: numCharactersPerLine.
+ 		wrapFlag
+ 			ifTrue: [compRect := compRect withWidth: (compRect width min: preferredWidth)]
+ 			ifFalse: [compRect := compRect withWidth: preferredWidth]].
  	^ compRect!

Item was changed:
  ----- Method: TextMorph>>container (in category 'geometry') -----
  	"Return the container for composing this text.  There are four cases:
  	1.  container is specified as, eg, an arbitrary shape,
  	2.  container is specified as the bound rectangle, because
  		this morph is linked to others,
  	3.  container is nil, and wrap is true -- grow downward as necessary,
  	4.  container is nil, and wrap is false -- grow in 2D as necessary."
+ 	container ifNil: [
+ 		successor ifNotNil: [^ self compositionRectangle].
+ 		^ wrapFlag
+ 			ifTrue: [self compositionRectangle withHeight: self maximumContainerExtent y]
+ 			ifFalse: [
+ 				numCharactersPerLine
+ 					ifNil: [self compositionRectangle topLeft extent: self maximumContainerExtent]
+ 					ifNotNil: [self compositionRectangle withHeight: self maximumContainerExtent y]]].
- 	container ifNil:
- 		[successor ifNotNil: [^ self compositionRectangle].
- 		wrapFlag ifTrue: [^ self compositionRectangle withHeight: self maximumContainerExtent y].
- 		^ self compositionRectangle topLeft extent: self maximumContainerExtent].
  	^ container!

Item was changed:
  ----- Method: TextMorph>>fit (in category 'private') -----
  	"Adjust my bounds to fit the text.  Should be a no-op if autoFit is not specified.
  	Required after the text changes,
  	or if wrapFlag is true and the user attempts to change the extent."
  	| newExtent para cBounds lastOfLines heightOfLast |
+ 	self isAutoFit
+ 		ifTrue: "Adjust at least my height"
+ 			[wrapFlag
+ 				ifTrue: "Keep my width"
+ 					[ | box | 
+ 					box := self innerBounds. "i.e, no borders"
+ 					margins ifNotNil: [box := box insetBy: margins].
+ 					newExtent := box width @ self paragraph extent y]
+ 				ifFalse: "Adjust my width"
+ 					[ newExtent := self paragraph extent ].
- 	self isAutoFit 
- 		ifTrue: 
- 			[
- 			newExtent := self paragraph extent.
  			newExtent := newExtent + (2 * self borderWidth).
  				ifNotNil: [newExtent := ((0 @ 0 extent: newExtent) expandBy: margins) extent].
  			newExtent ~= bounds extent 
  					[(container isNil and: [successor isNil]) 
  							[para := paragraph.	"Save para (layoutChanged smashes it)"
  							super extent: newExtent.
  							paragraph := para]].
  			container notNil & successor isNil 
  					[cBounds := container bounds truncated.
  					"23 sept 2000 - try to allow vertical growth"
  					lastOfLines := self paragraph lines last.
  					heightOfLast := lastOfLines bottom - lastOfLines top.
  					(lastOfLines last < text size 
  						and: [lastOfLines bottom + heightOfLast >= self bottom]) 
  								[container releaseCachedState.
  								cBounds := cBounds origin corner: cBounds corner + (0 @ heightOfLast)].
  					self privateBounds: cBounds]].
  	"These statements should be pushed back into senders"
  	self paragraph positionWhenComposed: self position.
  	successor ifNotNil: [successor predecessorChanged].
  	self changed	"Too conservative: only paragraph composition
  					should cause invalidation."!

Item was added:
+ ----- Method: TextMorph>>numCharactersPerLine (in category 'layout') -----
+ numCharactersPerLine
+ 	^ numCharactersPerLine!

Item was added:
+ ----- Method: TextMorph>>numCharactersPerLine: (in category 'layout') -----
+ numCharactersPerLine: numCharsOrNil
+ 	"Reset composition rectangle to approx. numChars per line. Will be a perfect fit for monospaced fonts, average over multiple lines for other fonts. See commentary in #withNoLineLongerThan: and TextStyle >> #compositionWidthFor:."
+ 	numCharactersPerLine = numCharsOrNil ifTrue: [^ self].
+ 	numCharactersPerLine := numCharsOrNil.
+ 	self releaseParagraph; changed.!

Item was added:
+ ----- Method: TextMorph>>withNoLineLongerThan: (in category 'converting') -----
+ withNoLineLongerThan: numChars
+ 	"Convenience only to establish this protocol across String, Text, and TextMorph."
+ 	self numCharactersPerLine: numChars.
+ 	^ self paragraph asTextWithLineBreaks!

More information about the Squeak-dev mailing list