Hi Marcel,

here is a tiny regression from your change: new text actions can only be clicked after moving the mouse. For instance, with #interactivePrintIt being enabled, when the TextInspectIt appears directly under the cursor, I cannot trigger its action without moving the mouse first. The same happens after adding a URL to the text selection under the cursor using the keyboard etc. Likewise, after doing Cmd+0 on the selection, I can still click the URL.

So, a naive fix would be to send mouseEnterOnTextAction: again after each change to the emphasis of the text. Maybe you know a better way to fix this? :-)

Best,
Christoph

---
Sent from Squeak Inbox Talk

On 2023-08-17T10:32:29+02:00, marcel.taeumel@hpi.de wrote:

> Yes, you can also notice this with regular links without any extra text anchor/morph ... they are somehow off by a few pixels to the left ... does not affect regular selection though...
> Am 17.08.2023 09:00:25 schrieb christoph.thiede(a)student.hpi.uni-potsdam.de <christoph.thiede(a)student.hpi.uni-potsdam.de>:
> Very nice, thank you!
>
> The logic for mapping the cursor position to links is still buggy (e.g., in the screenshot when hovering at the X), but this seems to be a different issue. :-)
>
> Best,
> Christoph
>
> ---
> Sent from Squeak Inbox Talk [https://github.com/hpi-swa-lab/squeak-inbox-talk]
>
> On 2023-07-26T15:15:40+00:00, commits(a)source.squeak.org wrote:
>
> > Marcel Taeumel uploaded a new version of Morphic to project The Trunk:
> > http://source.squeak.org/trunk/Morphic-mt.2116.mcz
> >
> > ==================== Summary ====================
> >
> > Name: Morphic-mt.2116
> > Author: mt
> > Time: 26 July 2023, 5:15:38.140063 pm
> > UUID: ccde047a-5845-5345-8989-e1f2c738612c
> > Ancestors: Morphic-mt.2115
> >
> > In Morphic, redesign how text actions are processed:
> > - underline actions on mouse hover
> > - integrate with Morphic UI loop, i.e., no busy wait for mouse-up
> > - allow for selecting text from within text-action ranges for a better support of the preference "Interactive print-it"
> >
> > Unfortunately, the current text selection is not preserved anymore when just clicking a text action at the moment. Hmmm...
> >
> > =============== Diff against Morphic-mt.2115 ===============
> >
> > Item was removed:
> > - ----- Method: NewParagraph>>clickAt:for:controller: (in category 'editing') -----
> > - clickAt: clickPoint for: model controller: editor
> > -     "Give sensitive text a chance to fire. Display flash: (100(a)100 extent: 100(a)100)."
> > -     | startBlock action |
> > -     action := false.
> > -     startBlock := self characterBlockAtPoint: clickPoint.
> > -     (text attributesAt: startBlock stringIndex forStyle: textStyle)
> > -         do: [:att | | range target box boxes |
> > -             att mayActOnClick ifTrue:
> > -                 [(target := model) ifNil: [target := editor morph].
> > -                 range := text rangeOf: att startingAt: startBlock stringIndex.
> > -                 boxes := self selectionRectsFrom: (self characterBlockForIndex: range first)
> > -                             to: (self characterBlockForIndex: range last+1).
> > -                 box := boxes detect: [:each | each containsPoint: clickPoint] ifNone: [nil].
> > -                 box ifNotNil:
> > -                     [ box := (editor transformFrom: nil) invertBoundsRect: box.
> > -                     editor morph allOwnersDo: [ :m | box := box intersect: (m boundsInWorld) ].
> > -                     self flag: #fix. "mt: Make it stateful and with real events."
> > -                     Utilities awaitMouseUpIn: box
> > -                         repeating: []
> > -                         ifSucceed: [(att actOnClickFor: target in: self at: clickPoint editor: editor) ifTrue: [action := true]].
> > -                     Cursor currentCursor == Cursor webLink ifTrue:[Cursor normal show].
> > -                 ]]].
> > -     ^ action!
> >
> > Item was changed:
> > ----- Method: TextEditor>>mouseDown: (in category 'events') -----
> > mouseDown: evt
> >     "Either 1) handle text actions in the paragraph, 2) begin a text drag operation, or 3) modify the caret/selection."
> >     
> > -     | clickPoint b |
> > -
> >     oldInterval := self selectionInterval.
> > +     (self mouseDownOnTextAction: evt) ifTrue: [^ self].
> > -     clickPoint := evt cursorPoint.
> > -     b := paragraph characterBlockAtPoint: clickPoint.
> >
> > -     (paragraph clickAt: clickPoint for: model controller: self) ifTrue: [
> > -         self flag: #note. "mt: Do not reset the current text selection for successful text actions. Leave markBlock and pointBlock as is. This behavior matches the one in web browsers when clicking on links."
> > -         evt hand releaseKeyboardFocus: morph.
> > -         evt hand releaseMouseFocus: morph.
> > -         ^ self ].
> > -     
> >     (morph dragEnabled and: [self isEventInSelection: evt]) ifTrue: [
> >         evt hand
> >             waitForClicksOrDrag: morph
> >             event: evt
> >             selectors: {#click:. nil. nil. #startDrag:}
> >             threshold: HandMorph dragThreshold.
> >         morph setProperty: #waitingForTextDrag toValue: true.
> >         ^ self].
> >     
> >     evt shiftPressed
> > +         ifFalse: [ | b |
> > -         ifFalse: [
> >             self closeTypeIn.
> > +             b := paragraph characterBlockAtPoint: evt position.
> >             markBlock := b.
> >             pointBlock := b ]
> >          ifTrue: [
> >             self closeTypeIn.
> >             self mouseMove: evt ].
> > +         
> > + self storeSelectionInParagraph.!
> > - self storeSelectionInParagraph!
> >
> > Item was added:
> > + ----- Method: TextEditor>>mouseDownOnTextAction: (in category 'events') -----
> > + mouseDownOnTextAction: evt
> > +
> > +     self flag: #todo. "mt: Do not reset the current text selection for successful text actions. Leave markBlock and pointBlock as is. This behavior matches the one in web browsers when clicking on links."
> > +     ^ false!
> >
> > Item was added:
> > + ----- Method: TextEditor>>mouseEnterOnTextAction: (in category 'events') -----
> > + mouseEnterOnTextAction: evt
> > +     "Manage mouse-hovering effects for text actions."
> > +     
> > +     | index highlights |
> > +     self removeHighlightedTextActions.
> > +     
> > +     index := (paragraph characterBlockAtPoint: evt position) stringIndex.
> > +     (paragraph text attributesAt: index forStyle: paragraph textStyle)
> > +         do: [:attr | | highlight range |
> > +             attr mayActOnClick ifTrue: [
> > +                 highlights ifNil: [
> > +                     morph
> > +                         setProperty: #highlightedTextActions
> > +                         toValue: (highlights := OrderedCollection new)].
> > +                 highlight := TextEmphasis underlined.
> > +                 range := paragraph text rangeOf: attr startingAt: index.
> > +                 highlights add: {attr. highlight. range}.
> > +                 paragraph text
> > +                     addAttribute: highlight
> > +                     from: range start to: range stop]].
> > +             
> > +     highlights
> > +         ifNil: [self updateCursorForEvent: evt]
> > +         ifNotNil: [
> > +             morph updateFromParagraph.
> > +             evt hand showTemporaryCursor: Cursor webLink].!
> >
> > Item was changed:
> > ----- Method: TextEditor>>mouseUp: (in category 'events') -----
> > mouseUp: evt
> >     "An attempt to break up the old processRedButton code into threee phases"
> >
> > +     "0) Click on text actions."
> > +     (self mouseUpOnTextAction: evt) ifTrue: [^ self].
> > +
> > +     "1) A 'double-click' will result in selecting the whole word."
> > +     (self hasCaret and: [oldInterval = self selectionInterval])
> > -     oldInterval ifNil: [^ self]. "Patched during clickAt: repair"
> > -     (self hasCaret
> > -         and: [oldInterval = self selectionInterval])
> >         ifTrue: [self selectWord].
> > +     
> > +     "2) For the next type-in, configure emphasis. We don't want to do this on
> > +     every key-press for performance reasons."
> >     self setEmphasisHere.
> > +     
> > +     "3) Notice selection changes."
> > +     (self isDisjointFrom: oldInterval)
> > +         ifTrue: [otherInterval := oldInterval].
> > -     (self isDisjointFrom: oldInterval) ifTrue:
> > -         [otherInterval := oldInterval].
> >     self storeSelectionInParagraph.
> >
> > +     "4) Reset mouse cursor to account for selection changes."
> >     self updateCursorForEvent: evt.
> >     morph removeProperty: #waitingForTextDrag.!
> >
> > Item was added:
> > + ----- Method: TextEditor>>mouseUpOnTextAction: (in category 'events') -----
> > + mouseUpOnTextAction: evt
> > +
> > +     | target |
> > +     "Do not trigger text action if start and stop of the selection are within that action range."
> > +     self hasCaret ifFalse: [
> > +         self removeHighlightedTextActions.
> > +         ^ false].
> > +     
> > +     (morph hasProperty: #highlightedTextActions) ifTrue: [
> > +         target := model ifNil: [morph].
> > +         (morph valueOfProperty: #highlightedTextActions) do: [:ea |
> > +             ea first
> > +                 actOnClickFor: target
> > +                 in: paragraph
> > +                 at: evt position
> > +                 editor: self].
> > +         
> > +         self removeHighlightedTextActions.
> > +         morph removeProperty: #waitingForTextDrag.
> > +         evt hand releaseKeyboardFocus: morph.
> > +         evt hand releaseMouseFocus: morph.
> > +         ^ true].
> > +     
> > +     ^ false!
> >
> > Item was added:
> > + ----- Method: TextEditor>>removeHighlightedTextActions (in category 'events') -----
> > + removeHighlightedTextActions
> > +
> > +     (morph hasProperty: #highlightedTextActions)
> > +         ifTrue: [
> > +             (morph valueOfProperty: #highlightedTextActions) do: [:ea |
> > +                 paragraph text
> > +                     removeAttribute: ea second
> > +                     from: ea third start to: ea third stop].
> > +             morph removeProperty: #highlightedTextActions.
> > +             morph updateFromParagraph].!
> >
> > Item was removed:
> > - ----- Method: TextMorph>>enterClickableRegion: (in category 'editing') -----
> > - enterClickableRegion: evt
> > -     | index isLink |
> > -     evt hand hasSubmorphs ifTrue:[^false].
> > -     paragraph ifNotNil:[
> > -         index := (paragraph characterBlockAtPoint: evt position) stringIndex.
> > -         isLink := (paragraph text attributesAt: index forStyle: paragraph textStyle)
> > -                     anySatisfy:[:attr| attr mayActOnClick].
> > -         isLink ifTrue: [
> > -             evt hand showTemporaryCursor: Cursor webLink.
> > -             ^ true]].
> > -     ^ false
> > - !
> >
> > Item was removed:
> > - ----- Method: TextMorph>>handleMouseMove: (in category 'events-processing') -----
> > - handleMouseMove: anEvent
> > -     "Re-implemented to allow for mouse-up move events"
> > -     anEvent wasHandled ifTrue:[^self]. "not interested"
> > -     (anEvent hand hasSubmorphs) ifTrue:[^self].
> > -     anEvent wasHandled: true.
> > -     self mouseMove: anEvent.
> > -     (anEvent anyButtonPressed and:[anEvent hand mouseFocus == self]) ifFalse:[^self].
> > -     (self handlesMouseStillDown: anEvent) ifTrue:[
> > -         "Step at the new location"
> > -         self startStepping: #handleMouseStillDown:
> > -             at: Time millisecondClockValue
> > -             arguments: {anEvent copy resetHandlerFields}
> > -             stepTime: 1].
> > - !
> >
> > Item was added:
> > + ----- Method: TextMorph>>handlesMouseMove: (in category 'event handling') -----
> > + handlesMouseMove: anEvent
> > +     "Handle all mouse-move events unless something is attached to the hand."
> > +     
> > +     ^ anEvent hand hasSubmorphs not!
> >
> > Item was added:
> > + ----- Method: TextMorph>>handlesMouseStillDown: (in category 'event handling') -----
> > + handlesMouseStillDown: anEvent
> > +
> > +      (anEvent anyButtonPressed and: [anEvent hand mouseFocus == self]) ifFalse: [^ false].
> > +     ^ super handlesMouseStillDown: anEvent!
> >
> > Item was changed:
> > ----- Method: TextMorph>>mouseLeave: (in category 'event handling') -----
> > mouseLeave: evt
> >
> > +     evt hand showTemporaryCursor: nil.
> > +     self editor removeHighlightedTextActions.!
> > -     evt hand showTemporaryCursor: nil.!
> >
> > Item was changed:
> > ----- Method: TextMorph>>mouseMove: (in category 'event handling') -----
> > mouseMove: evt
> >
> >     evt redButtonPressed ifFalse: [
> > +         "Avoid expensive #handleInteraction:fromEvent: wrapper here."
> > +         self editor mouseEnterOnTextAction: evt.
> > -         (self enterClickableRegion: evt)
> > -             ifFalse: [self editor updateCursorForEvent: evt].
> >         ^ self].
> >
> >     self
> >         handleInteraction: [self editor mouseMove: evt]
> >         fromEvent: evt.!
> >
> > Item was changed:
> > ----- Method: TextMorph>>startDrag: (in category 'event handling') -----
> > startDrag: evt
> >
> >     self removeProperty: #waitingForTextDrag.
> > +     self editor removeHighlightedTextActions.
> >
> >     [evt hand grabMorph: (TransferMorph withPassenger: self selection from: self)]
> >         ensure: [evt hand releaseMouseFocus: self].!
> ["TextAction-wrong-pos.png"]