1/* 2 PPGNUstepGlue_SliderDragging.m 3 4 Copyright 2014-2018 Josh Freeman 5 http://www.twilightedge.com 6 7 This file is part of PikoPixel for GNUstep. 8 PikoPixel is a graphical application for drawing & editing pixel-art images. 9 10 PikoPixel is free software: you can redistribute it and/or modify it under 11 the terms of the GNU Affero General Public License as published by the 12 Free Software Foundation, either version 3 of the License, or (at your 13 option) any later version approved for PikoPixel by its copyright holder (or 14 an authorized proxy). 15 16 PikoPixel is distributed in the hope that it will be useful, but WITHOUT ANY 17 WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 18 FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more 19 details. 20 21 You should have received a copy of the GNU Affero General Public License 22 along with this program. If not, see <http://www.gnu.org/licenses/>. 23*/ 24 25// Three fixes for dragging sliders on GNUstep: 26// 1) Better responsiveness on sliders that control a slow operation (such as the navigator 27// popup's zoom slider); The fix works by processing only the last mouseDragged event in the 28// event queue (discarding earlier mouseDragged events) so the expensive operation can be 29// skipped for out-of-date events that correspond to an old mouse position. 30// 2) Make the mouse-tracking behavior the same as on OS X: When the dragging-mouse is moved 31// outside the bounds of the slider control, Macs continue sending the control's action message, 32// but GNUstep does not; The fix is to patch -[NSView mouse:inRect:] for the three NSView 33// subclasses that contain NSSliderCells in PikoPixel (PPParabolicSlider, NSSlider, 34// & PPLayersTableView), so they always return YES while tracking. When 35// -[NSCell trackMouse:inRect:ofView:untilMouseUp:] calls one of the patched methods to check 36// whether the mouse is still inside the rect, it will receive YES no matter where the mouse is, 37// so the control's action message will always be sent. 38// 3) Workaround for rare crash in GSHorizontalTypesetter while dragging a slider with an 39// empty title; Patched init & initWithCoder: methods to return a slider with its title cell 40// set to nil (so that when -[NSSliderCell drawInteriorWithFrame:...] calls 41// [_titleCell drawInteriorWithFrame:...], it's a no-op), and patched setTitle: so that when 42// setting a non-empty title, it first sets up a non-nil title cell. 43 44#ifdef GNUSTEP 45 46#import <Cocoa/Cocoa.h> 47#import "NSObject_PPUtilities.h" 48#import "PPAppBootUtilities.h" 49#import "PPApplication.h" 50#import "PPParabolicSlider.h" 51#import "PPLayersTableView.h" 52#import "NSEvent_PPUtilities.h" 53 54 55static bool gIsTrackingMouseInSlider = NO; 56 57 58@implementation NSObject (PPGNUstepGlue_SliderDragging) 59 60+ (void) ppGSGlue_SliderDragging_InstallPatches 61{ 62 macroSwizzleInstanceMethod(NSSliderCell, init, ppGSPatch_Init); 63 64 macroSwizzleInstanceMethod(NSSliderCell, initWithCoder:, ppGSPatch_InitWithCoder:); 65 66 macroSwizzleInstanceMethod(NSSliderCell, setTitle:, ppGSPatch_SetTitle:); 67 68 macroSwizzleInstanceMethod(NSSliderCell, startTrackingAt:inView:, 69 ppGSPatch_StartTrackingAt:inView:); 70 71 macroSwizzleInstanceMethod(NSSliderCell, stopTracking:at:inView:mouseIsUp:, 72 ppGSPatch_StopTracking:at:inView:mouseIsUp:); 73 74 75 macroSwizzleInstanceMethod(PPApplication, nextEventMatchingMask:untilDate:inMode:dequeue:, 76 ppGSPatch_NextEventMatchingMask:untilDate:inMode:dequeue:); 77 78 79 // Patch -[NSView mouse:inRect:] for all NSView subclasses that contain an NSSliderCell in 80 // PikoPixel: PPParabolicSlider, NSSlider, PPLayersTableView 81 82 macroSwizzleInstanceMethod(PPParabolicSlider, mouse:inRect:, ppGSPatch_Mouse:inRect:); 83 84 macroSwizzleInstanceMethod(NSSlider, mouse:inRect:, ppGSPatch_Mouse:inRect:); 85 86 macroSwizzleInstanceMethod(PPLayersTableView, mouse:inRect:, ppGSPatch_Mouse:inRect:); 87} 88 89+ (void) load 90{ 91 macroPerformNSObjectSelectorAfterAppLoads(ppGSGlue_SliderDragging_InstallPatches); 92} 93 94@end 95 96@implementation NSSliderCell (PPGNUstepGlue_SliderDragging) 97 98- (id) ppGSPatch_Init 99{ 100 self = [self ppGSPatch_Init]; 101 102 [self setTitleCell: nil]; 103 104 return self; 105} 106 107- (id) ppGSPatch_InitWithCoder: (NSCoder *) decoder 108{ 109 self = [self ppGSPatch_InitWithCoder: decoder]; 110 111 [self setTitleCell: nil]; 112 113 return self; 114} 115 116- (void) ppGSPatch_SetTitle: (NSString *) title 117{ 118 if (![self titleCell] && [title length]) 119 { 120 NSTextFieldCell *titleCell = [[[NSTextFieldCell alloc] init] autorelease]; 121 122 [titleCell setTextColor: [NSColor controlTextColor]]; 123 [titleCell setAlignment: NSCenterTextAlignment]; 124 125 [self setTitleCell: titleCell]; 126 } 127 128 [self ppGSPatch_SetTitle: title]; 129} 130 131- (BOOL) ppGSPatch_StartTrackingAt: (NSPoint) startPoint inView: (NSView *) controlView 132{ 133 BOOL didStartTracking = [self ppGSPatch_StartTrackingAt: startPoint inView: controlView]; 134 135 if (didStartTracking) 136 { 137 gIsTrackingMouseInSlider = YES; 138 } 139 140 return didStartTracking; 141} 142 143- (void) ppGSPatch_StopTracking: (NSPoint) lastPoint 144 at: (NSPoint) stopPoint 145 inView: (NSView *) controlView 146 mouseIsUp: (BOOL) flag 147{ 148 gIsTrackingMouseInSlider = NO; 149 150 [self ppGSPatch_StopTracking: lastPoint 151 at: stopPoint 152 inView: controlView 153 mouseIsUp: flag]; 154} 155 156@end 157 158@implementation PPApplication (PPGNUstepGlue_SliderDragging) 159 160- (NSEvent *) ppGSPatch_NextEventMatchingMask: (NSUInteger) mask 161 untilDate: (NSDate *) expiration 162 inMode: (NSString *) mode 163 dequeue: (BOOL) flag 164{ 165 static int recursionLevel = 0; 166 NSEvent *event = [self ppGSPatch_NextEventMatchingMask: mask 167 untilDate: expiration 168 inMode: mode 169 dequeue: flag]; 170 171 if (gIsTrackingMouseInSlider 172 && (recursionLevel == 0) 173 && ([event type] == NSLeftMouseDragged)) 174 { 175 // -[NSEvent ppLatestMouseDraggedEventFromEventQueue] calls back to this method, so 176 // keep track of recursion level to prevent recursing more than once 177 178 recursionLevel++; 179 180 event = [event ppLatestMouseDraggedEventFromEventQueue]; 181 182 recursionLevel--; 183 } 184 185 return event; 186} 187 188@end 189 190@implementation NSView (PPGNUstepGlue_SliderDragging) 191 192- (BOOL) ppGSPatch_Mouse: (NSPoint) aPoint inRect: (NSRect) aRect 193{ 194 if (gIsTrackingMouseInSlider) 195 { 196 return YES; 197 } 198 199 return [self ppGSPatch_Mouse: aPoint inRect: aRect]; 200} 201 202@end 203 204#endif // GNUSTEP 205 206