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