1/*
2    PPDocument_SamplerImages.m
3
4    Copyright 2013-2018 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 "PPDocument.h"
26
27#import "PPDocumentSamplerImage.h"
28#import "PPDocument_Notifications.h"
29#import "NSObject_PPUtilities.h"
30
31
32#define kSamplerImageChangeTypeMask_Add         (1 << 0)
33#define kSamplerImageChangeTypeMask_Remove      (1 << 1)
34#define kSamplerImageChangeTypeMask_Move        (1 << 2)
35
36
37@interface PPDocument (SamplerImagesPrivateMethods)
38
39- (void) setSamplerImagesWithArchivedSamplerImagesData: (NSData *) archivedSamplerImagesData;
40
41- (bool) hasSamplerImageAtIndex: (int) index;
42
43- (void) insertSamplerImage: (PPDocumentSamplerImage *) samplerImage
44            atIndex: (int) index;
45- (void) insertArchivedSamplerImage: (NSData *) archivedSamplerImageData
46            atIndex: (int) index;
47- (void) moveSamplerImageAtIndex: (int) oldIndex
48            toIndex: (int) newIndex;
49- (void) removeSamplerImageAtIndex: (int) index;
50- (void) removeAllSamplerImages;
51
52- (void) resetActiveSamplerImageIndexes;
53
54- (void) setActionNameForChangeTypesMask: (unsigned) changeTypesMask
55            numImagesChanged: (unsigned) numImagesChanged;
56
57@end
58
59@implementation PPDocument (SamplerImages)
60
61- (void) setupSamplerImageIndexes
62{
63    _samplerImageMinIndexValues[(int) kPPSamplerImagePanelType_PopupPanel] = -1;
64
65    [self resetActiveSamplerImageIndexes];
66}
67
68- (int) numSamplerImages
69{
70    return _numSamplerImages;
71}
72
73- (NSArray *) samplerImages
74{
75    return _samplerImages;
76}
77
78- (void) setSamplerImages: (NSArray *) newSamplerImages
79{
80    unsigned changeTypesMask = 0, numImagesChanged = 0;
81    NSUInteger numNewSamplerImages, index, oldIndex;
82    PPDocumentSamplerImage *newSamplerImage;
83
84    numNewSamplerImages = [newSamplerImages count];
85
86    if (numNewSamplerImages > 0)
87    {
88        if (_numSamplerImages > 0)
89        {
90            // disallow duplicate sampler images
91            if ([[NSSet setWithArray: newSamplerImages] count] != numNewSamplerImages)
92            {
93                goto ERROR;
94            }
95
96            for (index=0; index<numNewSamplerImages; index++)
97            {
98                newSamplerImage = [newSamplerImages objectAtIndex: index];
99
100                oldIndex = [_samplerImages indexOfObject: newSamplerImage];
101
102                if (oldIndex != NSNotFound)
103                {
104                    if (oldIndex != index)
105                    {
106                        [self moveSamplerImageAtIndex: oldIndex toIndex: index];
107
108                        changeTypesMask |= kSamplerImageChangeTypeMask_Move;
109                        numImagesChanged++;
110                    }
111                }
112                else
113                {
114                    [self insertSamplerImage: newSamplerImage atIndex: index];
115
116                    changeTypesMask |= kSamplerImageChangeTypeMask_Add;
117                    numImagesChanged++;
118                }
119            }
120
121            while (index < _numSamplerImages)
122            {
123                [self removeSamplerImageAtIndex: index];    // decrements _numSamplerImages
124
125                changeTypesMask |= kSamplerImageChangeTypeMask_Remove;
126                numImagesChanged++;
127            }
128        }
129        else    // !(_numSamplerImages > 0)
130        {
131            for (index=0; index<numNewSamplerImages; index++)
132            {
133                newSamplerImage = [newSamplerImages objectAtIndex: index];
134
135                [self insertSamplerImage: newSamplerImage atIndex: index];
136            }
137
138            changeTypesMask |= kSamplerImageChangeTypeMask_Add;
139            numImagesChanged += numNewSamplerImages;
140
141            [self resetActiveSamplerImageIndexes];
142        }
143    }
144    else    // !(numNewSamplerImages > 0)
145    {
146        changeTypesMask |= kSamplerImageChangeTypeMask_Remove;
147        numImagesChanged = _numSamplerImages;
148
149        [self removeAllSamplerImages];
150    }
151
152    [self setActionNameForChangeTypesMask: changeTypesMask numImagesChanged: numImagesChanged];
153
154    return;
155
156ERROR:
157    return;
158}
159
160- (PPDocumentSamplerImage *) activeSamplerImageForPanelType:
161                                                    (PPSamplerImagePanelType) samplerPanelType
162{
163    int samplerImageIndex;
164
165    if (!PPSamplerImagePanelType_IsValid(samplerPanelType))
166    {
167        goto ERROR;
168    }
169
170    samplerImageIndex = _activeSamplerImageIndexes[(int) samplerPanelType];
171
172    if (![self hasSamplerImageAtIndex: samplerImageIndex])
173    {
174        goto ERROR;
175    }
176
177    return [_samplerImages objectAtIndex: samplerImageIndex];
178
179ERROR:
180    return nil;
181}
182
183- (void) activateNextSamplerImageForPanelType: (PPSamplerImagePanelType) samplerPanelType
184{
185    int panelIndex, oldIndexValue;
186
187    if (!PPSamplerImagePanelType_IsValid(samplerPanelType))
188    {
189        goto ERROR;
190    }
191
192    panelIndex = (int) samplerPanelType;
193    oldIndexValue = _activeSamplerImageIndexes[panelIndex]++;
194
195    if (_activeSamplerImageIndexes[panelIndex] >= _numSamplerImages)
196    {
197        _activeSamplerImageIndexes[panelIndex] = _samplerImageMinIndexValues[panelIndex];
198    }
199
200    if (_activeSamplerImageIndexes[panelIndex] != oldIndexValue)
201    {
202        [self postNotification_SwitchedActiveSamplerImageForPanelType: samplerPanelType];
203    }
204
205    return;
206
207ERROR:
208    return;
209}
210
211- (void) activatePreviousSamplerImageForPanelType: (PPSamplerImagePanelType) samplerPanelType
212{
213    int panelIndex, oldIndexValue;
214
215    if (!PPSamplerImagePanelType_IsValid(samplerPanelType))
216    {
217        goto ERROR;
218    }
219
220    panelIndex = (int) samplerPanelType;
221    oldIndexValue = _activeSamplerImageIndexes[samplerPanelType]--;
222
223    if (_activeSamplerImageIndexes[panelIndex] < _samplerImageMinIndexValues[panelIndex])
224    {
225        _activeSamplerImageIndexes[panelIndex] = _numSamplerImages - 1;
226    }
227
228    if (_activeSamplerImageIndexes[panelIndex] != oldIndexValue)
229    {
230        [self postNotification_SwitchedActiveSamplerImageForPanelType: samplerPanelType];
231    }
232
233    return;
234
235ERROR:
236    return;
237}
238
239- (bool) hasActiveSamplerImageForPanelType: (PPSamplerImagePanelType) samplerPanelType
240{
241    if (!PPSamplerImagePanelType_IsValid(samplerPanelType))
242    {
243        goto ERROR;
244    }
245
246    return [self hasSamplerImageAtIndex: _activeSamplerImageIndexes[(int) samplerPanelType]];
247
248ERROR:
249    return NO;
250}
251
252- (bool) shouldEnableSamplerImagePanel
253{
254    return _shouldEnableSamplerImagePanel;
255}
256
257- (void) setShouldEnableSamplerImagePanel: (bool) shouldEnableSamplerImagePanel
258{
259    _shouldEnableSamplerImagePanel = (shouldEnableSamplerImagePanel) ? YES : NO;
260}
261
262#pragma mark Private methods
263
264- (void) setSamplerImagesWithArchivedSamplerImagesData: (NSData *) archivedSamplerImagesData
265{
266    NSArray *samplerImages = nil;
267
268    if (archivedSamplerImagesData)
269    {
270        samplerImages = [NSKeyedUnarchiver unarchiveObjectWithData: archivedSamplerImagesData];
271    }
272
273    [self setSamplerImages: samplerImages];
274}
275
276- (bool) hasSamplerImageAtIndex: (int) index
277{
278    return ((index >= 0) && (index < _numSamplerImages)) ? YES : NO;
279}
280
281- (void) insertSamplerImage: (PPDocumentSamplerImage *) samplerImage
282            atIndex: (int) index;
283{
284    int panelType;
285    bool didSwitchActiveImageForPanel = NO;
286
287    if (!samplerImage
288        || ((index != _numSamplerImages) && ![self hasSamplerImageAtIndex: index])
289        || ([_samplerImages indexOfObject: samplerImage] != NSNotFound))
290    {
291        goto ERROR;
292    }
293
294    if (_numSamplerImages)
295    {
296        for (panelType=0; panelType<kNumPPSamplerImagePanelTypes; panelType++)
297        {
298            if ((_activeSamplerImageIndexes[panelType] >= index)
299                && (index < _numSamplerImages))
300            {
301                _activeSamplerImageIndexes[panelType]++;
302            }
303        }
304    }
305    else    // !(_numSamplerImages)
306    {
307        didSwitchActiveImageForPanel = YES;
308    }
309
310    [_samplerImages insertObject: samplerImage atIndex: index];
311    _numSamplerImages = [_samplerImages count];
312
313    [[[self undoManager] prepareWithInvocationTarget: self] removeSamplerImageAtIndex: index];
314
315    if (didSwitchActiveImageForPanel)
316    {
317        [self postNotification_SwitchedActiveSamplerImageForPanelType:
318                                                                kPPSamplerImagePanelType_Panel];
319    }
320
321    [self ppPerformSelectorAtomicallyFromNewStackFrame:
322                                            @selector(postNotification_UpdatedSamplerImages)];
323
324    return;
325
326ERROR:
327    return;
328}
329
330- (void) insertArchivedSamplerImage: (NSData *) archivedSamplerImageData
331            atIndex: (int) index
332{
333    PPDocumentSamplerImage *samplerImage;
334
335    if (!archivedSamplerImageData)
336        goto ERROR;
337
338    samplerImage = [NSKeyedUnarchiver unarchiveObjectWithData: archivedSamplerImageData];
339
340    if (![samplerImage isKindOfClass: [PPDocumentSamplerImage class]])
341    {
342        goto ERROR;
343    }
344
345    [self insertSamplerImage: samplerImage atIndex: index];
346
347    return;
348
349ERROR:
350    return;
351}
352
353- (void) moveSamplerImageAtIndex: (int) oldIndex
354            toIndex: (int) newIndex
355{
356    PPDocumentSamplerImage *samplerImage;
357    int minIndex, maxIndex, indexOffset, panelType;
358
359    if (![self hasSamplerImageAtIndex: oldIndex]
360        || ![self hasSamplerImageAtIndex: newIndex]
361        || (oldIndex == newIndex))
362    {
363        goto ERROR;
364    }
365
366    samplerImage = [[[_samplerImages objectAtIndex: oldIndex] retain] autorelease];
367
368    [_samplerImages removeObjectAtIndex: oldIndex];
369    [_samplerImages insertObject: samplerImage atIndex: newIndex];
370
371    if (oldIndex < newIndex)
372    {
373        minIndex = oldIndex;
374        maxIndex = newIndex;
375        indexOffset = -1;
376    }
377    else
378    {
379        minIndex = newIndex;
380        maxIndex = oldIndex;
381        indexOffset = 1;
382    }
383
384    for (panelType=0; panelType<kNumPPSamplerImagePanelTypes; panelType++)
385    {
386        if (_activeSamplerImageIndexes[panelType] == oldIndex)
387        {
388            _activeSamplerImageIndexes[panelType] = newIndex;
389        }
390        else if ((_activeSamplerImageIndexes[panelType] >= minIndex)
391                    && (_activeSamplerImageIndexes[panelType] <= maxIndex))
392        {
393            _activeSamplerImageIndexes[panelType] += indexOffset;
394        }
395    }
396
397    [[[self undoManager] prepareWithInvocationTarget: self] moveSamplerImageAtIndex: newIndex
398                                                            toIndex: oldIndex];
399
400    [self ppPerformSelectorAtomicallyFromNewStackFrame:
401                                            @selector(postNotification_UpdatedSamplerImages)];
402
403    return;
404
405ERROR:
406    return;
407}
408
409- (void) removeSamplerImageAtIndex: (int) index
410{
411    NSData *oldSamplerImageData;
412    int panelType;
413    bool didSwitchActiveSamplerImageForPanelType[kNumPPSamplerImagePanelTypes];
414
415    if (![self hasSamplerImageAtIndex: index])
416    {
417        goto ERROR;
418    }
419
420    oldSamplerImageData =
421        [NSKeyedArchiver archivedDataWithRootObject: [_samplerImages objectAtIndex: index]];
422
423    [_samplerImages removeObjectAtIndex: index];
424    _numSamplerImages = [_samplerImages count];
425
426    for (panelType=0; panelType<kNumPPSamplerImagePanelTypes; panelType++)
427    {
428        didSwitchActiveSamplerImageForPanelType[panelType] = NO;
429
430        if (_activeSamplerImageIndexes[panelType] > index)
431        {
432            _activeSamplerImageIndexes[panelType]--;
433        }
434        else if (_activeSamplerImageIndexes[panelType] == index)
435        {
436            if (index >= _numSamplerImages)
437            {
438                _activeSamplerImageIndexes[panelType] = _samplerImageMinIndexValues[panelType];
439            }
440
441            didSwitchActiveSamplerImageForPanelType[panelType] = YES;
442        }
443    }
444
445    [[[self undoManager] prepareWithInvocationTarget: self]
446                                            insertArchivedSamplerImage: oldSamplerImageData
447                                            atIndex: index];
448
449    for (panelType=0; panelType<kNumPPSamplerImagePanelTypes; panelType++)
450    {
451        if (didSwitchActiveSamplerImageForPanelType[panelType])
452        {
453            [self postNotification_SwitchedActiveSamplerImageForPanelType: panelType];
454        }
455    }
456
457    [self ppPerformSelectorAtomicallyFromNewStackFrame:
458                                            @selector(postNotification_UpdatedSamplerImages)];
459
460    return;
461
462ERROR:
463    return;
464}
465
466- (void) removeAllSamplerImages
467{
468    NSData *oldSamplerImagesData;
469    int panelType;
470    bool hadActiveSamplerImageForPanelType[kNumPPSamplerImagePanelTypes];
471
472    if (!_numSamplerImages)
473        return;
474
475    oldSamplerImagesData = [NSKeyedArchiver archivedDataWithRootObject: _samplerImages];
476
477    for (panelType=0; panelType<kNumPPSamplerImagePanelTypes; panelType++)
478    {
479        hadActiveSamplerImageForPanelType[panelType] =
480                        [self hasSamplerImageAtIndex: _activeSamplerImageIndexes[panelType]];
481    }
482
483    [_samplerImages removeAllObjects];
484    _numSamplerImages = [_samplerImages count];
485
486    [self resetActiveSamplerImageIndexes];
487
488    [[[self undoManager]
489                    prepareWithInvocationTarget: self]
490                        setSamplerImagesWithArchivedSamplerImagesData: oldSamplerImagesData];
491
492    for (panelType=0; panelType<kNumPPSamplerImagePanelTypes; panelType++)
493    {
494        if (hadActiveSamplerImageForPanelType[panelType])
495        {
496            [self postNotification_SwitchedActiveSamplerImageForPanelType: panelType];
497        }
498    }
499
500    [self ppPerformSelectorAtomicallyFromNewStackFrame:
501                                            @selector(postNotification_UpdatedSamplerImages)];
502}
503
504- (void) resetActiveSamplerImageIndexes
505{
506    memcpy(_activeSamplerImageIndexes, _samplerImageMinIndexValues,
507            sizeof(_activeSamplerImageIndexes));
508}
509
510- (void) setActionNameForChangeTypesMask: (unsigned) changeTypesMask
511            numImagesChanged: (unsigned) numImagesChanged
512{
513    NSString *changeDescription, *actionName;
514    bool didChangeMultipleImages;
515
516    if (!numImagesChanged)
517        return;
518
519    didChangeMultipleImages = (numImagesChanged > 1) ? YES : NO;
520
521    switch (changeTypesMask)
522    {
523        case kSamplerImageChangeTypeMask_Add:
524        {
525            changeDescription = @"Add";
526        }
527        break;
528
529        case kSamplerImageChangeTypeMask_Remove:
530        {
531            changeDescription = @"Remove";
532        }
533        break;
534
535        case kSamplerImageChangeTypeMask_Move:
536        {
537            changeDescription = @"Reorder";
538            didChangeMultipleImages = YES;
539        }
540        break;
541
542        default:
543        {
544            changeDescription = @"Edit";
545        }
546        break;
547    }
548
549    actionName = [NSString stringWithFormat: @"%@ Sampler %@", changeDescription,
550                                            (didChangeMultipleImages) ? @"Images" : @"Image"];
551
552    [[self undoManager] setActionName: actionName];
553}
554
555@end
556