2015.04.13: Completely replaced the previous code with a new and better implementation.
When handling mouse-clicks in an application that must differentiate between single and double-clicks (and even triple-clicks) is a job that just begs for code reuse. As you may known, when the application receives a "triple-click-mouse-up" event, it has already received a single-click-mouse-up event and a double-click-mouse-up event. Any double-click-mouse-up or triple-click-mouse-up event may have to cancel the operation of the preceding event.
In this post I have included the source code for BRMouseUpView, a child class of NSView that replaces the mouseUp function with a "filteredMouseUp" function. The filteredMouseUp function will only be called for the last mouseUp event in a sequence. Be aware that this also means that any mouseclick action is called with a small delay. This delay depends on the "Double-Click speed" setting of the user in his "Mouse" Control Panel. This may result in an unacceptable user experience. If you use this code, please make sure that you application remains usable even when the user sets the double-click speed to its slowest value.
The code:
import Foundation
import Cocoa
import Dispatch
class BRMouseUpView: NSView {
final let systemDoubleClickDelay = NSEvent.doubleClickInterval()
private final var lastMouseUpEvent: NSEvent!
final var maxClickCount = 2
private func delayedMouseUpAction(event: NSEvent) {
if event.clickCount >= lastMouseUpEvent.clickCount {
filteredMouseUp(event)
}
final override func mouseUp(theEvent: NSEvent) {
[unowned self] in
if theEvent.clickCount == 0 {
self.filteredMouseUp(theEvent)
return
}
self.lastMouseUpEvent = theEvent
if theEvent.clickCount == self.maxClickCount {
self.filteredMouseUp(theEvent)
return
} else if theEvent.clickCount < self.maxClickCount {
let executionTime = dispatch_time(DISPATCH_TIME_NOW, Int64(1_000_000_000 * self.systemDoubleClickDelay))
dispatch_after(executionTime, dispatch_get_main_queue(), {
[unowned self] in self.delayedMouseUpAction(theEvent)
})
}
})
}
func filteredMouseUp(theEvent: NSEvent) {
}
import Cocoa
import Dispatch
/// This class replaces the "mouseUp" function with a "filteredMouseUp" function. The filteredMouseUp function is only called for the last mouseUp even in a sequence of mouseUp events. A sequence of mouseUp events is defined by the "doubleClickInterval" (which is set by the user in the mouse control panel) and the maximum number of mouseUp clicks in a sequence as defined by the variable "maxClickCount".
///
/// Note: A mouseUp event for the end of a dragging sequence has a clickCount of 0, this mouseUp call is not filtered out!
///
/// Note2: By default the maxClickCount is set to 2, limiting the detection to drag-end-clicks, single-clicks and double-clicks. Set this variable to your desired maximum sequence length. Note that filteredMouseUp is never called for an event with a clickcount higher than this number.
//
// A suggested use of the filteredMouseUp is as follows:
//
// override func filteredMouseUp(theEvent: NSEvent) {
// switch theEvent.clickCount {
// case 0: draggingEnded(theEvent)
// case 1: singleMouseClick(theEvent)
// case 2: doubleMouseCLick(theEvent)
// etc...
// }
// }
// The double click time as defined by the user in the system preferences panel.
// The last mouse up event
// Speed up the user experience by defining the maximum number of clicks in a sequence.
// "filteredMouseUp" will not receive events with a higher clickCount than defined in this variable.
// This function will execute the last mouseUp in a sequence of mouseUp events
// Check if a newer event is present in the lastMouseUpEvent. Done by comparing the clickcount.
// It is the same, no new mouseUp was received since this function was pushed on the queue
}
// else: no else, if the old clickcount is lower than the lastMouseUpEvent clickcount, this call should be ignored.
}
// Handles the mouseUp event. Should not be overriden (there should be no need anymore to override this implementation).
dispatch_async(dispatch_get_main_queue(), {
// Special case: end of dragging, always call the filteredMouseUp for this case
self.filteredMouseUp(theEvent)
return
}
// Store the latest event, this is used to discard older events
// Speed up the user experience for the max click count
return
// Dispatch this event, it will be canceled if another mouseUp event occurs within the doubleClickInterval.
dispatch_after(executionTime, dispatch_get_main_queue(), {
[unowned self] in self.delayedMouseUpAction(theEvent)
})
}
})
}
/// Handles a double mouse click on the mouse-up event. To be overriden by a child class
// Remove or replace as necessary
log.atLevelWarning(id: 0, source: "BRMouseHandlingView.handleMouseUpEvent", message: "Childview from BRMouseHandlingView should implement handleMouseUpEvent")
}}
PS: I use SwifterLog as the logging framework, if you use something else, please adjust the logging calls where needed.
Happy coding...
Did this help?, then please help out a small independent.
If you decide that you want to make a small donation, you can do so by clicking this
link: a cup of coffee ($2) or use the popup on the right hand side for different amounts.
Payments will be processed by PayPal, receiver will be sales at balancingrock dot nl
Bitcoins will be gladly accepted at: 1GacSREBxPy1yskLMc9de2nofNv2SNdwqH
We don't get the world we wish for... we get the world we pay for.
Thanks for sharing this article about click speed test. But I am little bit confuse that where can I test my click speed test?
ReplyDelete