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