1/* DockIcon.m 2 * 3 * Copyright (C) 2005-2014 Free Software Foundation, Inc. 4 * 5 * Author: Enrico Sersale <enrico@imago.ro> 6 * Date: January 2005 7 * 8 * This file is part of the GNUstep GWorkspace application 9 * 10 * This program is free software; you can redistribute it and/or modify 11 * it under the terms of the GNU General Public License as published by 12 * the Free Software Foundation; either version 2 of the License, or 13 * (at your option) any later version. 14 * 15 * This program is distributed in the hope that it will be useful, 16 * but WITHOUT ANY WARRANTY; without even the implied warranty of 17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18 * GNU General Public License for more details. 19 * 20 * You should have received a copy of the GNU General Public License 21 * along with this program; if not, write to the Free Software 22 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111 USA. 23 */ 24 25#include <math.h> 26#include <unistd.h> 27 28#import <Foundation/Foundation.h> 29#import <AppKit/AppKit.h> 30 31#import "DockIcon.h" 32#import "Dock.h" 33#import "GWDesktopManager.h" 34#import "GWorkspace.h" 35 36@implementation DockIcon 37 38- (void)dealloc 39{ 40 RELEASE (appName); 41 RELEASE (highlightColor); 42 RELEASE (darkerColor); 43 RELEASE (highlightImage); 44 RELEASE (trashFullIcon); 45 RELEASE (dragIcon); 46 47 [super dealloc]; 48} 49 50- (id)initForNode:(FSNode *)anode 51 appName:(NSString *)aname 52 iconSize:(int)isize 53{ 54 self = [super initForNode: anode 55 nodeInfoType: FSNInfoNameType 56 extendedType: nil 57 iconSize: isize 58 iconPosition: NSImageOnly 59 labelFont: [NSFont systemFontOfSize: 12] 60 textColor: [NSColor controlTextColor] 61 gridIndex: 0 62 dndSource: NO 63 acceptDnd: NO 64 slideBack: NO]; 65 66 if (self) { 67 if (aname != nil) { 68 ASSIGN (appName, aname); 69 } else { 70 ASSIGN (appName, [[node name] stringByDeletingPathExtension]); 71 } 72 73 dragIcon = [icon copy]; 74 75 docked = NO; 76 launched = NO; 77 apphidden = NO; 78 79 nc = [NSNotificationCenter defaultCenter]; 80 fm = [NSFileManager defaultManager]; 81 ws = [NSWorkspace sharedWorkspace]; 82 83 [self setToolTip: appName]; 84 } 85 86 return self; 87} 88 89- (NSString *)appName 90{ 91 return appName; 92} 93 94- (void)setWsIcon:(BOOL)value 95{ 96 isWsIcon = value; 97 if (isWsIcon) { 98 [self removeAllToolTips]; 99 } 100} 101 102- (BOOL)isWsIcon 103{ 104 return isWsIcon; 105} 106 107- (void)setTrashIcon:(BOOL)value 108{ 109 if (value != isTrashIcon) { 110 isTrashIcon = value; 111 112 if (isTrashIcon) { 113 NSArray *subNodes; 114 NSUInteger i, count; 115 116 ASSIGN (icon, [fsnodeRep trashIconOfSize: ceil(icnBounds.size.width)]); 117 ASSIGN (trashFullIcon, [fsnodeRep trashFullIconOfSize: ceil(icnBounds.size.width)]); 118 119 subNodes = [node subNodes]; 120 count = [subNodes count]; 121 122 for (i = 0; i < [subNodes count]; i++) { 123 if ([[subNodes objectAtIndex: i] isReserved]) { 124 count --; 125 } 126 } 127 128 [self setTrashFull: !(count == 0)]; 129 130 } else { 131 ASSIGN (icon, [fsnodeRep iconOfSize: ceil(icnBounds.size.width) 132 forNode: node]); 133 } 134 } 135 136 if (isTrashIcon) { 137 [self removeAllToolTips]; 138 } 139} 140 141- (void)setTrashFull:(BOOL)value 142{ 143 trashFull = value; 144 [self setNeedsDisplay: YES]; 145} 146 147- (BOOL)isTrashIcon 148{ 149 return isTrashIcon; 150} 151 152- (BOOL)isSpecialIcon 153{ 154 return (isWsIcon || isTrashIcon); 155} 156 157- (void)setDocked:(BOOL)value 158{ 159 docked = value; 160} 161 162- (BOOL)isDocked 163{ 164 return docked; 165} 166 167- (void)setLaunched:(BOOL)value 168{ 169 launched = value; 170 [self setNeedsDisplay: YES]; 171} 172 173- (BOOL)isLaunched 174{ 175 return launched; 176} 177 178- (void)setAppHidden:(BOOL)value 179{ 180 apphidden = value; 181 [self setNeedsDisplay: YES]; 182 [container setNeedsDisplayInRect: [self frame]]; 183} 184 185- (BOOL)isAppHidden 186{ 187 return apphidden; 188} 189 190- (void)animateLaunch 191{ 192 launching = YES; 193 dissFract = 0.2; 194 195 while (1) { 196 NSDate *date = [NSDate dateWithTimeIntervalSinceNow: 0.02]; 197 [[NSRunLoop currentRunLoop] runUntilDate: date]; 198 [self display]; 199 200 dissFract += 0.05; 201 if (dissFract >= 1) { 202 launching = NO; 203 break; 204 } 205 } 206 207 [self setNeedsDisplay: YES]; 208} 209 210- (void)setHighlightColor:(NSColor *)color 211{ 212 ASSIGN (highlightColor, [color highlightWithLevel: 0.2]); 213 ASSIGN (darkerColor, [color shadowWithLevel: 0.4]); 214} 215 216- (void)setHighlightImage:(NSImage *)image 217{ 218 DESTROY (highlightImage); 219 220 if (image) { 221 NSSize size = [self frame].size; 222 223 highlightImage = [[NSImage alloc] initWithSize: size]; 224 [highlightImage lockFocus]; 225 [image compositeToPoint: NSZeroPoint 226 fromRect: [self frame] 227 operation: NSCompositeCopy]; 228 [highlightImage unlockFocus]; 229 } 230} 231 232- (void)setUseHlightImage:(BOOL)value 233{ 234 useHligtImage = value; 235} 236 237- (void)setIsDndSourceIcon:(BOOL)value 238{ 239 if (isDndSourceIcon != value) { 240 isDndSourceIcon = value; 241 [self setNeedsDisplay: YES]; 242 } 243} 244 245- (void)setIconSize:(int)isize 246{ 247 icnBounds = NSMakeRect(0, 0, isize, isize); 248 if (isTrashIcon) { 249 ASSIGN (icon, [fsnodeRep trashIconOfSize: ceil(icnBounds.size.width)]); 250 ASSIGN (trashFullIcon, [fsnodeRep trashFullIconOfSize: ceil(icnBounds.size.width)]); 251 } else { 252 ASSIGN (icon, [fsnodeRep iconOfSize: ceil(icnBounds.size.width) 253 forNode: node]); 254 } 255 hlightRect.size.width = ceil(isize / 3 * 4); 256 hlightRect.size.height = ceil(hlightRect.size.width * [fsnodeRep highlightHeightFactor]); 257 if ((hlightRect.size.height - isize) < 4) { 258 hlightRect.size.height = isize + 4; 259 } 260 hlightRect.origin.x = 0; 261 hlightRect.origin.y = 0; 262 ASSIGN (highlightPath, [fsnodeRep highlightPathOfSize: hlightRect.size]); 263 [self tile]; 264} 265 266- (void)mouseUp:(NSEvent *)theEvent 267{ 268 if ([theEvent clickCount] > 1) { 269 if ([self isSpecialIcon] == NO) { 270 if (launched == NO) { 271 [ws launchApplication: appName]; 272 } else if (apphidden) { 273 [[GWorkspace gworkspace] unhideAppWithPath: [node path] andName: appName]; 274 } else { 275 [[GWorkspace gworkspace] activateAppWithPath: [node path] andName: appName]; 276 } 277 } else if (isWsIcon) { 278 [[GWDesktopManager desktopManager] showRootViewer]; 279 280 } else if (isTrashIcon) { 281 NSString *path = [node path]; 282 [[GWDesktopManager desktopManager] selectFile: path inFileViewerRootedAtPath: path]; 283 } 284 } 285} 286 287- (void)mouseDown:(NSEvent *)theEvent 288{ 289 NSEvent *nextEvent = nil; 290 BOOL startdnd = NO; 291 292 if ([theEvent clickCount] == 1) { 293 [self select]; 294 295 dragdelay = 0; 296 [(Dock *)container setDndSourceIcon: nil]; 297 298 while (1) { 299 nextEvent = [[self window] nextEventMatchingMask: 300 NSLeftMouseUpMask | NSLeftMouseDraggedMask]; 301 302 if ([nextEvent type] == NSLeftMouseUp) { 303 [[self window] postEvent: nextEvent atStart: NO]; 304 [self unselect]; 305 break; 306 307 } else if (([nextEvent type] == NSLeftMouseDragged) 308 && ([self isSpecialIcon] == NO)) { 309 if (dragdelay < 5) { 310 dragdelay++; 311 } else { 312 startdnd = YES; 313 break; 314 } 315 } 316 } 317 318 if (startdnd == YES) { 319 [self startExternalDragOnEvent: theEvent withMouseOffset: NSZeroSize]; 320 } 321 } 322} 323 324- (NSMenu *)menuForEvent:(NSEvent *)theEvent 325{ 326 if ([self isSpecialIcon] == NO) { 327 NSString *appPath = [ws fullPathForApplication: appName]; 328 329 if (appPath) { 330 CREATE_AUTORELEASE_POOL(arp); 331 NSMenu *menu = [[NSMenu alloc] initWithTitle: appName]; 332 NSMenuItem *item; 333 GWLaunchedApp *app; 334 335 item = [NSMenuItem new]; 336 [item setTitle: NSLocalizedString(@"Show In File Viewer", @"")]; 337 [item setTarget: (Dock *)container]; 338 [item setAction: @selector(iconMenuAction:)]; 339 [item setRepresentedObject: appPath]; 340 [menu addItem: item]; 341 RELEASE (item); 342 343 app = [[GWorkspace gworkspace] launchedAppWithPath: appPath 344 andName: appName]; 345 if (app && [app isRunning]) { 346 item = [NSMenuItem new]; 347 [item setTarget: (Dock *)container]; 348 [item setAction: @selector(iconMenuAction:)]; 349 [item setRepresentedObject: app]; 350 351 if ([app isHidden]) { 352 [item setTitle: NSLocalizedString(@"Unhide", @"")]; 353 } else { 354 [item setTitle: NSLocalizedString(@"Hide", @"")]; 355 } 356 357 [menu addItem: item]; 358 RELEASE (item); 359 360 item = [NSMenuItem new]; 361 [item setTitle: NSLocalizedString(@"Quit", @"")]; 362 [item setTarget: (Dock *)container]; 363 [item setAction: @selector(iconMenuAction:)]; 364 [item setRepresentedObject: app]; 365 [menu addItem: item]; 366 RELEASE (item); 367 } 368 369 RELEASE (arp); 370 371 return AUTORELEASE (menu); 372 } 373 } 374 375 return [super menuForEvent: theEvent]; 376} 377 378- (void)startExternalDragOnEvent:(NSEvent *)event 379 withMouseOffset:(NSSize)offset 380{ 381 NSPasteboard *pb = [NSPasteboard pasteboardWithName: NSDragPboard]; 382 NSMutableDictionary *dict = [NSMutableDictionary dictionary]; 383 384 [dict setObject: appName forKey: @"name"]; 385 [dict setObject: [node path] forKey: @"path"]; 386 [dict setObject: [NSNumber numberWithBool: docked] 387 forKey: @"docked"]; 388 [dict setObject: [NSNumber numberWithBool: launched] 389 forKey: @"launched"]; 390 [dict setObject: [NSNumber numberWithBool: apphidden] 391 forKey: @"hidden"]; 392 393 [pb declareTypes: [NSArray arrayWithObject: @"DockIconPboardType"] 394 owner: nil]; 395 396 if ([pb setData: [NSArchiver archivedDataWithRootObject: dict] 397 forType: @"DockIconPboardType"]) { 398 NSPoint dragPoint = [event locationInWindow]; 399 NSSize fs = [self frame].size; 400 401 dragPoint.x -= ((fs.width - icnPoint.x) / 2); 402 dragPoint.y -= ((fs.height - icnPoint.y) / 2); 403 404 [self unselect]; 405 [self setIsDndSourceIcon: YES]; 406 [(Dock *)container setDndSourceIcon: self]; 407 [(Dock *)container tile]; 408 409 [[self window] dragImage: dragIcon 410 at: dragPoint 411 offset: NSZeroSize 412 event: event 413 pasteboard: pb 414 source: self 415 slideBack: NO]; 416 } 417} 418 419- (void)draggedImage:(NSImage *)anImage 420 endedAt:(NSPoint)aPoint 421 deposited:(BOOL)flag 422{ 423 dragdelay = 0; 424 [self setIsDndSourceIcon: NO]; 425 [(Dock *)container setDndSourceIcon: nil]; 426} 427 428- (void)drawRect:(NSRect)rect 429{ 430#define DRAWDOT(c1, c2, p) \ 431{ \ 432[c1 set]; \ 433NSRectFill(NSMakeRect(p.x, p.y, 3, 2)); \ 434[c2 set]; \ 435NSRectFill(NSMakeRect(p.x + 1, p.y, 2, 1)); \ 436NSRectFill(NSMakeRect(p.x + 2, p.y + 1, 1, 1)); \ 437} 438 439#define DRAWDOTS(c1, c2, p) \ 440{ \ 441int i, x = p.x, y = p.y; \ 442for (i = 0; i < 3; i++) { \ 443[c1 set]; \ 444NSRectFill(NSMakeRect(x, y, 3, 2)); \ 445[c2 set]; \ 446NSRectFill(NSMakeRect(x + 1, y, 2, 1)); \ 447NSRectFill(NSMakeRect(x + 2, y + 1, 1, 1)); \ 448x += 6; \ 449} \ 450} 451 452 if (isSelected || launching) { 453 [highlightColor set]; 454 NSRectFill(rect); 455 456 if (highlightImage && useHligtImage) { 457 [highlightImage dissolveToPoint: NSZeroPoint fraction: 0.2]; 458 } 459 } 460 461 if (launching) { 462 [icon dissolveToPoint: icnPoint fraction: dissFract]; 463 return; 464 } 465 466 if (isDndSourceIcon == NO) { 467 if (isTrashIcon == NO) { 468 [icon compositeToPoint: icnPoint operation: NSCompositeSourceOver]; 469 } else { 470 if (trashFull) { 471 [trashFullIcon compositeToPoint: icnPoint operation: NSCompositeSourceOver]; 472 } else { 473 [icon compositeToPoint: icnPoint operation: NSCompositeSourceOver]; 474 } 475 } 476 477 if ((isWsIcon == NO) && (isTrashIcon == NO)) { 478 if (apphidden) { 479 DRAWDOT (darkerColor, [NSColor whiteColor], NSMakePoint(4, 2)); 480 } else if (launched == NO) { 481 DRAWDOTS (darkerColor, [NSColor whiteColor], NSMakePoint(4, 2)); 482 } 483 } 484 } 485} 486 487- (BOOL)acceptsDraggedPaths:(NSArray *)paths 488{ 489 unsigned i; 490 491 if ([self isSpecialIcon] == NO) { 492 for (i = 0; i < [paths count]; i++) { 493 NSString *path = [paths objectAtIndex: i]; 494 FSNode *nod = [FSNode nodeWithPath: path]; 495 496 if (([nod isPlain] || ([nod isPackage] && ([nod isApplication] == NO))) == NO) { 497 return NO; 498 } 499 } 500 501 [self select]; 502 return YES; 503 504 } else if (isTrashIcon) { 505 NSString *fromPath = [[paths objectAtIndex: 0] stringByDeletingLastPathComponent]; 506 BOOL accept = YES; 507 508 if ([fromPath isEqual: [[GWDesktopManager desktopManager] trashPath]] == NO) { 509 NSArray *vpaths = [ws mountedLocalVolumePaths]; 510 511 for (i = 0; i < [paths count]; i++) { 512 NSString *path = [paths objectAtIndex: i]; 513 514 if (([vpaths containsObject: path] == NO) 515 && ([fm isWritableFileAtPath: path] == NO)) { 516 accept = NO; 517 break; 518 } 519 } 520 } else { 521 accept = NO; 522 } 523 524 if (accept) { 525 [self select]; 526 } 527 528 return accept; 529 } 530 531 return NO; 532} 533 534- (void)setDraggedPaths:(NSArray *)paths 535{ 536 NSUInteger i; 537 538 [self unselect]; 539 540 if ([self isSpecialIcon] == NO) 541 { 542 for (i = 0; i < [paths count]; i++) 543 { 544 NSString *path = [paths objectAtIndex: i]; 545 FSNode *nod = [FSNode nodeWithPath: path]; 546 547 if ([nod isPlain] || ([nod isPackage] && ([nod isApplication] == NO))) 548 { 549 NS_DURING 550 { 551 [ws openFile: path withApplication: appName]; 552 } 553 NS_HANDLER 554 { 555 NSRunAlertPanel(NSLocalizedString(@"error", @""), 556 [NSString stringWithFormat: @"%@ %@!", 557 NSLocalizedString(@"Can't open ", @""), [path lastPathComponent]], 558 NSLocalizedString(@"OK", @""), 559 nil, 560 nil); 561 } 562 NS_ENDHANDLER 563 } 564 } 565 } 566 else if (isTrashIcon) // FIXME this is largely similar to RecyclerIcon #### 567 { 568 NSArray *vpaths = [ws mountedLocalVolumePaths]; 569 NSMutableArray *files = [NSMutableArray array]; 570 NSMutableArray *umountPaths = [NSMutableArray array]; 571 NSMutableDictionary *opinfo = [NSMutableDictionary dictionary]; 572 573 for (i = 0; i < [paths count]; i++) 574 { 575 NSString *srcpath = [paths objectAtIndex: i]; 576 577 if ([vpaths containsObject: srcpath]) 578 { 579 [umountPaths addObject: srcpath]; 580 } 581 else 582 { 583 [files addObject: [srcpath lastPathComponent]]; 584 } 585 } 586 587 for (i = 0; i < [umountPaths count]; i++) 588 { 589 NSString *umpath = [umountPaths objectAtIndex: i]; 590 591 if (![ws unmountAndEjectDeviceAtPath: umpath]) 592 { 593 NSString *err = NSLocalizedString(@"Error", @""); 594 NSString *msg = NSLocalizedString(@"You are not allowed to umount\n", @""); 595 NSString *buttstr = NSLocalizedString(@"Continue", @""); 596 NSRunAlertPanel(err, [NSString stringWithFormat: @"%@ \"%@\"!\n", msg, umpath], buttstr, nil, nil); 597 [[GWDesktopManager desktopManager] unlockVolumeAtPath:umpath]; 598 } 599 } 600 601 if ([files count]) 602 { 603 NSString *fromPath = [[paths objectAtIndex: 0] stringByDeletingLastPathComponent]; 604 605 if ([fm isWritableFileAtPath: fromPath] == NO) { 606 NSString *err = NSLocalizedString(@"Error", @""); 607 NSString *msg = NSLocalizedString(@"You do not have write permission\nfor", @""); 608 NSString *buttstr = NSLocalizedString(@"Continue", @""); 609 NSRunAlertPanel(err, [NSString stringWithFormat: @"%@ \"%@\"!\n", msg, fromPath], buttstr, nil, nil); 610 return; 611 } 612 613 [opinfo setObject: NSWorkspaceRecycleOperation forKey: @"operation"]; 614 [opinfo setObject: fromPath forKey: @"source"]; 615 [opinfo setObject: [node path] forKey: @"destination"]; 616 [opinfo setObject: files forKey: @"files"]; 617 618 [[GWDesktopManager desktopManager] performFileOperation: opinfo]; 619 } 620 } 621} 622 623@end 624 625