1/* 2 PPLineTool.m 3 4 Copyright 2013-2018,2020 Josh Freeman 5 http://www.twilightedge.com 6 7 This file is part of PikoPixel for Mac OS X and 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#import "PPLineTool.h" 26 27#import "PPDocument.h" 28#import "PPGeometry.h" 29#import "NSCursor_PPUtilities.h" 30#import "NSBezierPath_PPUtilities.h" 31 32 33#define kLineToolAttributesMask (0) 34 35 36@interface PPLineTool (PrivateMethods) 37 38- (void) beginNewSegment; 39- (void) deleteLastSegment; 40 41@end 42 43@implementation PPLineTool 44 45- init 46{ 47 self = [super init]; 48 49 if (!self) 50 goto ERROR; 51 52 _drawPath = [[NSBezierPath bezierPath] retain]; 53 54 if (!_drawPath) 55 goto ERROR; 56 57 return self; 58 59ERROR: 60 [self release]; 61 62 return nil; 63} 64 65- (void) dealloc 66{ 67 [_drawPath release]; 68 69 [super dealloc]; 70} 71 72- (void) mouseDownForDocument: (PPDocument *) ppDocument 73 withCanvasView: (PPCanvasView *) canvasView 74 currentPoint: (NSPoint) currentPoint 75 modifierKeyFlags: (unsigned) modifierKeyFlags 76{ 77 [_drawPath removeAllPoints]; 78 [_drawPath ppAppendSinglePixelLineAtPoint: currentPoint]; 79 80 _segmentStartPoint = _segmentEndPoint = currentPoint; 81 _numSegments = 0; 82 83 [self beginNewSegment]; 84 85 _modifierKeyDown_NewSegment = 86 (modifierKeyFlags & kModifierKeyMask_NewLineSegment) ? YES : NO; 87 88 _modifierKeyDown_DeleteSegment = 89 (modifierKeyFlags & kModifierKeyMask_DeleteLineSegment) ? YES : NO; 90 91#if PP_DEPLOYMENT_TARGET_ALLOWS_SYSTEM_INTERCEPTION_OF_COMMAND_KEY 92 93 _commandKeyIsPressed = (modifierKeyFlags & NSCommandKeyMask) ? YES : NO; 94 _disallowDeleteSegment = _commandKeyIsPressed; 95 96#endif 97 98 _shouldFillDrawPath = NO; 99 100 [ppDocument beginDrawingWithPenMode: kPPPenMode_Fill]; 101 [ppDocument drawPixelAtPoint: currentPoint]; 102} 103 104- (void) mouseDraggedOrModifierKeysChangedForDocument: (PPDocument *) ppDocument 105 withCanvasView: (PPCanvasView *) canvasView 106 currentPoint: (NSPoint) currentPoint 107 lastPoint: (NSPoint) lastPoint 108 mouseDownPoint: (NSPoint) mouseDownPoint 109 modifierKeyFlags: (unsigned) modifierKeyFlags 110{ 111 bool modifierKeyDown_NewSegment, modifierKeyDown_DeleteSegment, allowDeleteSegment, 112 shouldFillDrawPath, shouldRedrawLine = NO, endPointDidMove; 113 114 // new/delete segment modifiers 115 116 modifierKeyDown_NewSegment = 117 (modifierKeyFlags & kModifierKeyMask_NewLineSegment) ? YES : NO; 118 119 modifierKeyDown_DeleteSegment = 120 (modifierKeyFlags & kModifierKeyMask_DeleteLineSegment) ? YES : NO; 121 122 allowDeleteSegment = (!modifierKeyDown_NewSegment) ? YES : NO; 123 124#if PP_DEPLOYMENT_TARGET_ALLOWS_SYSTEM_INTERCEPTION_OF_COMMAND_KEY 125 126 // some Linux/BSD desktop environments intercept the Command/Super key when it's pressed 127 // by itself, so the user has to press Command+Option/Super+Alt in order to enable the 128 // "Fill Drawn Shape" mode, however, this may interfere with the "Delete Segment" mode 129 // (Option/Alt key); in order to avoid conflicts between the two modes, the "Delete Segment" 130 // mode is disallowed as soon as the Command-key modifier flag's raised (the key may have 131 // already been pressed for awhile, but doesn't pass through to PP until another modifier's 132 // pressed), and "Delete Segment" is not allowed again until both the Command & Option keys 133 // are up 134 135 _commandKeyIsPressed = (modifierKeyFlags & NSCommandKeyMask) ? YES : NO; 136 137 if (_commandKeyIsPressed) 138 { 139 _disallowDeleteSegment = YES; 140 } 141 else if (_disallowDeleteSegment && !modifierKeyDown_DeleteSegment) 142 { 143 _disallowDeleteSegment = NO; 144 } 145 146 if (_disallowDeleteSegment) 147 { 148 allowDeleteSegment = NO; 149 } 150 151#endif // PP_DEPLOYMENT_TARGET_ALLOWS_SYSTEM_INTERCEPTION_OF_COMMAND_KEY 152 153 if (_modifierKeyDown_NewSegment != modifierKeyDown_NewSegment) 154 { 155 _modifierKeyDown_NewSegment = modifierKeyDown_NewSegment; 156 157 if (_modifierKeyDown_NewSegment && !modifierKeyDown_DeleteSegment) 158 { 159 [self beginNewSegment]; 160 } 161 } 162 163 if (_modifierKeyDown_DeleteSegment != modifierKeyDown_DeleteSegment) 164 { 165 _modifierKeyDown_DeleteSegment = modifierKeyDown_DeleteSegment; 166 167 if (_modifierKeyDown_DeleteSegment && allowDeleteSegment) 168 { 169 [self deleteLastSegment]; 170 171 shouldRedrawLine = YES; 172 } 173 } 174 175 // fill shape modifier 176 177 if (_numSegments > 1) 178 { 179 shouldFillDrawPath = (modifierKeyFlags & kModifierKeyMask_FillShape) ? YES : NO; 180 } 181 else 182 { 183 shouldFillDrawPath = NO; 184 } 185 186 if (_shouldFillDrawPath != shouldFillDrawPath) 187 { 188 _shouldFillDrawPath = shouldFillDrawPath; 189 190 shouldRedrawLine = YES; 191 } 192 193 // lock aspect ratio modifier 194 195 if (modifierKeyFlags & kModifierKeyMask_LockAspectRatio) 196 { 197 currentPoint = 198 PPGeometry_NearestPointOnOneSixteenthSlope(_segmentStartPoint, currentPoint); 199 } 200 201 // update draw path's end point 202 203 endPointDidMove = (!NSEqualPoints(_segmentEndPoint, currentPoint)) ? YES : NO; 204 205 if (endPointDidMove) 206 { 207 [_drawPath ppSetLastLineEndPointToPixelAtPoint: currentPoint]; 208 209 _segmentEndPoint = currentPoint; 210 211 shouldRedrawLine = YES; 212 } 213 214 // draw 215 216 if (shouldRedrawLine) 217 { 218 [ppDocument undoCurrentDrawingAtNextDraw]; 219 [ppDocument drawBezierPath: _drawPath andFill: _shouldFillDrawPath]; 220 } 221} 222 223- (void) mouseUpForDocument: (PPDocument *) ppDocument 224 withCanvasView: (PPCanvasView *) canvasView 225 currentPoint: (NSPoint) currentPoint 226 mouseDownPoint: (NSPoint) mouseDownPoint 227 modifierKeyFlags: (unsigned) modifierKeyFlags 228{ 229 [ppDocument finishDrawing]; 230 231 [_drawPath removeAllPoints]; 232 _numSegments = 0; 233} 234 235- (NSCursor *) cursor 236{ 237 return [NSCursor ppLineToolCursor]; 238} 239 240- (unsigned) toolAttributeFlags 241{ 242 return kLineToolAttributesMask; 243} 244 245#pragma mark Private methods 246 247- (void) beginNewSegment 248{ 249 [_drawPath ppAppendZeroLengthLineAtLastLineEndPoint]; 250 _segmentStartPoint = _segmentEndPoint; 251 _numSegments++; 252} 253 254- (void) deleteLastSegment 255{ 256 NSPoint previousStartPoint; 257 258 if (_numSegments <= 1) 259 { 260 return; 261 } 262 263 if ([_drawPath ppRemoveLastLineStartPointAndGetPreviousStartPoint: &previousStartPoint]) 264 { 265 _segmentStartPoint = previousStartPoint; 266 _numSegments--; 267 } 268} 269 270@end 271