1/* 2 PPGNUstepGlue_Miscellaneous.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// Miscellaneous standalone (single-method) GNUstep workarounds & tweaks 26 27#ifdef GNUSTEP 28 29#import <Cocoa/Cocoa.h> 30#import "NSObject_PPUtilities.h" 31#import "PPAppBootUtilities.h" 32#import "PPApplication.h" 33#import "NSFileManager_PPUtilities.h" 34#import "PPPopupPanel.h" 35#import "PPToolsPanelController.h" 36#import "PPDocument.h" 37#import "PPCanvasView.h" 38#import "PPToolButtonMatrix.h" 39#import "NSPasteboard_PPUtilities.h" 40#import "NSDocumentController_PPUtilities.h" 41#import "PPUserFolderPaths.h" 42#import "PPGeometry.h" 43 44 45@implementation NSObject (PPGNUstepGlue_Miscellaneous) 46 47+ (void) ppGSGlue_Miscellaneous_InstallPatches 48{ 49 macroSwizzleInstanceMethod(PPApplication, validateMenuItem:, ppGSPatch_ValidateMenuItem:); 50 51 52 macroSwizzleInstanceMethod(NSFileManager, ppVerifySupportFileDirectory, 53 ppGSPatch_VerifySupportFileDirectory); 54 55 56 macroSwizzleInstanceMethod(NSBrowser, setMaxVisibleColumns:, 57 ppGSPatch_SetMaxVisibleColumns:); 58 59 60 macroSwizzleInstanceMethod(NSImage, copyWithZone:, ppGSPatch_CopyWithZone:); 61 62 63 macroSwizzleInstanceMethod(PPPopupPanel, canBecomeKeyWindow, ppGSPatch_CanBecomeKeyWindow); 64 65 66 macroSwizzleInstanceMethod(PPToolsPanelController, defaultPinnedWindowFrame, 67 ppGSPatch_DefaultPinnedWindowFrame); 68 69 70 macroSwizzleInstanceMethod(PPDocument, setupNewPPDocumentWithCanvasSize:, 71 ppGSPatch_SetupNewPPDocumentWithCanvasSize:); 72 73 74 macroSwizzleInstanceMethod(PPCanvasView, drawRect:, ppGSPatch_DrawRect:); 75 76 77 macroSwizzleClassMethod(PPToolButtonMatrix, cellClass, ppGSPatch_CellClass); 78 79 80 macroSwizzleInstanceMethod(NSColorWell, performClick:, ppGSPatch_PerformClick:); 81} 82 83+ (void) load 84{ 85 macroPerformNSObjectSelectorAfterAppLoads(ppGSGlue_Miscellaneous_InstallPatches); 86} 87 88@end 89 90@implementation PPApplication (PPGNUstepGlue_Miscellaneous) 91 92// PATCH: -[PPApplication validateMenuItem:] 93// GNUstep doesn't support comparing selectors with ==, so patched to use sel_isEqual(). 94 95- (BOOL) ppGSPatch_ValidateMenuItem: (id <NSMenuItem>) menuItem 96{ 97 SEL menuItemAction = [menuItem action]; 98 99 if (sel_isEqual(menuItemAction, @selector(newDocumentFromPasteboard:))) 100 { 101 return [NSPasteboard ppPasteboardHasBitmap] ? YES : NO; 102 } 103 104 // printing is currently disabled, so both printing-related menu items (print & page setup) 105 // use runPageLayout: as their action for convenience when invalidating them 106 107 if (sel_isEqual(menuItemAction, @selector(runPageLayout:))) 108 { 109 return NO; 110 } 111 112 113 if (sel_isEqual(menuItemAction, @selector(activateNextDocumentWindow:)) 114 || sel_isEqual(menuItemAction, @selector(activatePreviousDocumentWindow:))) 115 { 116 return [[NSDocumentController sharedDocumentController] ppHasMultipleDocuments]; 117 } 118 119 return YES; 120} 121 122@end 123 124@implementation NSFileManager (PPGNUstepGlue_Miscellaneous) 125 126// PATCH: -[NSFileManager ppVerifySupportFileDirectory] 127// On GNUstep, the ApplicationSupport directory may not exist yet, and if it's not there, then 128// creating the PikoPixel support folder (in the ApplicationSupport directory) will fail, 129// because ppVerifySupportFileDirectory calls -[NSFileManager createDirectoryAtPath:...] with 130// the createIntermediates parameter set to NO. 131// Patch calls -[NSFileManager createDirectoryAtPath:...] with the createIntermediates 132// parameter set to YES. 133 134- (bool) ppGSPatch_VerifySupportFileDirectory 135{ 136 NSString *supportFolderPath = PPUserFolderPaths_ApplicationSupport(); 137 BOOL isDirectory = NO; 138 139 if (![supportFolderPath length]) 140 { 141 goto ERROR; 142 } 143 144 if ([self fileExistsAtPath: supportFolderPath isDirectory: &isDirectory]) 145 { 146 if (!isDirectory) 147 goto ERROR; 148 149 return YES; 150 } 151 152 return [self createDirectoryAtPath: supportFolderPath 153 withIntermediateDirectories: YES 154 attributes: nil 155 error: NULL]; 156 157ERROR: 158 return NO; 159} 160 161@end 162 163@implementation NSBrowser (PPGNUstepGlue_Miscellaneous) 164 165// PATCH: -[NSBrowser setMaxVisibleColumns:] 166// Patch allows browser columns on NSSavePanels & NSOpenPanels to be wider than the default 167// max-width on GNUstep (140). 168 169#define kNSBrowserMaxColumnWidth 190 170 171- (void) ppGSPatch_SetMaxVisibleColumns: (NSInteger) columnCount 172{ 173 static Class NSSavePanelClass = nil; 174 175 if (!NSSavePanelClass) 176 { 177 NSSavePanelClass = [[NSSavePanel class] retain]; 178 } 179 180 if ([[self window] isKindOfClass: NSSavePanelClass]) 181 { 182 columnCount = [self frame].size.width / kNSBrowserMaxColumnWidth; 183 184 if (columnCount < 1) 185 { 186 columnCount = 1; 187 } 188 } 189 190 [self ppGSPatch_SetMaxVisibleColumns: columnCount]; 191} 192 193@end 194 195@implementation NSImage (PPGNUstepGlue_Miscellaneous) 196 197// PATCH: -[NSImage copyWithZone:] 198// GNUstep's implementation of copyWithZone: only copies image representations that are 199// non-cached, so if the source image only contains NSCachedImageReps, the copy will be empty. 200// Patch checks whether the copy is a valid image; If not - and the source image is valid - 201// a valid representation is created by manually drawing the source image into the copy. 202 203- (id) ppGSPatch_CopyWithZone: (NSZone *) zone 204{ 205 NSImage *copiedImage = [self ppGSPatch_CopyWithZone: zone]; 206 207 if (copiedImage 208 && ![copiedImage isValid] 209 && [self isValid]) 210 { 211 NSRect imageFrame = PPGeometry_OriginRectOfSize([self size]); 212 213 [copiedImage lockFocus]; 214 215 [self drawInRect: imageFrame 216 fromRect: imageFrame 217 operation: NSCompositeCopy 218 fraction: 1.0f]; 219 220 [copiedImage unlockFocus]; 221 } 222 223 return copiedImage; 224} 225 226@end 227 228@implementation PPPopupPanel (PPGNUstepGlue_Miscellaneous) 229 230// PATCH: -[PPPopupPanel canBecomeKeyWindow] 231// GNUstep's implementation of -[NSWindow canBecomeKeyWindow] doesn't automatically return NO 232// when the window has no title bar or resize bar (as on OS X), so need PPPopupPanel override 233// to prevent popup panels from becoming key. 234 235- (BOOL) ppGSPatch_CanBecomeKeyWindow 236{ 237 return NO; 238} 239 240@end 241 242@implementation PPToolsPanelController (PPGNUstepGlue_Miscellaneous) 243 244// PATCH: -[PPToolsPanelController defaultPinnedWindowFrame] 245// Prevents the tools panel's default position from overlapping the main menu. 246 247#define kMinDistanceBetweenToolsPanelAndMainMenu 20 248 249- (NSRect) ppGSPatch_DefaultPinnedWindowFrame 250{ 251 NSRect panelFrame, mainMenuFrame, mainMenuFrameMargin; 252 253 panelFrame = [self ppGSPatch_DefaultPinnedWindowFrame]; 254 mainMenuFrame = [[[NSApp mainMenu] window] frame]; 255 mainMenuFrameMargin = NSInsetRect(mainMenuFrame, -kMinDistanceBetweenToolsPanelAndMainMenu, 256 -kMinDistanceBetweenToolsPanelAndMainMenu); 257 258 if (!NSIsEmptyRect(NSIntersectionRect(panelFrame, mainMenuFrameMargin))) 259 { 260 panelFrame.origin.y = NSMinY(mainMenuFrameMargin) - panelFrame.size.height; 261 } 262 263 return panelFrame; 264} 265 266@end 267 268@implementation PPDocument (PPGNUstepGlue_Miscellaneous) 269 270// PATCH: -[PPDocument setupNewPPDocumentWithCanvasSize:] 271// On GNUstep, closing a new, unmodified PPDocument will display a confirmation dialog, due to 272// unsaved changes (whereas OS X can close an unmodified PPDocument immediately). 273// This is because a document's change count isn't cleared when its undoManager is sent a 274// removeAllActions message (a new PPDocument is changed during setup, so removeAllActions is 275// called at the end of setupNewPPDocumentWithCanvasSize:). 276// The workaround is to manually clear the new document's change count. 277 278- (bool) ppGSPatch_SetupNewPPDocumentWithCanvasSize: (NSSize) canvasSize 279{ 280 bool returnValue = [self ppGSPatch_SetupNewPPDocumentWithCanvasSize: canvasSize]; 281 282 if (returnValue) 283 { 284 [self updateChangeCount: NSChangeCleared]; 285 } 286 287 return returnValue; 288} 289 290@end 291 292@implementation PPCanvasView (PPGNUstepGlue_Miscellaneous) 293 294// PATCH: -[PPCanvasView drawRect:] 295// Fix for PPCanvasView zoomout artifacts that appear when there's only one scrollbar visible 296// (but not both) - workaround is to draw over the artifacts by manually filling the area 297// outside the visible canvas with the enclosing scrollview's background color (normally 298// the canvasview doesn't draw this area and the underlying scrollview's background shows 299// through automatically). 300 301- (void) ppGSPatch_DrawRect: (NSRect) rect 302{ 303 // redraw the background surrounding the visible canvas if: 304 // - drawing is allowed (zoomed-images draw-mode is 0 (normal)) 305 // - and the dirty-rect covers some area outside the visible canvas 306 // - and at least one scrollbar is visible (should only be one at this point - if they were 307 // both visible, the dirty-rect would not be outside the visible canvas) 308 if ((_zoomedImagesDrawMode == 0) 309 && (!NSContainsRect(_offsetZoomedVisibleCanvasBounds, rect)) 310 && ((_offsetZoomedCanvasFrame.origin.x == 0.0) 311 || (_offsetZoomedCanvasFrame.origin.y == 0.0))) 312 { 313 static NSColor *backgroundColor = nil; 314 NSRect rect1 = NSZeroRect, rect2 = NSZeroRect; 315 316 if (!backgroundColor) 317 { 318 backgroundColor = [[[self enclosingScrollView] backgroundColor] retain]; 319 } 320 321 if (_offsetZoomedCanvasFrame.origin.x == 0.0) 322 { 323 // horizontal scrollbar - background-fill areas are above & below visible canvas 324 325 CGFloat canvasMaxY = NSMaxY(_offsetZoomedCanvasFrame); 326 327 rect1 = NSMakeRect(rect.origin.x, 328 canvasMaxY, 329 rect.size.width, 330 NSMaxY(rect) - canvasMaxY); 331 332 rect2 = NSMakeRect(rect.origin.x, 333 rect.origin.y, 334 rect.size.width, 335 _offsetZoomedCanvasFrame.origin.y - rect.origin.y); 336 } 337 else if (_offsetZoomedCanvasFrame.origin.y == 0.0) 338 { 339 // vertical scrollbar - background-fill areas are on left & right of visible canvas 340 341 CGFloat canvasMaxX = NSMaxX(_offsetZoomedCanvasFrame); 342 343 rect1 = NSMakeRect(canvasMaxX, 344 rect.origin.y, 345 NSMaxX(rect) - canvasMaxX, 346 rect.size.height); 347 348 rect2 = NSMakeRect(rect.origin.x, 349 rect.origin.y, 350 _offsetZoomedCanvasFrame.origin.x - rect.origin.x, 351 rect.size.height); 352 } 353 354 [backgroundColor set]; 355 NSRectFill(rect1); 356 NSRectFill(rect2); 357 } 358 359 [self ppGSPatch_DrawRect: rect]; 360} 361 362@end 363 364@implementation PPToolButtonMatrix (PPGNUstepGlue_Miscellaneous) 365 366// PATCH: +[PPToolButtonMatrix cellClass] 367// GNUstep's nib loader has an issue when loading instances of NSMatrix subclasses (but not 368// instances of NSMatrix itself): All of the NSMatrix-subclass' cells become NSActionCells, 369// regardless of what their actual class is in the nib file. This is because the nib loader 370// mistakenly forces the loaded cells (which originally have the correct class) to be 371// reallocated by the class object returned by +cellClass (+[NSMatrix cellClass] returns 372// NSActionCell). 373// The patch returns the correct class for allocating PPToolButtonMatrix's cells: NSButtonCell. 374 375+ (Class) ppGSPatch_CellClass 376{ 377 static Class buttonCellClass = NULL; 378 379 if (!buttonCellClass) 380 { 381 buttonCellClass = [[NSButtonCell class] retain]; 382 } 383 384 return buttonCellClass; 385} 386 387@end 388 389@implementation NSColorWell (PPGNUstepGlue_Miscellaneous) 390 391// PATCH: -[NSColorWell performClick:] 392// On GNUstep, -[NSColorWell performClick:] is unimplemented (calling the method falls through 393// to inherited implementation which doesn't activate/deactivate the well). (This is now fixed 394// in the GNUstep trunk, 2016-11-21). 395// Patch implements correct behavior for color wells: clicking deactivates if it's already 396// active, & activates if it's not active. 397 398- (void) ppGSPatch_PerformClick: (id) sender 399{ 400 if ([self isActive]) 401 { 402 [self deactivate]; 403 } 404 else 405 { 406 [self activate: YES]; 407 } 408} 409 410@end 411 412#endif // GNUSTEP 413 414