'From Squeak3.2alpha of 2 October 2001 [latest update: #4411] on 3 October 2001 at 9:40:32 am'! "Change Set: DoubleClickTimeout Date: 28 September 2001 Author: Joshua Gargus Update: 4398 Adds double-click timeout to EventHandler, and changes the sole place in the image that cares (PlayingCardMorph) to use the new scheme. This seems more uniform than the previous approach. Also refactors MouseClickState a bit in order to make it easier to change how the various click events are dispatched."! Object subclass: #EventHandler instanceVariableNames: 'mouseDownRecipient mouseDownSelector mouseMoveRecipient mouseMoveSelector mouseStillDownRecipient mouseStillDownSelector mouseUpRecipient mouseUpSelector mouseEnterRecipient mouseEnterSelector mouseLeaveRecipient mouseLeaveSelector mouseEnterDraggingRecipient mouseEnterDraggingSelector mouseLeaveDraggingRecipient mouseLeaveDraggingSelector keyStrokeRecipient keyStrokeSelector valueParameter startDragRecipient startDragSelector doubleClickSelector doubleClickRecipient clickSelector clickRecipient gestureSelector gestureRecipient gestureDictionaryOrName doubleClickTimeoutRecipient doubleClickTimeoutSelector ' classVariableNames: '' poolDictionaries: '' category: 'Morphic-Events'! Object subclass: #MouseClickState instanceVariableNames: 'clickClient clickState firstClickDown firstClickUp firstClickTime clickSelector dblClickSelector dblClickTime dblClickTimeoutSelector dragSelector dragThreshold ' classVariableNames: '' poolDictionaries: '' category: 'Morphic-Kernel'! !EventHandler methodsFor: 'initialization' stamp: 'jcg 9/21/2001 12:57'! forgetDispatchesTo: aSelector "aSelector is no longer implemented by my corresponding Player, so don't call it any more" mouseDownSelector == aSelector ifTrue: [mouseDownRecipient _ mouseDownSelector _ nil]. mouseMoveSelector == aSelector ifTrue: [mouseMoveRecipient _ mouseMoveSelector _ nil]. mouseStillDownSelector == aSelector ifTrue: [mouseStillDownRecipient _ mouseStillDownSelector _ nil]. mouseUpSelector == aSelector ifTrue: [mouseUpRecipient _ mouseUpSelector _ nil]. mouseEnterSelector == aSelector ifTrue: [mouseEnterRecipient _ mouseEnterSelector _ nil]. mouseLeaveSelector == aSelector ifTrue: [mouseLeaveRecipient _ mouseLeaveSelector _ nil]. mouseEnterDraggingSelector == aSelector ifTrue: [mouseEnterDraggingRecipient _ mouseEnterDraggingSelector _ nil]. mouseLeaveDraggingSelector == aSelector ifTrue: [mouseLeaveDraggingRecipient _ mouseLeaveDraggingSelector _ nil]. clickSelector == aSelector ifTrue: [clickRecipient _ clickSelector _ nil]. doubleClickSelector == aSelector ifTrue: [doubleClickRecipient _ doubleClickSelector _ nil]. doubleClickTimeoutSelector == aSelector ifTrue: [doubleClickTimeoutRecipient _ doubleClickTimeoutSelector _ nil]. keyStrokeSelector == aSelector ifTrue: [keyStrokeRecipient _ keyStrokeSelector _ nil].! ! !EventHandler methodsFor: 'initialization' stamp: 'jcg 9/21/2001 12:58'! on: eventName send: selector to: recipient eventName = #mouseDown ifTrue: [mouseDownRecipient _ recipient. mouseDownSelector _ selector. ^ self]. eventName = #mouseMove ifTrue: [mouseMoveRecipient _ recipient. mouseMoveSelector _ selector. ^ self]. eventName = #mouseStillDown ifTrue: [mouseStillDownRecipient _ recipient. mouseStillDownSelector _ selector. ^ self]. eventName = #mouseUp ifTrue: [mouseUpRecipient _ recipient. mouseUpSelector _ selector. ^ self]. eventName = #mouseEnter ifTrue: [mouseEnterRecipient _ recipient. mouseEnterSelector _ selector. ^ self]. eventName = #mouseLeave ifTrue: [mouseLeaveRecipient _ recipient. mouseLeaveSelector _ selector. ^ self]. eventName = #mouseEnterDragging ifTrue: [mouseEnterDraggingRecipient _ recipient. mouseEnterDraggingSelector _ selector. ^ self]. eventName = #mouseLeaveDragging ifTrue: [mouseLeaveDraggingRecipient _ recipient. mouseLeaveDraggingSelector _ selector. ^ self]. eventName = #click ifTrue: [clickRecipient _ recipient. clickSelector _ selector. ^ self]. eventName = #doubleClick ifTrue: [doubleClickRecipient _ recipient. doubleClickSelector _ selector. ^ self]. eventName = #doubleClickTimeout ifTrue: [doubleClickTimeoutRecipient _ recipient. doubleClickTimeoutSelector _ selector. ^ self]. eventName = #startDrag ifTrue: [startDragRecipient _ recipient. startDragSelector _ selector. ^ self]. eventName = #keyStroke ifTrue: [keyStrokeRecipient _ recipient. keyStrokeSelector _ selector. ^ self]. eventName = #gesture ifTrue: [gestureRecipient _ recipient. gestureSelector _ selector. ^ self]. self error: 'Event name, ' , eventName , ' is not recognizable.' ! ! !EventHandler methodsFor: 'events' stamp: 'jcg 9/21/2001 13:06'! doubleClickTimeout: event fromMorph: sourceMorph ^ self send: doubleClickTimeoutSelector to: doubleClickTimeoutRecipient withEvent: event fromMorph: sourceMorph! ! !Morph methodsFor: 'event handling' stamp: 'jcg 10/2/2001 09:26'! doubleClickTimeout: evt "Handle a double-click timeout event. This message is only sent to clients that request it by sending #waitForClicksOrDrag:event: to the initiating hand in their mouseDown: method. This default implementation does nothing." self eventHandler ifNotNil: [self eventHandler doubleClickTimeout: evt fromMorph: self].! ! !Morph methodsFor: 'meta-actions' stamp: 'jcg 9/21/2001 13:22'! blueButtonDown: anEvent "Special gestures (cmd-mouse on the Macintosh; Alt-mouse on Windows and Unix) allow a mouse-sensitive morph to be moved or bring up a halo for the morph." | h tfm doNotDrag | h _ anEvent hand halo. "Prevent wrap around halo transfers originating from throwing the event back in" doNotDrag _ false. h ifNotNil:[ (h innerTarget == self) ifTrue:[doNotDrag _ true]. (h innerTarget hasOwner: self) ifTrue:[doNotDrag _ true]. (self hasOwner: h target) ifTrue:[doNotDrag _ true]]. tfm _ (self transformedFrom: nil) inverseTransformation. "cmd-drag on flexed morphs works better this way" h _ self addHalo: (anEvent transformedBy: tfm). doNotDrag ifTrue:[^self]. "Initiate drag transition if requested" anEvent hand waitForClicksOrDrag: h event: (anEvent transformedBy: tfm) selectors: { nil. nil. nil. #dragTarget:. } threshold: 5. "Pass focus explicitly here" anEvent hand newMouseFocus: h.! ! !HaloMorph methodsFor: 'events' stamp: 'jcg 9/21/2001 13:18'! blueButtonDown: event "Transfer the halo to the next likely recipient" target ifNil:[^self delete]. event hand obtainHalo: self. positionOffset _ event position - (target point: target position in: owner). self isMagicHalo ifTrue:[ self isMagicHalo: false. ^self magicAlpha: 1.0]. "wait for drags or transfer" event hand waitForClicksOrDrag: self event: event selectors: { #transferHalo:. nil. nil. #dragTarget:. } threshold: 5.! ! !HandMorph methodsFor: 'double click support' stamp: 'jcg 9/21/2001 13:22'! waitForClicksOrDrag: aMorph event: evt "Wait until the difference between click, double-click, or drag gesture is known, then inform the given morph what transpired. This message is sent when the given morph first receives a mouse-down event. If the mouse button goes up, then down again within DoubleClickTime, then 'doubleClick: evt' is sent to the morph. If the mouse button goes up but not down again within DoubleClickTime, then the message 'click: evt' is sent to the morph. Finally, if the button does not go up within DoubleClickTime, then 'drag: evt' is sent to the morph. In all cases, the event supplied is the original mouseDown event that initiated the gesture. mouseMove: and mouseUp: events are not sent to the morph until it becomes the mouse focus, which is typically done by the client in its click:, doubleClick:, or drag: methods." ^self waitForClicksOrDrag: aMorph event: evt selectors: #( #click: #doubleClick: #doubleClickTimeout: #startDrag:) threshold: 10! ! !HandMorph methodsFor: 'double click support' stamp: 'jcg 9/21/2001 13:19'! waitForClicksOrDrag: aMorph event: evt selectors: clickAndDragSelectors threshold: threshold "Wait until the difference between click, double-click, or drag gesture is known, then inform the given morph what transpired. This message is sent when the given morph first receives a mouse-down event. If the mouse button goes up, then down again within DoubleClickTime, then 'doubleClick: evt' is sent to the morph. If the mouse button goes up but not down again within DoubleClickTime, then the message 'click: evt' is sent to the morph. Finally, if the button does not go up within DoubleClickTime, then 'drag: evt' is sent to the morph. In all cases, the event supplied is the original mouseDown event that initiated the gesture. mouseMove: and mouseUp: events are not sent to the morph until it becomes the mouse focus, which is typically done by the client in its click:, doubleClick:, or drag: methods." mouseClickState _ MouseClickState new client: aMorph click: clickAndDragSelectors first dblClick: clickAndDragSelectors second dblClickTime: DoubleClickTime dblClickTimeout: clickAndDragSelectors third drag: clickAndDragSelectors fourth threshold: threshold event: evt.! ! !MouseClickState methodsFor: 'initialize' stamp: 'jcg 9/21/2001 13:08'! client: aMorph click: aClickSelector dblClick: aDblClickSelector dblClickTime: timeOut dblClickTimeout: aDblClickTimeoutSelector drag: aDragSelector threshold: aNumber event: firstClickEvent clickClient _ aMorph. clickSelector _ aClickSelector. dblClickSelector _ aDblClickSelector. dblClickTime _ timeOut. dblClickTimeoutSelector _ aDblClickTimeoutSelector. dragSelector _ aDragSelector. dragThreshold _ aNumber. firstClickDown _ firstClickEvent. firstClickTime _ firstClickEvent timeStamp. clickState _ #firstClickDown.! ! !MouseClickState methodsFor: 'event handling' stamp: 'jcg 9/21/2001 11:23'! click clickSelector ifNotNil: [clickClient perform: clickSelector with: firstClickDown]! ! !MouseClickState methodsFor: 'event handling' stamp: 'jcg 9/21/2001 11:24'! doubleClick dblClickSelector ifNotNil: [clickClient perform: dblClickSelector with: firstClickDown]! ! !MouseClickState methodsFor: 'event handling' stamp: 'jcg 9/21/2001 13:09'! doubleClickTimeout dblClickTimeoutSelector ifNotNil: [ clickClient perform: dblClickTimeoutSelector with: firstClickDown]! ! !MouseClickState methodsFor: 'event handling' stamp: 'jcg 9/21/2001 11:27'! drag: event dragSelector ifNotNil: [clickClient perform: dragSelector with: event]! ! !MouseClickState methodsFor: 'event handling' stamp: 'jcg 9/21/2001 13:16'! handleEvent: evt from: aHand "Process the given mouse event to detect a click, double-click, or drag. Return true if the event should be processed by the sender, false if it shouldn't. NOTE: This method heavily relies on getting *all* mouse button events." | localEvt timedOut isDrag | timedOut _ (evt timeStamp - firstClickTime) > dblClickTime. localEvt _ evt transformedBy: (clickClient transformedFrom: aHand owner). isDrag _ (localEvt cursorPoint - firstClickDown cursorPoint) r > dragThreshold. clickState == #firstClickDown ifTrue: [ "Careful here - if we had a slow cycle we may have a timedOut mouseUp event" (timedOut and:[localEvt isMouseUp not]) ifTrue:[ "timeout before #mouseUp -> keep waiting for drag if requested" clickState _ #firstClickTimedOut. dragSelector ifNil:[ aHand resetClickState. self doubleClickTimeout; click "***"]. ^true]. localEvt isMouseUp ifTrue:[ (timedOut or:[dblClickSelector isNil]) ifTrue:[ self click. aHand resetClickState. ^true]. "Otherwise transfer to #firstClickUp" firstClickUp _ evt copy. clickState _ #firstClickUp. "If timedOut or the client's not interested in dbl clicks get outta here" self click. aHand handleEvent: firstClickUp. ^false]. isDrag ifTrue:["drag start" self doubleClickTimeout. "***" aHand resetClickState. dragSelector "If no drag selector send #click instead" ifNil: [self click] ifNotNil: [self drag: localEvt]. ^true]. ^false]. clickState == #firstClickTimedOut ifTrue:[ localEvt isMouseUp ifTrue:["neither drag nor double click" aHand resetClickState. self doubleClickTimeout; click. "***" ^true]. isDrag ifTrue:["drag start" aHand resetClickState. self doubleClickTimeout; drag: localEvt. "***" ^true]. ^false]. clickState = #firstClickUp ifTrue:[ (timedOut) ifTrue:[ "timed out after mouseUp - send #click: and mouseUp" aHand resetClickState. self doubleClickTimeout. "***" ^true]. localEvt isMouseDown ifTrue:["double click" aHand resetClickState. self doubleClick. ^false]]. ^true! ! !PasteUpMorph methodsFor: 'event handling' stamp: 'jcg 9/21/2001 13:22'! mouseDown: evt "Handle a mouse down event." | grabbedMorph handHadHalos | grabbedMorph _ self morphToGrab: evt. grabbedMorph ifNotNil:[ grabbedMorph isSticky ifTrue:[^self]. self isPartsBin ifFalse:[^evt hand grabMorph: grabbedMorph]. grabbedMorph _ grabbedMorph partRepresented duplicate. grabbedMorph restoreSuspendedEventHandler. (grabbedMorph fullBounds containsPoint: evt position) ifFalse:[grabbedMorph position: evt position]. "Note: grabbedMorph is ownerless after duplicate so use #grabMorph:from: instead" ^evt hand grabMorph: grabbedMorph from: self]. (super handlesMouseDown: evt) ifTrue:[^super mouseDown: evt]. handHadHalos _ evt hand halo notNil. evt hand halo: nil. "shake off halos" evt hand releaseKeyboardFocus. "shake of keyboard foci" evt shiftPressed ifTrue:[ ^evt hand waitForClicksOrDrag: self event: evt selectors: { #findWindow:. nil. nil. #dragThroughOnDesktop:} threshold: 5]. self isWorldMorph ifTrue: [ handHadHalos ifTrue: [^self addAlarm: #invokeWorldMenu: with: evt after: 200]. ^self invokeWorldMenu: evt ]. "otherwise, explicitly ignore the event if we're not the world, so that we could be picked up if need be" self isWorldMorph ifFalse:[evt wasHandled: false]. ! ! !PlayingCardMorph methodsFor: 'events' stamp: 'jcg 9/21/2001 13:25'! mouseDown: evt "Do nothing upon mouse-down except inform the hand to watch for a double-click; wait until an ensuing click:, doubleClick:, or drag: message gets dispatched" evt hand waitForClicksOrDrag: self event: evt selectors: { #click:. #doubleClick:. #firstClickTimedOut:. nil} threshold: 5! ! !PluggableListMorph methodsFor: 'events' stamp: 'jcg 9/21/2001 13:21'! mouseDown: evt | aMorph selectors | evt yellowButtonPressed "First check for option (menu) click" ifTrue: [^ self yellowButtonActivity: evt shiftPressed]. aMorph _ self itemFromPoint: evt position. aMorph ifNil:[^super mouseDown: evt]. self dragEnabled ifTrue: [aMorph highlightForMouseDown]. selectors _ Array with: #click: with: (doubleClickSelector ifNotNil:[#doubleClick:]) with: nil with: (self dragEnabled ifTrue:[#startDrag:] ifFalse:[nil]). evt hand waitForClicksOrDrag: self event: evt selectors: selectors threshold: 10 "pixels".! ! !SimpleHierarchicalListMorph methodsFor: 'events' stamp: 'jcg 9/21/2001 13:23'! mouseDown: evt | aMorph selectors | aMorph _ self itemFromPoint: evt position. (aMorph notNil and:[aMorph inToggleArea: (aMorph point: evt position from: self)]) ifTrue:[^self toggleExpandedState: aMorph event: evt]. evt yellowButtonPressed "First check for option (menu) click" ifTrue: [^ self yellowButtonActivity: evt shiftPressed]. aMorph ifNil:[^super mouseDown: evt]. aMorph highlightForMouseDown. selectors _ Array with: #click: with: nil with: nil with: (self dragEnabled ifTrue:[#startDrag:] ifFalse:[nil]). evt hand waitForClicksOrDrag: self event: evt selectors: selectors threshold: 10 "pixels".! ! MouseClickState removeSelector: #client:click:dblClick:dblClickTime:drag:threshold:event:! Object subclass: #EventHandler instanceVariableNames: 'mouseDownRecipient mouseDownSelector mouseMoveRecipient mouseMoveSelector mouseStillDownRecipient mouseStillDownSelector mouseUpRecipient mouseUpSelector mouseEnterRecipient mouseEnterSelector mouseLeaveRecipient mouseLeaveSelector mouseEnterDraggingRecipient mouseEnterDraggingSelector mouseLeaveDraggingRecipient mouseLeaveDraggingSelector keyStrokeRecipient keyStrokeSelector valueParameter startDragRecipient startDragSelector doubleClickSelector doubleClickRecipient doubleClickTimeoutSelector doubleClickTimeoutRecipient clickSelector clickRecipient gestureSelector gestureRecipient gestureDictionaryOrName ' classVariableNames: '' poolDictionaries: '' category: 'Morphic-Events'!