1/* FSNIconsView.m 2 * 3 * Copyright (C) 2004-2016 Free Software Foundation, Inc. 4 * 5 * Author: Enrico Sersale <enrico@imago.ro> 6 * Riccardo Mottola <rm@gnu.org> 7 * Date: March 2004 8 * 9 * This file is part of the GNUstep FSNode framework 10 * 11 * This program is free software; you can redistribute it and/or modify 12 * it under the terms of the GNU General Public License as published by 13 * the Free Software Foundation; either version 2 of the License, or 14 * (at your option) any later version. 15 * 16 * This program is distributed in the hope that it will be useful, 17 * but WITHOUT ANY WARRANTY; without even the implied warranty of 18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 19 * GNU General Public License for more details. 20 * 21 * You should have received a copy of the GNU General Public License 22 * along with this program; if not, write to the Free Software 23 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111 USA. 24 */ 25 26#include <math.h> 27#include <unistd.h> 28#include <sys/types.h> 29 30#import <Foundation/Foundation.h> 31#import <AppKit/AppKit.h> 32#import <GNUstepGUI/GSVersion.h> 33 34#import "FSNIconsView.h" 35#import "FSNIcon.h" 36#import "FSNFunctions.h" 37 38#define DEF_ICN_SIZE 48 39#define DEF_TEXT_SIZE 12 40#define DEF_ICN_POS NSImageAbove 41 42#define X_MARGIN (10) 43#define Y_MARGIN (12) 44 45#define EDIT_MARGIN (4) 46 47#ifndef max 48 #define max(a,b) ((a) >= (b) ? (a):(b)) 49#endif 50 51#ifndef min 52 #define min(a,b) ((a) <= (b) ? (a):(b)) 53#endif 54 55#define CHECK_SIZE(s) \ 56if (s.width < 1) s.width = 1; \ 57if (s.height < 1) s.height = 1; \ 58if (s.width > maxr.size.width) s.width = maxr.size.width; \ 59if (s.height > maxr.size.height) s.height = maxr.size.height 60 61#define SETRECT(o, x, y, w, h) { \ 62NSRect rct = NSMakeRect(x, y, w, h); \ 63if (rct.size.width < 0) rct.size.width = 0; \ 64if (rct.size.height < 0) rct.size.height = 0; \ 65[o setFrame: NSIntegralRect(rct)]; \ 66} 67 68/* we redefine the dockstyle to read the preferences without including Dock.h" */ 69typedef enum DockStyle 70{ 71 DockStyleClassic = 0, 72 DockStyleModern = 1 73} DockStyle; 74 75#define SUPPORTS_XOR ((GNUSTEP_GUI_MAJOR_VERSION > 0) \ 76 || (GNUSTEP_GUI_MAJOR_VERSION == 0 \ 77 && GNUSTEP_GUI_MINOR_VERSION > 22) \ 78 || (GNUSTEP_GUI_MAJOR_VERSION == 0 \ 79 && GNUSTEP_GUI_MINOR_VERSION == 22 \ 80 && GNUSTEP_GUI_SUBMINOR_VERSION > 0)) 81 82static void GWHighlightFrameRect(NSRect aRect) 83{ 84#if SUPPORTS_XOR 85 NSFrameRectWithWidthUsingOperation(aRect, 1.0, GSCompositeHighlight); 86#endif 87} 88 89 90@implementation FSNIconsView 91 92- (void)dealloc 93{ 94 RELEASE (node); 95 RELEASE (extInfoType); 96 RELEASE (icons); 97 RELEASE (labelFont); 98 RELEASE (nameEditor); 99 RELEASE (horizontalImage); 100 RELEASE (verticalImage); 101 RELEASE (lastSelection); 102 RELEASE (charBuffer); 103 RELEASE (backColor); 104 RELEASE (textColor); 105 RELEASE (disabledTextColor); 106 107 [super dealloc]; 108} 109 110- (id)init 111{ 112 self = [super init]; 113 114 if (self) { 115 NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; 116 NSString *appName = [defaults stringForKey: @"DesktopApplicationName"]; 117 NSString *selName = [defaults stringForKey: @"DesktopApplicationSelName"]; 118 id defentry; 119 120 fsnodeRep = [FSNodeRep sharedInstance]; 121 122 if (appName && selName) { 123 Class desktopAppClass = [[NSBundle mainBundle] classNamed: appName]; 124 SEL sel = NSSelectorFromString(selName); 125 desktopApp = [desktopAppClass performSelector: sel]; 126 } 127 128 /* we tie the transparent selection to the modern dock style */ 129 transparentSelection = NO; 130 defentry = [defaults objectForKey: @"dockstyle"]; 131 if ([defentry intValue] == DockStyleModern) 132 transparentSelection = YES; 133 134 ASSIGN (backColor, [NSColor windowBackgroundColor]); 135 ASSIGN (textColor, [NSColor controlTextColor]); 136 ASSIGN (disabledTextColor, [NSColor disabledControlTextColor]); 137 138 defentry = [defaults objectForKey: @"iconsize"]; 139 iconSize = defentry ? [defentry intValue] : DEF_ICN_SIZE; 140 141 defentry = [defaults objectForKey: @"labeltxtsize"]; 142 labelTextSize = defentry ? [defentry intValue] : DEF_TEXT_SIZE; 143 ASSIGN (labelFont, [NSFont systemFontOfSize: labelTextSize]); 144 145 defentry = [defaults objectForKey: @"iconposition"]; 146 iconPosition = defentry ? [defentry intValue] : DEF_ICN_POS; 147 148 defentry = [defaults objectForKey: @"fsn_info_type"]; 149 infoType = defentry ? [defentry intValue] : FSNInfoNameType; 150 extInfoType = nil; 151 152 if (infoType == FSNInfoExtendedType) { 153 defentry = [defaults objectForKey: @"extended_info_type"]; 154 155 if (defentry) { 156 NSArray *availableTypes = [fsnodeRep availableExtendedInfoNames]; 157 158 if ([availableTypes containsObject: defentry]) { 159 ASSIGN (extInfoType, defentry); 160 } 161 } 162 163 if (extInfoType == nil) { 164 infoType = FSNInfoNameType; 165 } 166 } 167 168 icons = [NSMutableArray new]; 169 170 nameEditor = [FSNIconNameEditor new]; 171 [nameEditor setDelegate: self]; 172 [nameEditor setFont: labelFont]; 173 [nameEditor setBezeled: NO]; 174 [nameEditor setAlignment: NSCenterTextAlignment]; 175 [nameEditor setBackgroundColor: backColor]; 176 [nameEditor setTextColor: textColor]; 177 [nameEditor setEditable: NO]; 178 [nameEditor setSelectable: NO]; 179 editIcon = nil; 180 181 isDragTarget = NO; 182 lastKeyPressed = 0.; 183 charBuffer = nil; 184 selectionMask = NSSingleSelectionMask; 185 186 [self calculateGridSize]; 187 188 [self registerForDraggedTypes: [NSArray arrayWithObjects: 189 NSFilenamesPboardType, 190 @"GWLSFolderPboardType", 191 @"GWRemoteFilenamesPboardType", 192 nil]]; 193 } 194 195 return self; 196} 197 198- (void)sortIcons 199{ 200 if (infoType == FSNInfoExtendedType) { 201 [icons sortUsingFunction: (int (*)(id, id, void*))compareWithExtType 202 context: (void *)NULL]; 203 } else { 204 [icons sortUsingSelector: [fsnodeRep compareSelectorForDirectory: [node path]]]; 205 } 206} 207 208- (void)calculateGridSize 209{ 210 NSSize highlightSize = NSZeroSize; 211 NSSize labelSize = NSZeroSize; 212 int lblmargin = [fsnodeRep labelMargin]; 213 214 highlightSize.width = ceil(iconSize / 3 * 4); 215 highlightSize.height = ceil(highlightSize.width * [fsnodeRep highlightHeightFactor]); 216 if ((highlightSize.height - iconSize) < 4) { 217 highlightSize.height = iconSize + 4; 218 } 219 220 labelSize.height = floor([fsnodeRep heightOfFont: labelFont]); 221 labelSize.width = [fsnodeRep labelWFactor] * labelTextSize; 222 223 gridSize.height = highlightSize.height; 224 225 if (infoType != FSNInfoNameType) { 226 float lbsh = (labelSize.height * 2) - 2; 227 228 if (iconPosition == NSImageAbove) { 229 gridSize.height += lbsh; 230 gridSize.width = labelSize.width; 231 } else { 232 if (lbsh > gridSize.height) { 233 gridSize.height = lbsh; 234 } 235 gridSize.width = highlightSize.width + labelSize.width + lblmargin; 236 } 237 } else { 238 if (iconPosition == NSImageAbove) { 239 gridSize.height += labelSize.height; 240 gridSize.width = labelSize.width; 241 } else { 242 gridSize.width = highlightSize.width + labelSize.width + lblmargin; 243 } 244 } 245} 246 247- (void)tile 248{ 249 CREATE_AUTORELEASE_POOL (pool); 250 NSRect svr = [[self superview] frame]; 251 NSRect r = [self frame]; 252 NSRect maxr = [[NSScreen mainScreen] frame]; 253 float px = 0 - gridSize.width; 254 float py = gridSize.height + Y_MARGIN; 255 NSSize sz; 256 NSUInteger poscount = 0; 257 NSUInteger count = [icons count]; 258 NSRect *irects = NSZoneMalloc (NSDefaultMallocZone(), sizeof(NSRect) * count); 259 NSCachedImageRep *rep = nil; 260 NSArray *selection; 261 NSUInteger i; 262 263 colcount = 0; 264 265 for (i = 0; i < count; i++) 266 { 267 px += (gridSize.width + X_MARGIN); 268 269 if (px >= (svr.size.width - gridSize.width)) { 270 px = X_MARGIN; 271 py += (gridSize.height + Y_MARGIN); 272 273 if (colcount < poscount) { 274 colcount = poscount; 275 } 276 poscount = 0; 277 } 278 279 poscount++; 280 281 irects[i] = NSMakeRect(px, py, gridSize.width, gridSize.height); 282 } 283 284 py += Y_MARGIN; 285 py = (py < svr.size.height) ? svr.size.height : py; 286 287 SETRECT (self, r.origin.x, r.origin.y, svr.size.width, py); 288 289 for (i = 0; i < count; i++) { 290 FSNIcon *icon = [icons objectAtIndex: i]; 291 292 irects[i].origin.y = py - irects[i].origin.y; 293 irects[i] = NSIntegralRect(irects[i]); 294 295 if (NSEqualRects(irects[i], [icon frame]) == NO) { 296 [icon setFrame: irects[i]]; 297 } 298 299 [icon setGridIndex: i]; 300 } 301 302 DESTROY (horizontalImage); 303 sz = NSMakeSize(svr.size.width, 2); 304 CHECK_SIZE (sz); 305 horizontalImage = [[NSImage allocWithZone: (NSZone *)[(NSObject *)self zone]] 306 initWithSize: sz]; 307 308 rep = [[NSCachedImageRep allocWithZone: (NSZone *)[(NSObject *)self zone]] 309 initWithSize: sz 310 depth: [NSWindow defaultDepthLimit] 311 separate: YES 312 alpha: YES]; 313 314 [horizontalImage addRepresentation: rep]; 315 RELEASE (rep); 316 317 DESTROY (verticalImage); 318 sz = NSMakeSize(2, py); 319 CHECK_SIZE (sz); 320 verticalImage = [[NSImage allocWithZone: (NSZone *)[(NSObject *)self zone]] 321 initWithSize: sz]; 322 323 rep = [[NSCachedImageRep allocWithZone: (NSZone *)[(NSObject *)self zone]] 324 initWithSize: sz 325 depth: [NSWindow defaultDepthLimit] 326 separate: YES 327 alpha: YES]; 328 329 [verticalImage addRepresentation: rep]; 330 RELEASE (rep); 331 332 NSZoneFree (NSDefaultMallocZone(), irects); 333 334 RELEASE (pool); 335 336 selection = [self selectedReps]; 337 if ([selection count]) { 338 [self scrollIconToVisible: [selection objectAtIndex: 0]]; 339 } 340 341 if ([[self subviews] containsObject: nameEditor]) { 342 [self updateNameEditor]; 343 } 344} 345 346- (void)scrollIconToVisible:(FSNIcon *)icon 347{ 348 NSRect irect = [icon frame]; 349 float border = floor(irect.size.height * 0.2); 350 351 irect.origin.y -= border; 352 irect.size.height += border * 2; 353 [self scrollRectToVisible: irect]; 354} 355 356- (NSString *)selectIconWithPrefix:(NSString *)prefix 357{ 358 NSUInteger i; 359 360 for (i = 0; i < [icons count]; i++) 361 { 362 FSNIcon *icon = [icons objectAtIndex: i]; 363 NSString *name = [icon shownInfo]; 364 365 if ([name hasPrefix: prefix]) 366 { 367 [icon select]; 368 [self scrollIconToVisible: icon]; 369 370 return name; 371 } 372 } 373 374 return nil; 375} 376 377- (void)selectIconInPrevLine 378{ 379 FSNIcon *icon; 380 NSUInteger i; 381 NSInteger pos = -1; 382 383 for (i = 0; i < [icons count]; i++) 384 { 385 icon = [icons objectAtIndex: i]; 386 387 if ([icon isSelected]) 388 { 389 pos = i - colcount; 390 break; 391 } 392 } 393 394 if (pos >= 0) 395 { 396 icon = [icons objectAtIndex: pos]; 397 [icon select]; 398 [self scrollIconToVisible: icon]; 399 } 400} 401 402- (void)selectIconInNextLine 403{ 404 FSNIcon *icon; 405 NSUInteger i; 406 NSUInteger pos = [icons count]; 407 408 for (i = 0; i < [icons count]; i++) 409 { 410 icon = [icons objectAtIndex: i]; 411 412 if ([icon isSelected]) 413 { 414 pos = i + colcount; 415 break; 416 } 417 } 418 419 if (pos <= ([icons count] -1)) 420 { 421 icon = [icons objectAtIndex: pos]; 422 [icon select]; 423 [self scrollIconToVisible: icon]; 424 } 425} 426 427- (void)selectPrevIcon 428{ 429 NSUInteger i; 430 431 for (i = 0; i < [icons count]; i++) 432 { 433 FSNIcon *icon = [icons objectAtIndex: i]; 434 435 if ([icon isSelected]) 436 { 437 if (i > 0) 438 { 439 icon = [icons objectAtIndex: i - 1]; 440 [icon select]; 441 [self scrollIconToVisible: icon]; 442 } 443 break; 444 } 445 } 446} 447 448- (void)selectNextIcon 449{ 450 NSUInteger count = [icons count]; 451 NSUInteger i; 452 453 for (i = 0; i < count; i++) 454 { 455 FSNIcon *icon = [icons objectAtIndex: i]; 456 457 if ([icon isSelected]) 458 { 459 if (i < (count - 1)) 460 { 461 icon = [icons objectAtIndex: i + 1]; 462 [icon select]; 463 [self scrollIconToVisible: icon]; 464 } 465 break; 466 } 467 } 468} 469 470- (void)mouseUp:(NSEvent *)theEvent 471{ 472 [self setSelectionMask: NSSingleSelectionMask]; 473} 474 475- (void)mouseDown:(NSEvent *)theEvent 476{ 477 if ([theEvent modifierFlags] != NSShiftKeyMask) { 478 selectionMask = NSSingleSelectionMask; 479 selectionMask |= FSNCreatingSelectionMask; 480 [self unselectOtherReps: nil]; 481 selectionMask = NSSingleSelectionMask; 482 483 DESTROY (lastSelection); 484 [self selectionDidChange]; 485 [self stopRepNameEditing]; 486 } 487} 488 489- (void)mouseDragged:(NSEvent *)theEvent 490{ 491 unsigned int eventMask = NSLeftMouseUpMask | NSLeftMouseDraggedMask | NSPeriodicMask; 492 NSDate *future = [NSDate distantFuture]; 493 NSPoint sp; 494 NSPoint p, pp; 495 NSRect visibleRect; 496 NSRect oldRect; 497 NSRect r; 498 NSRect selrect; 499 float x, y, w, h; 500 NSUInteger i; 501 502 pp = NSMakePoint(0,0); 503 504#define scrollPointToVisible(p) \ 505{ \ 506NSRect sr; \ 507sr.origin = p; \ 508sr.size.width = sr.size.height = 1.0; \ 509[self scrollRectToVisible: sr]; \ 510} 511 512#define CONVERT_CHECK \ 513{ \ 514NSRect br = [self bounds]; \ 515pp = [self convertPoint: p fromView: nil]; \ 516if (pp.x < 1) \ 517pp.x = 1; \ 518if (pp.x >= NSMaxX(br)) \ 519pp.x = NSMaxX(br) - 1; \ 520if (pp.y < 0) \ 521pp.y = -1; \ 522if (pp.y > NSMaxY(br)) \ 523pp.y = NSMaxY(br) + 1; \ 524} 525 526 p = [theEvent locationInWindow]; 527 sp = [self convertPoint: p fromView: nil]; 528 529 oldRect = NSZeroRect; 530 531 [[self window] disableFlushWindow]; 532 533 [NSEvent startPeriodicEventsAfterDelay: 0.02 withPeriod: 0.05]; 534 535 while ([theEvent type] != NSLeftMouseUp) { 536 BOOL scrolled = NO; 537 538 CREATE_AUTORELEASE_POOL (arp); 539 540 theEvent = [NSApp nextEventMatchingMask: eventMask 541 untilDate: future 542 inMode: NSEventTrackingRunLoopMode 543 dequeue: YES]; 544 545 if ([theEvent type] != NSPeriodic) { 546 p = [theEvent locationInWindow]; 547 } 548 549 CONVERT_CHECK; 550 551 visibleRect = [self visibleRect]; 552 553 if ([self mouse: pp inRect: visibleRect] == NO) 554 { 555 scrollPointToVisible(pp); 556 CONVERT_CHECK; 557 558 scrolled = YES; 559 } 560 561 x = min(sp.x, pp.x); 562 y = min(sp.y, pp.y); 563 w = max(1, max(pp.x, sp.x) - min(pp.x, sp.x)); 564 h = max(1, max(pp.y, sp.y) - min(pp.y, sp.y)); 565 566 r = NSMakeRect(x, y, w, h); 567 568 // Erase the previous rect 569 if (transparentSelection 570 || !SUPPORTS_XOR 571 || (!transparentSelection && scrolled)) 572 { 573 [self setNeedsDisplayInRect: oldRect]; 574 [[self window] displayIfNeeded]; 575 } 576 577 // Draw the new rect 578 579 [self lockFocus]; 580 581 if (transparentSelection || !SUPPORTS_XOR) 582 { 583 [[NSColor darkGrayColor] set]; 584 NSFrameRect(r); 585 if (transparentSelection) 586 { 587 [[[NSColor darkGrayColor] colorWithAlphaComponent: 0.33] set]; 588 NSRectFillUsingOperation(r, NSCompositeSourceOver); 589 } 590 } 591 else 592 { 593 if (!NSEqualRects(oldRect, r) && !scrolled) 594 { 595 GWHighlightFrameRect(oldRect); 596 GWHighlightFrameRect(r); 597 } 598 else if (scrolled) 599 { 600 GWHighlightFrameRect(r); 601 } 602 } 603 604 [self unlockFocus]; 605 606 oldRect = r; 607 608 [[self window] enableFlushWindow]; 609 [[self window] flushWindow]; 610 [[self window] disableFlushWindow]; 611 612 DESTROY (arp); 613 } 614 615 [NSEvent stopPeriodicEvents]; 616 [[self window] postEvent: theEvent atStart: NO]; 617 618 // Erase the previous rect 619 620 [self setNeedsDisplayInRect: oldRect]; 621 [[self window] displayIfNeeded]; 622 623 [[self window] enableFlushWindow]; 624 [[self window] flushWindow]; 625 626 selectionMask = FSNMultipleSelectionMask; 627 selectionMask |= FSNCreatingSelectionMask; 628 629 x = min(sp.x, pp.x); 630 y = min(sp.y, pp.y); 631 w = max(1, max(pp.x, sp.x) - min(pp.x, sp.x)); 632 h = max(1, max(pp.y, sp.y) - min(pp.y, sp.y)); 633 634 selrect = NSMakeRect(x, y, w, h); 635 636 for (i = 0; i < [icons count]; i++) { 637 FSNIcon *icon = [icons objectAtIndex: i]; 638 NSRect iconBounds = [self convertRect: [icon iconBounds] fromView: icon]; 639 640 if (NSIntersectsRect(selrect, iconBounds)) { 641 [icon select]; 642 } 643 } 644 645 selectionMask = NSSingleSelectionMask; 646 647 [self selectionDidChange]; 648} 649 650- (void)keyDown:(NSEvent *)theEvent 651{ 652 NSString *characters; 653 unichar character; 654 NSRect vRect, hiddRect; 655 NSPoint p; 656 float x, y, w, h; 657 658 characters = [theEvent characters]; 659 character = 0; 660 661 if ([characters length] > 0) 662 character = [characters characterAtIndex: 0]; 663 664 665 switch (character) { 666 case NSPageUpFunctionKey: 667 vRect = [self visibleRect]; 668 p = vRect.origin; 669 x = p.x; 670 y = p.y + vRect.size.height; 671 w = vRect.size.width; 672 h = vRect.size.height; 673 hiddRect = NSMakeRect(x, y, w, h); 674 [self scrollRectToVisible: hiddRect]; 675 return; 676 677 case NSPageDownFunctionKey: 678 vRect = [self visibleRect]; 679 p = vRect.origin; 680 x = p.x; 681 y = p.y - vRect.size.height; 682 w = vRect.size.width; 683 h = vRect.size.height; 684 hiddRect = NSMakeRect(x, y, w, h); 685 [self scrollRectToVisible: hiddRect]; 686 return; 687 688 case NSUpArrowFunctionKey: 689 [self selectIconInPrevLine]; 690 return; 691 692 case NSDownArrowFunctionKey: 693 [self selectIconInNextLine]; 694 return; 695 696 case NSLeftArrowFunctionKey: 697 { 698 if ([theEvent modifierFlags] & NSControlKeyMask) { 699 [super keyDown: theEvent]; 700 } else { 701 [self selectPrevIcon]; 702 } 703 } 704 return; 705 706 case NSRightArrowFunctionKey: 707 { 708 if ([theEvent modifierFlags] & NSControlKeyMask) { 709 [super keyDown: theEvent]; 710 } else { 711 [self selectNextIcon]; 712 } 713 } 714 return; 715 716 case NSCarriageReturnCharacter: 717 { 718 unsigned flags = [theEvent modifierFlags]; 719 BOOL closesndr = ((flags == NSAlternateKeyMask) 720 || (flags == NSControlKeyMask)); 721 [self openSelectionInNewViewer: closesndr]; 722 return; 723 } 724 725 default: 726 break; 727 } 728 729 if (([characters length] > 0) && (character < 0xF700)) { 730 SEL icnwpSel = @selector(selectIconWithPrefix:); 731 IMP icnwp = [self methodForSelector: icnwpSel]; 732 733 if (charBuffer == nil) { 734 charBuffer = [characters substringToIndex: 1]; 735 RETAIN (charBuffer); 736 lastKeyPressed = 0.0; 737 } else { 738 if ([theEvent timestamp] - lastKeyPressed < 500.0) { 739 ASSIGN (charBuffer, ([charBuffer stringByAppendingString: 740 [characters substringToIndex: 1]])); 741 } else { 742 ASSIGN (charBuffer, ([characters substringToIndex: 1])); 743 lastKeyPressed = 0.0; 744 } 745 } 746 747 lastKeyPressed = [theEvent timestamp]; 748 749 if ((*icnwp)(self, icnwpSel, charBuffer)) { 750 return; 751 } 752 } 753 754 [super keyDown: theEvent]; 755} 756 757- (NSMenu *)menuForEvent:(NSEvent *)theEvent 758{ 759 NSArray *selnodes; 760 NSMenu *menu; 761 NSMenuItem *menuItem; 762 NSString *firstext; 763 NSDictionary *apps; 764 NSEnumerator *app_enum; 765 id key; 766 NSUInteger i; 767 768 if ([theEvent modifierFlags] == NSControlKeyMask) { 769 return [super menuForEvent: theEvent]; 770 } 771 772 selnodes = [self selectedNodes]; 773 774 if ([selnodes count]) { 775 NSAutoreleasePool *pool; 776 777 firstext = [[[selnodes objectAtIndex: 0] path] pathExtension]; 778 779 for (i = 0; i < [selnodes count]; i++) { 780 FSNode *snode = [selnodes objectAtIndex: i]; 781 NSString *selpath = [snode path]; 782 NSString *ext = [selpath pathExtension]; 783 784 if ([ext isEqual: firstext] == NO) { 785 return [super menuForEvent: theEvent]; 786 } 787 788 if ([snode isDirectory] == NO) { 789 if ([snode isPlain] == NO) { 790 return [super menuForEvent: theEvent]; 791 } 792 } else { 793 if (([snode isPackage] == NO) || [snode isApplication]) { 794 return [super menuForEvent: theEvent]; 795 } 796 } 797 } 798 799 menu = [[NSMenu alloc] initWithTitle: NSLocalizedStringFromTableInBundle(@"Open with", nil, [NSBundle bundleForClass:[self class]], @"")]; 800 apps = [[NSWorkspace sharedWorkspace] infoForExtension: firstext]; 801 app_enum = [[apps allKeys] objectEnumerator]; 802 803 pool = [NSAutoreleasePool new]; 804 805 while ((key = [app_enum nextObject])) { 806 menuItem = [NSMenuItem new]; 807 key = [key stringByDeletingPathExtension]; 808 [menuItem setTitle: key]; 809 [menuItem setTarget: desktopApp]; 810 [menuItem setAction: @selector(openSelectionWithApp:)]; 811 [menuItem setRepresentedObject: key]; 812 [menu addItem: menuItem]; 813 RELEASE (menuItem); 814 } 815 816 RELEASE (pool); 817 818 return [menu autorelease]; 819 } 820 821 return [super menuForEvent: theEvent]; 822} 823 824- (void)resizeWithOldSuperviewSize:(NSSize)oldFrameSize 825{ 826 [self tile]; 827} 828 829- (void)viewDidMoveToSuperview 830{ 831 [super viewDidMoveToSuperview]; 832 833 if ([self superview]) { 834 [[self window] setBackgroundColor: backColor]; 835 } 836} 837 838- (void)drawRect:(NSRect)rect 839{ 840 [super drawRect: rect]; 841 [backColor set]; 842 NSRectFill(rect); 843} 844 845- (BOOL)acceptsFirstMouse:(NSEvent *)theEvent 846{ 847 return YES; 848} 849 850- (BOOL)acceptsFirstResponder 851{ 852 return YES; 853} 854 855@end 856 857 858@implementation FSNIconsView (NodeRepContainer) 859 860- (void)showContentsOfNode:(FSNode *)anode 861{ 862 CREATE_AUTORELEASE_POOL(arp); 863 NSArray *subNodes = [anode subNodes]; 864 NSUInteger i; 865 866 for (i = 0; i < [icons count]; i++) { 867 [[icons objectAtIndex: i] removeFromSuperview]; 868 } 869 [icons removeAllObjects]; 870 editIcon = nil; 871 872 ASSIGN (node, anode); 873 [self readNodeInfo]; 874 [self calculateGridSize]; 875 876 for (i = 0; i < [subNodes count]; i++) { 877 FSNode *subnode = [subNodes objectAtIndex: i]; 878 FSNIcon *icon = [[FSNIcon alloc] initForNode: subnode 879 nodeInfoType: infoType 880 extendedType: extInfoType 881 iconSize: iconSize 882 iconPosition: iconPosition 883 labelFont: labelFont 884 textColor: textColor 885 gridIndex: -1 886 dndSource: YES 887 acceptDnd: YES 888 slideBack: YES]; 889 [icons addObject: icon]; 890 [self addSubview: icon]; 891 RELEASE (icon); 892 } 893 894 [icons sortUsingSelector: [fsnodeRep compareSelectorForDirectory: [node path]]]; 895 [self tile]; 896 897 DESTROY (lastSelection); 898 [self selectionDidChange]; 899 RELEASE (arp); 900} 901 902- (NSDictionary *)readNodeInfo 903{ 904 NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; 905 NSString *prefsname = [NSString stringWithFormat: @"viewer_at_%@", [node path]]; 906 NSDictionary *nodeDict = nil; 907 908 if ([node isWritable] 909 && ([[fsnodeRep volumes] containsObject: [node path]] == NO)) { 910 NSString *infoPath = [[node path] stringByAppendingPathComponent: @".gwdir"]; 911 912 if ([[NSFileManager defaultManager] fileExistsAtPath: infoPath]) { 913 NSDictionary *dict = [NSDictionary dictionaryWithContentsOfFile: infoPath]; 914 915 if (dict) { 916 nodeDict = [NSDictionary dictionaryWithDictionary: dict]; 917 } 918 } 919 } 920 921 if (nodeDict == nil) { 922 id defEntry = [defaults dictionaryForKey: prefsname]; 923 924 if (defEntry) { 925 nodeDict = [NSDictionary dictionaryWithDictionary: defEntry]; 926 } 927 } 928 929 if (nodeDict) { 930 id entry = [nodeDict objectForKey: @"iconsize"]; 931 iconSize = entry ? [entry intValue] : iconSize; 932 933 entry = [nodeDict objectForKey: @"labeltxtsize"]; 934 if (entry) { 935 labelTextSize = [entry intValue]; 936 ASSIGN (labelFont, [NSFont systemFontOfSize: labelTextSize]); 937 } 938 939 entry = [nodeDict objectForKey: @"iconposition"]; 940 iconPosition = entry ? [entry intValue] : iconPosition; 941 942 entry = [nodeDict objectForKey: @"fsn_info_type"]; 943 infoType = entry ? [entry intValue] : infoType; 944 945 if (infoType == FSNInfoExtendedType) { 946 DESTROY (extInfoType); 947 entry = [nodeDict objectForKey: @"ext_info_type"]; 948 949 if (entry) { 950 NSArray *availableTypes = [fsnodeRep availableExtendedInfoNames]; 951 952 if ([availableTypes containsObject: entry]) { 953 ASSIGN (extInfoType, entry); 954 } 955 } 956 957 if (extInfoType == nil) { 958 infoType = FSNInfoNameType; 959 } 960 } 961 } 962 963 return nodeDict; 964} 965 966- (NSMutableDictionary *)updateNodeInfo:(BOOL)ondisk 967{ 968 CREATE_AUTORELEASE_POOL(arp); 969 NSMutableDictionary *updatedInfo = nil; 970 971 if ([node isValid]) { 972 NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; 973 NSString *prefsname = [NSString stringWithFormat: @"viewer_at_%@", [node path]]; 974 NSString *infoPath = [[node path] stringByAppendingPathComponent: @".gwdir"]; 975 BOOL writable = ([node isWritable] && ([[fsnodeRep volumes] containsObject: [node path]] == NO)); 976 977 if (writable) { 978 if ([[NSFileManager defaultManager] fileExistsAtPath: infoPath]) { 979 NSDictionary *dict = [NSDictionary dictionaryWithContentsOfFile: infoPath]; 980 981 if (dict) { 982 updatedInfo = [dict mutableCopy]; 983 } 984 } 985 986 } else { 987 NSDictionary *prefs = [defaults dictionaryForKey: prefsname]; 988 989 if (prefs) { 990 updatedInfo = [prefs mutableCopy]; 991 } 992 } 993 994 if (updatedInfo == nil) { 995 updatedInfo = [NSMutableDictionary new]; 996 } 997 998 [updatedInfo setObject: [NSNumber numberWithInt: iconSize] 999 forKey: @"iconsize"]; 1000 1001 [updatedInfo setObject: [NSNumber numberWithInt: labelTextSize] 1002 forKey: @"labeltxtsize"]; 1003 1004 [updatedInfo setObject: [NSNumber numberWithInt: iconPosition] 1005 forKey: @"iconposition"]; 1006 1007 [updatedInfo setObject: [NSNumber numberWithInt: infoType] 1008 forKey: @"fsn_info_type"]; 1009 1010 if (infoType == FSNInfoExtendedType) { 1011 [updatedInfo setObject: extInfoType forKey: @"ext_info_type"]; 1012 } 1013 1014 if (ondisk) { 1015 if (writable) { 1016 [updatedInfo writeToFile: infoPath atomically: YES]; 1017 } else { 1018 [defaults setObject: updatedInfo forKey: prefsname]; 1019 } 1020 } 1021 } 1022 1023 RELEASE (arp); 1024 1025 return (AUTORELEASE (updatedInfo)); 1026} 1027 1028- (void)reloadContents 1029{ 1030 NSArray *selection = [self selectedNodes]; 1031 NSMutableArray *opennodes = [NSMutableArray array]; 1032 NSUInteger i; 1033 1034 RETAIN (selection); 1035 1036 for (i = 0; i < [icons count]; i++) { 1037 FSNIcon *icon = [icons objectAtIndex: i]; 1038 1039 if ([icon isOpened]) { 1040 [opennodes addObject: [icon node]]; 1041 } 1042 } 1043 1044 RETAIN (opennodes); 1045 1046 [self showContentsOfNode: node]; 1047 1048 selectionMask = FSNMultipleSelectionMask; 1049 selectionMask |= FSNCreatingSelectionMask; 1050 1051 for (i = 0; i < [selection count]; i++) { 1052 FSNode *nd = [selection objectAtIndex: i]; 1053 1054 if ([nd isValid]) { 1055 FSNIcon *icon = [self repOfSubnode: nd]; 1056 1057 if (icon) { 1058 [icon select]; 1059 } 1060 } 1061 } 1062 1063 selectionMask = NSSingleSelectionMask; 1064 1065 RELEASE (selection); 1066 1067 for (i = 0; i < [opennodes count]; i++) { 1068 FSNode *nd = [opennodes objectAtIndex: i]; 1069 1070 if ([nd isValid]) { 1071 FSNIcon *icon = [self repOfSubnode: nd]; 1072 1073 if (icon) { 1074 [icon setOpened: YES]; 1075 } 1076 } 1077 } 1078 1079 RELEASE (opennodes); 1080 1081 [self checkLockedReps]; 1082 [self tile]; 1083 1084 selection = [self selectedReps]; 1085 1086 if ([selection count]) { 1087 [self scrollIconToVisible: [selection objectAtIndex: 0]]; 1088 } 1089 1090 [self selectionDidChange]; 1091} 1092 1093- (void)reloadFromNode:(FSNode *)anode 1094{ 1095 if ([node isEqual: anode]) { 1096 [self reloadContents]; 1097 1098 } else if ([node isSubnodeOfNode: anode]) { 1099 NSArray *components = [FSNode nodeComponentsFromNode: anode toNode: node]; 1100 int i; 1101 1102 for (i = 0; i < [components count]; i++) { 1103 FSNode *component = [components objectAtIndex: i]; 1104 1105 if ([component isValid] == NO) { 1106 component = [FSNode nodeWithPath: [component parentPath]]; 1107 [self showContentsOfNode: component]; 1108 break; 1109 } 1110 } 1111 } 1112} 1113 1114- (FSNode *)baseNode 1115{ 1116 return node; 1117} 1118 1119- (FSNode *)shownNode 1120{ 1121 return node; 1122} 1123 1124- (BOOL)isSingleNode 1125{ 1126 return YES; 1127} 1128 1129- (BOOL)isShowingNode:(FSNode *)anode 1130{ 1131 return [node isEqual: anode]; 1132} 1133 1134- (BOOL)isShowingPath:(NSString *)path 1135{ 1136 return [[node path] isEqual: path]; 1137} 1138 1139- (void)sortTypeChangedAtPath:(NSString *)path 1140{ 1141 if ((path == nil) || [[node path] isEqual: path]) { 1142 [self reloadContents]; 1143 } 1144} 1145 1146- (void)nodeContentsWillChange:(NSDictionary *)info 1147{ 1148 [self checkLockedReps]; 1149} 1150 1151- (void)nodeContentsDidChange:(NSDictionary *)info 1152{ 1153 NSString *operation = [info objectForKey: @"operation"]; 1154 NSString *source = [info objectForKey: @"source"]; 1155 NSString *destination = [info objectForKey: @"destination"]; 1156 NSArray *files = [info objectForKey: @"files"]; 1157 NSString *ndpath = [node path]; 1158 NSUInteger i; 1159 1160 if ([operation isEqual: @"GWorkspaceRenameOperation"]) { 1161 files = [NSArray arrayWithObject: [source lastPathComponent]]; 1162 source = [source stringByDeletingLastPathComponent]; 1163 } 1164 1165 if (([ndpath isEqual: source] == NO) && ([ndpath isEqual: destination] == NO)) { 1166 [self reloadContents]; 1167 return; 1168 } 1169 1170 if ([ndpath isEqual: source]) { 1171 if ([operation isEqual: NSWorkspaceMoveOperation] 1172 || [operation isEqual: NSWorkspaceDestroyOperation] 1173 || [operation isEqual: @"GWorkspaceRenameOperation"] 1174 || [operation isEqual: NSWorkspaceRecycleOperation] 1175 || [operation isEqual: @"GWorkspaceRecycleOutOperation"]) { 1176 1177 if ([operation isEqual: NSWorkspaceRecycleOperation]) { 1178 files = [info objectForKey: @"origfiles"]; 1179 } 1180 1181 for (i = 0; i < [files count]; i++) { 1182 NSString *fname = [files objectAtIndex: i]; 1183 FSNode *subnode = [FSNode nodeWithRelativePath: fname parent: node]; 1184 [self removeRepOfSubnode: subnode]; 1185 } 1186 } 1187 } 1188 1189 if ([operation isEqual: @"GWorkspaceRenameOperation"]) { 1190 files = [NSArray arrayWithObject: [destination lastPathComponent]]; 1191 destination = [destination stringByDeletingLastPathComponent]; 1192 } 1193 1194 if ([ndpath isEqual: destination] 1195 && ([operation isEqual: NSWorkspaceMoveOperation] 1196 || [operation isEqual: NSWorkspaceCopyOperation] 1197 || [operation isEqual: NSWorkspaceLinkOperation] 1198 || [operation isEqual: NSWorkspaceDuplicateOperation] 1199 || [operation isEqual: @"GWorkspaceCreateDirOperation"] 1200 || [operation isEqual: @"GWorkspaceCreateFileOperation"] 1201 || [operation isEqual: NSWorkspaceRecycleOperation] 1202 || [operation isEqual: @"GWorkspaceRenameOperation"] 1203 || [operation isEqual: @"GWorkspaceRecycleOutOperation"])) { 1204 1205 if ([operation isEqual: NSWorkspaceRecycleOperation]) { 1206 files = [info objectForKey: @"files"]; 1207 } 1208 1209 for (i = 0; i < [files count]; i++) { 1210 NSString *fname = [files objectAtIndex: i]; 1211 FSNode *subnode = [FSNode nodeWithRelativePath: fname parent: node]; 1212 FSNIcon *icon = [self repOfSubnode: subnode]; 1213 1214 if (icon) { 1215 [icon setNode: subnode]; 1216 } else { 1217 [self addRepForSubnode: subnode]; 1218 } 1219 } 1220 1221 [self sortIcons]; 1222 } 1223 1224 [self checkLockedReps]; 1225 [self tile]; 1226 [self setNeedsDisplay: YES]; 1227 [self selectionDidChange]; 1228} 1229 1230- (void)watchedPathChanged:(NSDictionary *)info 1231{ 1232 NSString *event = [info objectForKey: @"event"]; 1233 NSArray *files = [info objectForKey: @"files"]; 1234 NSString *ndpath = [node path]; 1235 NSUInteger i; 1236 1237 if ([event isEqual: @"GWFileDeletedInWatchedDirectory"]) { 1238 for (i = 0; i < [files count]; i++) { 1239 NSString *fname = [files objectAtIndex: i]; 1240 NSString *fpath = [ndpath stringByAppendingPathComponent: fname]; 1241 [self removeRepOfSubnodePath: fpath]; 1242 } 1243 1244 } else if ([event isEqual: @"GWFileCreatedInWatchedDirectory"]) { 1245 for (i = 0; i < [files count]; i++) { 1246 NSString *fname = [files objectAtIndex: i]; 1247 FSNode *subnode = [FSNode nodeWithRelativePath: fname parent: node]; 1248 1249 if (subnode && [subnode isValid]) { 1250 FSNIcon *icon = [self repOfSubnode: subnode]; 1251 1252 if (icon) { 1253 [icon setNode: subnode]; 1254 } else { 1255 [self addRepForSubnode: subnode]; 1256 } 1257 } 1258 } 1259 } 1260 1261 [self sortIcons]; 1262 [self tile]; 1263 [self setNeedsDisplay: YES]; 1264 [self selectionDidChange]; 1265} 1266 1267- (void)setShowType:(FSNInfoType)type 1268{ 1269 if (infoType != type) { 1270 NSUInteger i; 1271 1272 infoType = type; 1273 DESTROY (extInfoType); 1274 1275 [self calculateGridSize]; 1276 1277 for (i = 0; i < [icons count]; i++) { 1278 FSNIcon *icon = [icons objectAtIndex: i]; 1279 1280 [icon setNodeInfoShowType: infoType]; 1281 [icon tile]; 1282 } 1283 1284 [self sortIcons]; 1285 [self tile]; 1286 } 1287} 1288 1289- (void)setExtendedShowType:(NSString *)type 1290{ 1291 if ((extInfoType == nil) || ([extInfoType isEqual: type] == NO)) { 1292 int i; 1293 1294 infoType = FSNInfoExtendedType; 1295 ASSIGN (extInfoType, type); 1296 1297 [self calculateGridSize]; 1298 1299 for (i = 0; i < [icons count]; i++) { 1300 FSNIcon *icon = [icons objectAtIndex: i]; 1301 1302 [icon setExtendedShowType: extInfoType]; 1303 [icon tile]; 1304 } 1305 1306 [self sortIcons]; 1307 [self tile]; 1308 } 1309} 1310 1311- (FSNInfoType)showType 1312{ 1313 return infoType; 1314} 1315 1316- (void)setIconSize:(int)size 1317{ 1318 NSUInteger i; 1319 1320 iconSize = size; 1321 [self calculateGridSize]; 1322 1323 for (i = 0; i < [icons count]; i++) { 1324 FSNIcon *icon = [icons objectAtIndex: i]; 1325 [icon setIconSize: iconSize]; 1326 } 1327 1328 [self tile]; 1329} 1330 1331- (int)iconSize 1332{ 1333 return iconSize; 1334} 1335 1336- (void)setLabelTextSize:(int)size 1337{ 1338 NSUInteger i; 1339 1340 labelTextSize = size; 1341 ASSIGN (labelFont, [NSFont systemFontOfSize: labelTextSize]); 1342 [self calculateGridSize]; 1343 1344 for (i = 0; i < [icons count]; i++) { 1345 FSNIcon *icon = [icons objectAtIndex: i]; 1346 [icon setFont: labelFont]; 1347 } 1348 1349 [nameEditor setFont: labelFont]; 1350 1351 [self tile]; 1352} 1353 1354- (int)labelTextSize 1355{ 1356 return labelTextSize; 1357} 1358 1359- (void)setIconPosition:(int)pos 1360{ 1361 NSUInteger i; 1362 1363 iconPosition = pos; 1364 [self calculateGridSize]; 1365 1366 for (i = 0; i < [icons count]; i++) { 1367 FSNIcon *icon = [icons objectAtIndex: i]; 1368 [icon setIconPosition: iconPosition]; 1369 } 1370 1371 [self tile]; 1372} 1373 1374- (int)iconPosition 1375{ 1376 return iconPosition; 1377} 1378 1379- (void)updateIcons 1380{ 1381 NSUInteger i; 1382 1383 for (i = 0; i < [icons count]; i++) { 1384 FSNIcon *icon = [icons objectAtIndex: i]; 1385 FSNode *inode = [icon node]; 1386 [icon setNode: inode]; 1387 } 1388} 1389 1390- (id)repOfSubnode:(FSNode *)anode 1391{ 1392 NSUInteger i; 1393 1394 for (i = 0; i < [icons count]; i++) { 1395 FSNIcon *icon = [icons objectAtIndex: i]; 1396 1397 if ([[icon node] isEqualToNode: anode]) { 1398 return icon; 1399 } 1400 } 1401 1402 return nil; 1403} 1404 1405- (id)repOfSubnodePath:(NSString *)apath 1406{ 1407 NSUInteger i; 1408 1409 for (i = 0; i < [icons count]; i++) { 1410 FSNIcon *icon = [icons objectAtIndex: i]; 1411 1412 if ([[[icon node] path] isEqual: apath]) { 1413 return icon; 1414 } 1415 } 1416 1417 return nil; 1418} 1419 1420- (id)addRepForSubnode:(FSNode *)anode 1421{ 1422 CREATE_AUTORELEASE_POOL(arp); 1423 FSNIcon *icon = [[FSNIcon alloc] initForNode: anode 1424 nodeInfoType: infoType 1425 extendedType: extInfoType 1426 iconSize: iconSize 1427 iconPosition: iconPosition 1428 labelFont: labelFont 1429 textColor: textColor 1430 gridIndex: -1 1431 dndSource: YES 1432 acceptDnd: YES 1433 slideBack: YES]; 1434 [icons addObject: icon]; 1435 [self addSubview: icon]; 1436 RELEASE (icon); 1437 RELEASE (arp); 1438 1439 return icon; 1440} 1441 1442- (id)addRepForSubnodePath:(NSString *)apath 1443{ 1444 FSNode *subnode = [FSNode nodeWithRelativePath: apath parent: node]; 1445 return [self addRepForSubnode: subnode]; 1446} 1447 1448- (void)removeRepOfSubnode:(FSNode *)anode 1449{ 1450 FSNIcon *icon = [self repOfSubnode: anode]; 1451 1452 if (icon) { 1453 [self removeRep: icon]; 1454 } 1455} 1456 1457- (void)removeRepOfSubnodePath:(NSString *)apath 1458{ 1459 FSNIcon *icon = [self repOfSubnodePath: apath]; 1460 1461 if (icon) { 1462 [self removeRep: icon]; 1463 } 1464} 1465 1466- (void)removeRep:(id)arep 1467{ 1468 if (arep == editIcon) { 1469 editIcon = nil; 1470 } 1471 [arep removeFromSuperview]; 1472 [icons removeObject: arep]; 1473} 1474 1475- (void)unloadFromNode:(FSNode *)anode 1476{ 1477 FSNode *parent = [FSNode nodeWithPath: [anode parentPath]]; 1478 [self showContentsOfNode: parent]; 1479} 1480 1481- (void)repSelected:(id)arep 1482{ 1483} 1484 1485- (void)unselectOtherReps:(id)arep 1486{ 1487 NSUInteger i; 1488 1489 if (selectionMask & FSNMultipleSelectionMask) { 1490 return; 1491 } 1492 1493 for (i = 0; i < [icons count]; i++) { 1494 FSNIcon *icon = [icons objectAtIndex: i]; 1495 1496 if (icon != arep) { 1497 [icon unselect]; 1498 } 1499 } 1500} 1501 1502- (void)selectReps:(NSArray *)reps 1503{ 1504 NSUInteger i; 1505 1506 selectionMask = NSSingleSelectionMask; 1507 selectionMask |= FSNCreatingSelectionMask; 1508 1509 [self unselectOtherReps: nil]; 1510 1511 selectionMask = FSNMultipleSelectionMask; 1512 selectionMask |= FSNCreatingSelectionMask; 1513 1514 for (i = 0; i < [reps count]; i++) { 1515 [[reps objectAtIndex: i] select]; 1516 } 1517 1518 selectionMask = NSSingleSelectionMask; 1519 1520 [self selectionDidChange]; 1521} 1522 1523- (void)selectRepsOfSubnodes:(NSArray *)nodes 1524{ 1525 NSUInteger i; 1526 1527 selectionMask = NSSingleSelectionMask; 1528 selectionMask |= FSNCreatingSelectionMask; 1529 1530 [self unselectOtherReps: nil]; 1531 1532 selectionMask = FSNMultipleSelectionMask; 1533 selectionMask |= FSNCreatingSelectionMask; 1534 1535 for (i = 0; i < [icons count]; i++) { 1536 FSNIcon *icon = [icons objectAtIndex: i]; 1537 1538 if ([nodes containsObject: [icon node]]) { 1539 [icon select]; 1540 } 1541 } 1542 1543 selectionMask = NSSingleSelectionMask; 1544 1545 [self selectionDidChange]; 1546} 1547 1548- (void)selectRepsOfPaths:(NSArray *)paths 1549{ 1550 NSUInteger i; 1551 1552 selectionMask = NSSingleSelectionMask; 1553 selectionMask |= FSNCreatingSelectionMask; 1554 1555 [self unselectOtherReps: nil]; 1556 1557 selectionMask = FSNMultipleSelectionMask; 1558 selectionMask |= FSNCreatingSelectionMask; 1559 1560 for (i = 0; i < [icons count]; i++) { 1561 FSNIcon *icon = [icons objectAtIndex: i]; 1562 1563 if ([paths containsObject: [[icon node] path]]) { 1564 [icon select]; 1565 } 1566 } 1567 1568 selectionMask = NSSingleSelectionMask; 1569 1570 [self selectionDidChange]; 1571} 1572 1573- (void)selectAll 1574{ 1575 NSUInteger i; 1576 1577 selectionMask = NSSingleSelectionMask; 1578 selectionMask |= FSNCreatingSelectionMask; 1579 1580 [self unselectOtherReps: nil]; 1581 1582 selectionMask = FSNMultipleSelectionMask; 1583 selectionMask |= FSNCreatingSelectionMask; 1584 1585 for (i = 0; i < [icons count]; i++) { 1586 FSNIcon *icon = [icons objectAtIndex: i]; 1587 FSNode *inode = [icon node]; 1588 1589 if ([inode isReserved] == NO) { 1590 [icon select]; 1591 } 1592 } 1593 1594 selectionMask = NSSingleSelectionMask; 1595 1596 [self selectionDidChange]; 1597} 1598 1599- (void)scrollSelectionToVisible 1600{ 1601 NSArray *selection = [self selectedReps]; 1602 1603 if ([selection count]) { 1604 [self scrollIconToVisible: [selection objectAtIndex: 0]]; 1605 } else { 1606 NSRect r = [self frame]; 1607 [self scrollRectToVisible: NSMakeRect(0, r.size.height - 1, 1, 1)]; 1608 } 1609} 1610 1611- (NSArray *)reps 1612{ 1613 return icons; 1614} 1615 1616- (NSArray *)selectedReps 1617{ 1618 NSMutableArray *selectedReps = [NSMutableArray array]; 1619 NSUInteger i; 1620 1621 for (i = 0; i < [icons count]; i++) { 1622 FSNIcon *icon = [icons objectAtIndex: i]; 1623 1624 if ([icon isSelected]) { 1625 [selectedReps addObject: icon]; 1626 } 1627 } 1628 1629 return [selectedReps makeImmutableCopyOnFail: NO]; 1630} 1631 1632- (NSArray *)selectedNodes 1633{ 1634 NSMutableArray *selectedNodes = [NSMutableArray array]; 1635 NSUInteger i; 1636 1637 for (i = 0; i < [icons count]; i++) { 1638 FSNIcon *icon = [icons objectAtIndex: i]; 1639 1640 if ([icon isSelected]) { 1641 NSArray *selection = [icon selection]; 1642 1643 if (selection) { 1644 [selectedNodes addObjectsFromArray: selection]; 1645 } else { 1646 [selectedNodes addObject: [icon node]]; 1647 } 1648 } 1649 } 1650 1651 return [selectedNodes makeImmutableCopyOnFail: NO]; 1652} 1653 1654- (NSArray *)selectedPaths 1655{ 1656 NSMutableArray *selectedPaths = [NSMutableArray array]; 1657 NSUInteger i, j; 1658 1659 for (i = 0; i < [icons count]; i++) { 1660 FSNIcon *icon = [icons objectAtIndex: i]; 1661 1662 if ([icon isSelected]) { 1663 NSArray *selection = [icon selection]; 1664 1665 if (selection) { 1666 for (j = 0; j < [selection count]; j++) { 1667 [selectedPaths addObject: [[selection objectAtIndex: j] path]]; 1668 } 1669 } else { 1670 [selectedPaths addObject: [[icon node] path]]; 1671 } 1672 } 1673 } 1674 1675 return [selectedPaths makeImmutableCopyOnFail: NO]; 1676} 1677 1678- (void)selectionDidChange 1679{ 1680 if (!(selectionMask & FSNCreatingSelectionMask)) { 1681 NSArray *selection = [self selectedNodes]; 1682 1683 if ([selection count] == 0) { 1684 selection = [NSArray arrayWithObject: node]; 1685 } 1686 1687 if ((lastSelection == nil) || ([selection isEqual: lastSelection] == NO)) { 1688 ASSIGN (lastSelection, selection); 1689 [desktopApp selectionChanged: selection]; 1690 } 1691 1692 [self updateNameEditor]; 1693 } 1694} 1695 1696- (void)checkLockedReps 1697{ 1698 NSUInteger i; 1699 1700 for (i = 0; i < [icons count]; i++) { 1701 [[icons objectAtIndex: i] checkLocked]; 1702 } 1703} 1704 1705- (void)setSelectionMask:(FSNSelectionMask)mask 1706{ 1707 selectionMask = mask; 1708} 1709 1710- (FSNSelectionMask)selectionMask 1711{ 1712 return selectionMask; 1713} 1714 1715- (void)openSelectionInNewViewer:(BOOL)newv 1716{ 1717 [desktopApp openSelectionInNewViewer: newv]; 1718} 1719 1720- (void)restoreLastSelection 1721{ 1722 if (lastSelection) { 1723 [self selectRepsOfSubnodes: lastSelection]; 1724 } 1725} 1726 1727- (void)setLastShownNode:(FSNode *)anode 1728{ 1729} 1730 1731- (BOOL)needsDndProxy 1732{ 1733 return NO; 1734} 1735 1736- (BOOL)involvedByFileOperation:(NSDictionary *)opinfo 1737{ 1738 return [node involvedByFileOperation: opinfo]; 1739} 1740 1741- (BOOL)validatePasteOfFilenames:(NSArray *)names 1742 wasCut:(BOOL)cut 1743{ 1744 NSString *nodePath = [node path]; 1745 NSString *prePath = [NSString stringWithString: nodePath]; 1746 NSString *basePath; 1747 1748 if ([names count] == 0) { 1749 return NO; 1750 } 1751 1752 if ([node isWritable] == NO) { 1753 return NO; 1754 } 1755 1756 basePath = [[names objectAtIndex: 0] stringByDeletingLastPathComponent]; 1757 if ([basePath isEqual: nodePath]) { 1758 return NO; 1759 } 1760 1761 if ([names containsObject: nodePath]) { 1762 return NO; 1763 } 1764 1765 while (1) { 1766 if ([names containsObject: prePath]) { 1767 return NO; 1768 } 1769 if ([prePath isEqual: path_separator()]) { 1770 break; 1771 } 1772 prePath = [prePath stringByDeletingLastPathComponent]; 1773 } 1774 1775 return YES; 1776} 1777 1778- (void)setBackgroundColor:(NSColor *)acolor 1779{ 1780 ASSIGN (backColor, acolor); 1781 [[self window] setBackgroundColor: backColor]; 1782 [self setNeedsDisplay: YES]; 1783} 1784 1785- (NSColor *)backgroundColor 1786{ 1787 return backColor; 1788} 1789 1790- (void)setTextColor:(NSColor *)acolor 1791{ 1792 NSUInteger i; 1793 1794 for (i = 0; i < [icons count]; i++) { 1795 [[icons objectAtIndex: i] setLabelTextColor: acolor]; 1796 } 1797 1798 [nameEditor setTextColor: acolor]; 1799 1800 ASSIGN (textColor, acolor); 1801} 1802 1803- (NSColor *)textColor 1804{ 1805 return textColor; 1806} 1807 1808- (NSColor *)disabledTextColor 1809{ 1810 return disabledTextColor; 1811} 1812 1813@end 1814 1815 1816@implementation FSNIconsView (DraggingDestination) 1817 1818- (NSDragOperation)draggingEntered:(id <NSDraggingInfo>)sender 1819{ 1820 NSPasteboard *pb; 1821 NSDragOperation sourceDragMask; 1822 NSArray *sourcePaths; 1823 NSString *basePath; 1824 NSString *nodePath; 1825 NSString *prePath; 1826 NSUInteger count; 1827 1828 isDragTarget = NO; 1829 1830 pb = [sender draggingPasteboard]; 1831 1832 if (pb && [[pb types] containsObject: NSFilenamesPboardType]) { 1833 sourcePaths = [pb propertyListForType: NSFilenamesPboardType]; 1834 1835 } else if ([[pb types] containsObject: @"GWRemoteFilenamesPboardType"]) { 1836 NSData *pbData = [pb dataForType: @"GWRemoteFilenamesPboardType"]; 1837 NSDictionary *pbDict = [NSUnarchiver unarchiveObjectWithData: pbData]; 1838 1839 sourcePaths = [pbDict objectForKey: @"paths"]; 1840 1841 } else if ([[pb types] containsObject: @"GWLSFolderPboardType"]) { 1842 NSData *pbData = [pb dataForType: @"GWLSFolderPboardType"]; 1843 NSDictionary *pbDict = [NSUnarchiver unarchiveObjectWithData: pbData]; 1844 1845 sourcePaths = [pbDict objectForKey: @"paths"]; 1846 1847 } else { 1848 return NSDragOperationNone; 1849 } 1850 1851 count = [sourcePaths count]; 1852 if (count == 0) { 1853 return NSDragOperationNone; 1854 } 1855 1856 if ([node isWritable] == NO) { 1857 return NSDragOperationNone; 1858 } 1859 1860 nodePath = [node path]; 1861 1862 basePath = [[sourcePaths objectAtIndex: 0] stringByDeletingLastPathComponent]; 1863 if ([basePath isEqual: nodePath]) { 1864 return NSDragOperationNone; 1865 } 1866 1867 if ([sourcePaths containsObject: nodePath]) { 1868 return NSDragOperationNone; 1869 } 1870 1871 prePath = [NSString stringWithString: nodePath]; 1872 1873 while (1) { 1874 if ([sourcePaths containsObject: prePath]) { 1875 return NSDragOperationNone; 1876 } 1877 if ([prePath isEqual: path_separator()]) { 1878 break; 1879 } 1880 prePath = [prePath stringByDeletingLastPathComponent]; 1881 } 1882 1883 if ([node isDirectory] && [node isParentOfPath: basePath]) { 1884 NSArray *subNodes = [node subNodes]; 1885 NSUInteger i; 1886 1887 for (i = 0; i < [subNodes count]; i++) { 1888 FSNode *nd = [subNodes objectAtIndex: i]; 1889 1890 if ([nd isDirectory]) { 1891 NSUInteger j; 1892 1893 for (j = 0; j < count; j++) { 1894 NSString *fname = [[sourcePaths objectAtIndex: j] lastPathComponent]; 1895 1896 if ([[nd name] isEqual: fname]) { 1897 return NSDragOperationNone; 1898 } 1899 } 1900 } 1901 } 1902 } 1903 1904 isDragTarget = YES; 1905 forceCopy = NO; 1906 1907 sourceDragMask = [sender draggingSourceOperationMask]; 1908 1909 if (sourceDragMask == NSDragOperationCopy) { 1910 return NSDragOperationCopy; 1911 } else if (sourceDragMask == NSDragOperationLink) { 1912 return NSDragOperationLink; 1913 } else { 1914 if ([[NSFileManager defaultManager] isWritableFileAtPath: basePath]) { 1915 return NSDragOperationAll; 1916 } else { 1917 forceCopy = YES; 1918 return NSDragOperationCopy; 1919 } 1920 } 1921 1922 isDragTarget = NO; 1923 return NSDragOperationNone; 1924} 1925 1926- (NSDragOperation)draggingUpdated:(id <NSDraggingInfo>)sender 1927{ 1928 NSDragOperation sourceDragMask = [sender draggingSourceOperationMask]; 1929 NSRect vr = [self visibleRect]; 1930 NSRect scr = vr; 1931 int xsc = 0.0; 1932 int ysc = 0.0; 1933 int sc = 0; 1934 float margin = 4.0; 1935 NSRect ir = NSInsetRect(vr, margin, margin); 1936 NSPoint p = [sender draggingLocation]; 1937 int i; 1938 1939 p = [self convertPoint: p fromView: nil]; 1940 1941 if ([self mouse: p inRect: ir] == NO) { 1942 if (p.x < (NSMinX(vr) + margin)) { 1943 xsc = -gridSize.width; 1944 } else if (p.x > (NSMaxX(vr) - margin)) { 1945 xsc = gridSize.width; 1946 } 1947 1948 if (p.y < (NSMinY(vr) + margin)) { 1949 ysc = -gridSize.height; 1950 } else if (p.y > (NSMaxY(vr) - margin)) { 1951 ysc = gridSize.height; 1952 } 1953 1954 sc = (abs(xsc) >= abs(ysc)) ? xsc : ysc; 1955 1956 for (i = 0; i < (int)fabsf(sc / margin); i++) { 1957 CREATE_AUTORELEASE_POOL (pool); 1958 NSDate *limit = [NSDate dateWithTimeIntervalSinceNow: 0.01]; 1959 int x = (abs(xsc) >= i) ? (xsc > 0 ? margin : -margin) : 0; 1960 int y = (abs(ysc) >= i) ? (ysc > 0 ? margin : -margin) : 0; 1961 1962 scr = NSOffsetRect(scr, x, y); 1963 [self scrollRectToVisible: scr]; 1964 1965 vr = [self visibleRect]; 1966 ir = NSInsetRect(vr, margin, margin); 1967 1968 p = [[self window] mouseLocationOutsideOfEventStream]; 1969 p = [self convertPoint: p fromView: nil]; 1970 1971 if ([self mouse: p inRect: ir]) { 1972 RELEASE (pool); 1973 break; 1974 } 1975 1976 [[NSRunLoop currentRunLoop] runUntilDate: limit]; 1977 RELEASE (pool); 1978 } 1979 } 1980 1981 if (isDragTarget == NO) { 1982 return NSDragOperationNone; 1983 } 1984 1985 if (sourceDragMask == NSDragOperationCopy) { 1986 return NSDragOperationCopy; 1987 } else if (sourceDragMask == NSDragOperationLink) { 1988 return NSDragOperationLink; 1989 } else { 1990 return forceCopy ? NSDragOperationCopy : NSDragOperationAll; 1991 } 1992 1993 return NSDragOperationNone; 1994} 1995 1996- (void)draggingExited:(id <NSDraggingInfo>)sender 1997{ 1998 isDragTarget = NO; 1999} 2000 2001- (BOOL)prepareForDragOperation:(id <NSDraggingInfo>)sender 2002{ 2003 return isDragTarget; 2004} 2005 2006- (BOOL)performDragOperation:(id <NSDraggingInfo>)sender 2007{ 2008 return YES; 2009} 2010 2011- (void)concludeDragOperation:(id <NSDraggingInfo>)sender 2012{ 2013 NSPasteboard *pb; 2014 NSDragOperation sourceDragMask; 2015 NSArray *sourcePaths; 2016 NSString *operation, *source; 2017 NSMutableArray *files; 2018 NSMutableDictionary *opDict; 2019 NSString *trashPath; 2020 NSUInteger i; 2021 2022 isDragTarget = NO; 2023 2024 sourceDragMask = [sender draggingSourceOperationMask]; 2025 pb = [sender draggingPasteboard]; 2026 2027 if ([[pb types] containsObject: @"GWRemoteFilenamesPboardType"]) { 2028 NSData *pbData = [pb dataForType: @"GWRemoteFilenamesPboardType"]; 2029 2030 [desktopApp concludeRemoteFilesDragOperation: pbData 2031 atLocalPath: [node path]]; 2032 return; 2033 2034 } else if ([[pb types] containsObject: @"GWLSFolderPboardType"]) { 2035 NSData *pbData = [pb dataForType: @"GWLSFolderPboardType"]; 2036 2037 [desktopApp lsfolderDragOperation: pbData 2038 concludedAtPath: [node path]]; 2039 return; 2040 } 2041 2042 sourcePaths = [pb propertyListForType: NSFilenamesPboardType]; 2043 2044 if ([sourcePaths count] == 0) { 2045 return; 2046 } 2047 2048 source = [[sourcePaths objectAtIndex: 0] stringByDeletingLastPathComponent]; 2049 2050 trashPath = [desktopApp trashPath]; 2051 2052 if ([source isEqual: trashPath]) { 2053 operation = @"GWorkspaceRecycleOutOperation"; 2054 } else { 2055 if (sourceDragMask == NSDragOperationCopy) { 2056 operation = NSWorkspaceCopyOperation; 2057 } else if (sourceDragMask == NSDragOperationLink) { 2058 operation = NSWorkspaceLinkOperation; 2059 } else { 2060 if ([[NSFileManager defaultManager] isWritableFileAtPath: source]) { 2061 operation = NSWorkspaceMoveOperation; 2062 } else { 2063 operation = NSWorkspaceCopyOperation; 2064 } 2065 } 2066 } 2067 2068 files = [NSMutableArray array]; 2069 for(i = 0; i < [sourcePaths count]; i++) { 2070 [files addObject: [[sourcePaths objectAtIndex: i] lastPathComponent]]; 2071 } 2072 2073 opDict = [NSMutableDictionary dictionary]; 2074 [opDict setObject: operation forKey: @"operation"]; 2075 [opDict setObject: source forKey: @"source"]; 2076 [opDict setObject: [node path] forKey: @"destination"]; 2077 [opDict setObject: files forKey: @"files"]; 2078 2079 [desktopApp performFileOperation: opDict]; 2080} 2081 2082@end 2083 2084 2085@implementation FSNIconsView (IconNameEditing) 2086 2087- (void)updateNameEditor 2088{ 2089 [self stopRepNameEditing]; 2090 2091 if (lastSelection && ([lastSelection count] == 1)) { 2092 editIcon = [self repOfSubnode: [lastSelection objectAtIndex: 0]]; 2093 } 2094 2095 if (editIcon) { 2096 FSNode *ednode = [editIcon node]; 2097 NSString *nodeDescr = [editIcon shownInfo]; 2098 NSRect icnr = [editIcon frame]; 2099 NSRect labr = [editIcon labelRect]; 2100 int ipos = [editIcon iconPosition]; 2101 int margin = [fsnodeRep labelMargin]; 2102 float bw = [self bounds].size.width - EDIT_MARGIN; 2103 float edwidth = 0.0; 2104 NSRect edrect; 2105 2106 [editIcon setNameEdited: YES]; 2107 2108 edwidth = [[nameEditor font] widthOfString: nodeDescr]; 2109 edwidth += margin; 2110 2111 if (ipos == NSImageAbove) { 2112 float centerx = icnr.origin.x + (icnr.size.width / 2); 2113 2114 if ((centerx + (edwidth / 2)) >= bw) { 2115 centerx -= (centerx + (edwidth / 2) - bw); 2116 } else if ((centerx - (edwidth / 2)) < margin) { 2117 centerx += fabs(centerx - (edwidth / 2)) + margin; 2118 } 2119 2120 edrect = [self convertRect: labr fromView: editIcon]; 2121 edrect.origin.x = centerx - (edwidth / 2); 2122 edrect.size.width = edwidth; 2123 2124 } else if (ipos == NSImageLeft) { 2125 edrect = [self convertRect: labr fromView: editIcon]; 2126 edrect.size.width = edwidth; 2127 2128 if ((edrect.origin.x + edwidth) >= bw) { 2129 edrect.size.width = bw - edrect.origin.x; 2130 } 2131 } 2132 else 2133 { 2134 NSLog(@"Unexpected icon position in [FSNIconsView updateNameEditor]"); 2135 return; 2136 } 2137 2138 edrect = NSIntegralRect(edrect); 2139 2140 [nameEditor setFrame: edrect]; 2141 2142 if (ipos == NSImageAbove) { 2143 [nameEditor setAlignment: NSCenterTextAlignment]; 2144 } else if (ipos == NSImageLeft) { 2145 [nameEditor setAlignment: NSLeftTextAlignment]; 2146 } 2147 2148 [nameEditor setNode: ednode 2149 stringValue: nodeDescr 2150 index: 0]; 2151 2152 [nameEditor setBackgroundColor: [NSColor selectedControlColor]]; 2153 2154 if ([editIcon isLocked] == NO) { 2155 [nameEditor setTextColor: [NSColor controlTextColor]]; 2156 } else { 2157 [nameEditor setTextColor: [NSColor disabledControlTextColor]]; 2158 } 2159 2160 [nameEditor setEditable: NO]; 2161 [nameEditor setSelectable: NO]; 2162 [self addSubview: nameEditor]; 2163 } 2164} 2165 2166- (void)setNameEditorForRep:(id)arep 2167{ 2168} 2169 2170- (void)stopRepNameEditing 2171{ 2172 NSUInteger i; 2173 2174 if ([[self subviews] containsObject: nameEditor]) { 2175 NSRect edrect = [nameEditor frame]; 2176 [nameEditor abortEditing]; 2177 [nameEditor setEditable: NO]; 2178 [nameEditor setSelectable: NO]; 2179 [nameEditor setNode: nil stringValue: @"" index: -1]; 2180 [nameEditor removeFromSuperview]; 2181 [self setNeedsDisplayInRect: edrect]; 2182 } 2183 2184 for (i = 0; i < [icons count]; i++) { 2185 [[icons objectAtIndex: i] setNameEdited: NO]; 2186 } 2187 2188 editIcon = nil; 2189} 2190 2191- (BOOL)canStartRepNameEditing 2192{ 2193 return (editIcon && ([editIcon isLocked] == NO) 2194 && ([[editIcon node] isMountPoint] == NO)); 2195} 2196 2197- (void)controlTextDidChange:(NSNotification *)aNotification 2198{ 2199 NSRect icnr = [editIcon frame]; 2200 int ipos = [editIcon iconPosition]; 2201 float edwidth = [[nameEditor font] widthOfString: [nameEditor stringValue]]; 2202 int margin = [fsnodeRep labelMargin]; 2203 float bw = [self bounds].size.width - EDIT_MARGIN; 2204 NSRect edrect = [nameEditor frame]; 2205 2206 edwidth += margin; 2207 2208 if (ipos == NSImageAbove) { 2209 float centerx = icnr.origin.x + (icnr.size.width / 2); 2210 2211 while ((centerx + (edwidth / 2)) > bw) { 2212 centerx --; 2213 if (centerx < EDIT_MARGIN) { 2214 break; 2215 } 2216 } 2217 2218 while ((centerx - (edwidth / 2)) < EDIT_MARGIN) { 2219 centerx ++; 2220 if (centerx >= bw) { 2221 break; 2222 } 2223 } 2224 2225 edrect.origin.x = centerx - (edwidth / 2); 2226 edrect.size.width = edwidth; 2227 2228 } else if (ipos == NSImageLeft) { 2229 edrect.size.width = edwidth; 2230 2231 if ((edrect.origin.x + edwidth) >= bw) { 2232 edrect.size.width = bw - edrect.origin.x; 2233 } 2234 } 2235 2236 [self setNeedsDisplayInRect: [nameEditor frame]]; 2237 [nameEditor setFrame: NSIntegralRect(edrect)]; 2238} 2239 2240- (void)controlTextDidEndEditing:(NSNotification *)aNotification 2241{ 2242 FSNode *ednode = [nameEditor node]; 2243 2244#define CLEAREDITING \ 2245 [self stopRepNameEditing]; \ 2246 return 2247 2248 2249 if ([ednode isParentWritable] == NO) 2250 { 2251 showAlertNoPermission([FSNode class], [ednode parentName]); 2252 CLEAREDITING; 2253 } 2254 else if ([ednode isSubnodeOfPath: [desktopApp trashPath]]) 2255 { 2256 showAlertInRecycler([FSNode class]); 2257 CLEAREDITING; 2258 } 2259 else 2260 { 2261 NSString *newname = [nameEditor stringValue]; 2262 NSString *newpath = [[ednode parentPath] stringByAppendingPathComponent: newname]; 2263 NSString *extension = [newpath pathExtension]; 2264 NSCharacterSet *notAllowSet = [NSCharacterSet characterSetWithCharactersInString: @"/\\*:?\33"]; 2265 NSRange range = [newname rangeOfCharacterFromSet: notAllowSet]; 2266 NSArray *dirContents = [ednode subNodeNamesOfParent]; 2267 NSMutableDictionary *opinfo = [NSMutableDictionary dictionary]; 2268 2269 if (([newname length] == 0) || (range.length > 0)) 2270 { 2271 showAlertInvalidName([FSNode class]); 2272 CLEAREDITING; 2273 } 2274 2275 if (([extension length] 2276 && ([ednode isDirectory] && ([ednode isPackage] == NO)))) 2277 { 2278 if (showAlertExtensionChange([FSNode class], extension) == NSAlertDefaultReturn) 2279 { 2280 CLEAREDITING; 2281 } 2282 } 2283 2284 if ([dirContents containsObject: newname]) 2285 { 2286 if ([newname isEqual: [ednode name]]) 2287 { 2288 CLEAREDITING; 2289 } 2290 else 2291 { 2292 showAlertNameInUse([FSNode class], newname); 2293 CLEAREDITING; 2294 } 2295 } 2296 2297 [opinfo setObject: @"GWorkspaceRenameOperation" forKey: @"operation"]; 2298 [opinfo setObject: [ednode path] forKey: @"source"]; 2299 [opinfo setObject: newpath forKey: @"destination"]; 2300 [opinfo setObject: [NSArray arrayWithObject: @""] forKey: @"files"]; 2301 2302 [self stopRepNameEditing]; 2303 [desktopApp performFileOperation: opinfo]; 2304 } 2305} 2306 2307@end 2308 2309 2310 2311 2312 2313 2314 2315 2316 2317 2318 2319 2320 2321 2322 2323 2324 2325 2326 2327 2328 2329 2330