source: trunk/macosx/Controller.m @ 168

Last change on this file since 168 was 168, checked in by titer, 16 years ago

Set click point before displaying so that mouse up on a button instantly
deselects it.
Store application icon when first loaded so it doesn't have badges on it.
Move a few things into the nib file
Put ellipis at end of name / tracker / announce if it doesn't fit

File size: 36.0 KB
Line 
1/******************************************************************************
2 * Copyright (c) 2005-2006 Transmission authors and contributors
3 *
4 * Permission is hereby granted, free of charge, to any person obtaining a
5 * copy of this software and associated documentation files (the "Software"),
6 * to deal in the Software without restriction, including without limitation
7 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
8 * and/or sell copies of the Software, and to permit persons to whom the
9 * Software is furnished to do so, subject to the following conditions:
10 *
11 * The above copyright notice and this permission notice shall be included in
12 * all copies or substantial portions of the Software.
13 *
14 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
19 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
20 * DEALINGS IN THE SOFTWARE.
21 *****************************************************************************/
22
23#import <IOKit/IOMessage.h>
24
25#import "Controller.h"
26#import "Torrent.h"
27#import "TorrentCell.h"
28#import "StringAdditions.h"
29#import "Utils.h"
30#import "TorrentTableView.h"
31
32#import "PrefsController.h"
33
34#define TOOLBAR_OPEN        @"Toolbar Open"
35#define TOOLBAR_REMOVE      @"Toolbar Remove"
36#define TOOLBAR_INFO        @"Toolbar Info"
37#define TOOLBAR_PAUSE_ALL   @"Toolbar Pause All"
38#define TOOLBAR_RESUME_ALL  @"Toolbar Resume All"
39
40#define WEBSITE_URL         @"http://transmission.m0k.org/"
41#define FORUM_URL           @"http://transmission.m0k.org/forum/"
42#define VERSION_PLIST_URL   @"http://transmission.m0k.org/version.plist"
43
44#define GROWL_PATH  @"/Library/PreferencePanes/Growl.prefPane/Contents/Resources/GrowlHelperApp.app"
45
46static void sleepCallBack( void * controller, io_service_t y,
47        natural_t messageType, void * messageArgument )
48{
49    Controller * c = controller;
50    [c sleepCallBack: messageType argument: messageArgument];
51}
52
53
54@implementation Controller
55
56- (id) init
57{
58    fLib      = tr_init();
59    fTorrents = [[NSMutableArray alloc] initWithCapacity: 10];
60    return self;
61}
62
63- (void) dealloc
64{
65    [fTorrents release];
66    tr_close( fLib );
67    [fAppIcon release];
68    [super dealloc];
69}
70
71- (void) awakeFromNib
72{
73    fAppIcon = [[NSApp applicationIconImage] copy];
74
75    [fPrefsController setPrefsWindow: fLib];
76    fDefaults = [NSUserDefaults standardUserDefaults];
77
78    //check advanced bar menu item
79    [fAdvancedBarItem setState: [fDefaults
80        boolForKey:@"UseAdvancedBar"] ? NSOnState : NSOffState];
81
82    fToolbar = [[NSToolbar alloc] initWithIdentifier: @"Transmission Toolbar"];
83    [fToolbar setDelegate: self];
84    [fToolbar setAllowsUserCustomization: YES];
85    [fToolbar setAutosavesConfiguration: YES];
86    [fWindow setToolbar: fToolbar];
87    [fWindow setDelegate: self];
88
89    [fTableView setTorrents: fTorrents];
90    [[fTableView tableColumnWithIdentifier: @"Torrent"] setDataCell:
91        [[TorrentCell alloc] init]];
92
93    [fTableView registerForDraggedTypes: [NSArray arrayWithObjects:
94        NSFilenamesPboardType, NULL]];
95
96    //Register for sleep notifications
97    IONotificationPortRef  notify;
98    io_object_t            anIterator;
99
100    fRootPort = IORegisterForSystemPower( self, & notify, sleepCallBack,
101                                          & anIterator);
102    if (fRootPort)
103    {
104        CFRunLoopAddSource( CFRunLoopGetCurrent(),
105                            IONotificationPortGetRunLoopSource( notify ),
106                            kCFRunLoopCommonModes );
107    }
108    else
109        printf( "Could not IORegisterForSystemPower\n" );
110
111    NSString * torrentPath, * downloadFolder, * paused;
112    NSDictionary * dic;
113
114    Torrent * torrent;
115    NSEnumerator * enumerator = [[fDefaults arrayForKey: @"History"] objectEnumerator];
116    while ((dic = [enumerator nextObject]))
117    {
118        torrentPath    = [dic objectForKey: @"TorrentPath"];
119        downloadFolder = [dic objectForKey: @"DownloadFolder"];
120        paused         = [dic objectForKey: @"Paused"];
121
122        if (!torrentPath || !downloadFolder || !paused)
123            continue;
124
125        torrent = [[Torrent alloc] initWithPath: torrentPath lib: fLib];
126        if( !torrent )
127            continue;
128
129        [fTorrents addObject: torrent];
130
131        [torrent setFolder: downloadFolder];
132
133        if ([paused isEqualToString: @"NO"])
134            [torrent start];
135    }
136
137    //check and register Growl if it is installed for this user or all users
138    NSFileManager * manager = [NSFileManager defaultManager];
139    fHasGrowl = [manager fileExistsAtPath: GROWL_PATH]
140                || [manager fileExistsAtPath: [[NSString stringWithFormat: @"~%@",
141                GROWL_PATH] stringByExpandingTildeInPath]];
142    [self growlRegister: self];
143
144    //initialize badging
145    fBadger = [[Badger alloc] init];
146
147    //update the interface every 500 ms
148    fDownloading = 0;
149    fSeeding = 0;
150    fCompleted = 0;
151    [self updateUI: nil];
152    fTimer = [NSTimer scheduledTimerWithTimeInterval: 1.0 target: self
153        selector: @selector( updateUI: ) userInfo: nil repeats: YES];
154    [[NSRunLoop currentRunLoop] addTimer: fTimer
155        forMode: NSModalPanelRunLoopMode];
156    [[NSRunLoop currentRunLoop] addTimer: fTimer
157        forMode: NSEventTrackingRunLoopMode];
158
159    [self checkForUpdateTimer: nil];
160    fUpdateTimer = [NSTimer scheduledTimerWithTimeInterval: 60.0
161        target: self selector: @selector( checkForUpdateTimer: )
162        userInfo: nil repeats: YES];
163}
164
165- (void) windowDidBecomeKey: (NSNotification *) n
166{
167    /* Reset the number of recently completed downloads */
168    fCompleted = 0;
169}
170
171- (void) windowDidResize: (NSNotification *) n
172{
173    [fTableView sizeToFit];
174}
175
176- (BOOL) windowShouldClose: (id) sender
177{
178    [fWindow orderOut: nil];
179    return NO;
180}
181
182- (BOOL) applicationShouldHandleReopen: (NSApplication *) app
183    hasVisibleWindows: (BOOL) flag
184{
185    [self showMainWindow: nil];
186    return NO;
187}
188
189- (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender
190{
191    int active = 0;
192    Torrent * torrent;
193    NSEnumerator * enumerator = [fTorrents objectEnumerator];
194    while( ( torrent = [enumerator nextObject] ) )
195    {
196        if( [torrent isActive] )
197        {
198            active++;
199        }
200    }
201
202    if (active > 0 && [fDefaults boolForKey: @"CheckQuit"])
203    {
204        NSString * message = active == 1
205            ? @"There is an active torrent. Do you really want to quit?"
206            : [NSString stringWithFormat:
207                @"There are %d active torrents. Do you really want to quit?",
208                active];
209
210        NSBeginAlertSheet(@"Confirm Quit",
211                            @"Quit", @"Cancel", nil,
212                            fWindow, self,
213                            @selector(quitSheetDidEnd:returnCode:contextInfo:),
214                            nil, nil, message);
215        return NSTerminateLater;
216    }
217
218    return NSTerminateNow;
219}
220
221- (void) quitSheetDidEnd:(NSWindow *)sheet returnCode:(int)returnCode
222                        contextInfo:(void  *)contextInfo
223{
224    [NSApp stopModal];
225    [NSApp replyToApplicationShouldTerminate:
226        (returnCode == NSAlertDefaultReturn)];
227}
228
229- (void) applicationWillTerminate: (NSNotification *) notification
230{
231    NSEnumerator * enumerator;
232    Torrent * torrent;
233
234    // Stop updating the interface
235    [fTimer invalidate];
236    [fUpdateTimer invalidate];
237
238    //clear badge
239    [fBadger clearBadge];
240    [fBadger release];
241
242    // Save history
243    [self updateTorrentHistory];
244
245    // Stop running torrents
246    enumerator = [fTorrents objectEnumerator];
247    while( ( torrent = [enumerator nextObject] ) )
248    {
249        [torrent stop];
250    }
251
252    // Wait for torrents to stop (5 seconds timeout)
253    NSDate * start = [NSDate date];
254    while( [fTorrents count] )
255    {
256        torrent = [fTorrents objectAtIndex: 0];
257        while( [[NSDate date] timeIntervalSinceDate: start] < 5 &&
258                ![torrent isPaused] )
259        {
260            usleep( 100000 );
261            [torrent update];
262        }
263        [fTorrents removeObject: torrent];
264        [torrent release];
265    }
266}
267
268- (void) showPreferenceWindow: (id) sender
269{
270    //place the window if not open
271    if (![fPrefsWindow isVisible])
272    {
273        [fPrefsWindow center];
274    }
275
276    [fPrefsWindow makeKeyAndOrderFront:NULL];
277}
278
279- (void) folderChoiceClosed: (NSOpenPanel *) s returnCode: (int) code
280    contextInfo: (void *) info
281{
282    Torrent * torrent = [fTorrents lastObject];
283    if (code == NSOKButton)
284    {
285        [torrent setFolder: [[s filenames] objectAtIndex: 0]];
286        [torrent start];
287    }
288    else
289    {
290        [torrent release];
291    }
292    [NSApp stopModal];
293}
294
295- (void) application: (NSApplication *) sender
296         openFiles: (NSArray *) filenames
297{
298    NSString * downloadChoice, * downloadFolder, * torrentPath;
299    Torrent * torrent;
300
301    downloadChoice = [fDefaults stringForKey: @"DownloadChoice"];
302    downloadFolder = [fDefaults stringForKey: @"DownloadFolder"];
303
304    NSEnumerator * enumerator = [filenames objectEnumerator];
305    while ((torrentPath = [enumerator nextObject]))
306    {
307        torrent = [[Torrent alloc] initWithPath: torrentPath lib: fLib];
308        if( !torrent )
309            continue;
310        [fTorrents addObject: torrent];
311
312        /* Add it to the "File > Open Recent" menu */
313        [[NSDocumentController sharedDocumentController]
314            noteNewRecentDocumentURL: [NSURL fileURLWithPath: torrentPath]];
315
316        if( [downloadChoice isEqualToString: @"Constant"] )
317        {
318            [torrent setFolder: [downloadFolder stringByExpandingTildeInPath]];
319            [torrent start];
320        }
321        else if( [downloadChoice isEqualToString: @"Torrent"] )
322        {
323            [torrent setFolder: [torrentPath stringByDeletingLastPathComponent]];
324            [torrent start];
325        }
326        else
327        {
328            NSOpenPanel * panel = [NSOpenPanel openPanel];
329
330            [panel setPrompt: @"Select Download Folder"];
331            [panel setMessage: [NSString stringWithFormat:
332                                @"Select the download folder for %@",
333                                [torrentPath lastPathComponent]]];
334            [panel setAllowsMultipleSelection: NO];
335            [panel setCanChooseFiles: NO];
336            [panel setCanChooseDirectories: YES];
337
338            [panel beginSheetForDirectory: NULL file: NULL types: NULL
339                modalForWindow: fWindow modalDelegate: self didEndSelector:
340                @selector( folderChoiceClosed:returnCode:contextInfo: )
341                contextInfo: NULL];
342            [NSApp runModalForWindow: panel];
343        }
344    }
345
346    [self updateUI: NULL];
347    [self updateTorrentHistory];
348}
349
350- (void) advancedChanged: (id) sender
351{
352    [fAdvancedBarItem setState: ![fAdvancedBarItem state]];
353    [fDefaults setObject: [fAdvancedBarItem state] == NSOffState ? @"NO" : @"YES"
354                forKey:@"UseAdvancedBar"];
355
356    [fTableView display];
357}
358
359//called on by applescript
360- (void) open: (NSArray *) files
361{
362    fFilenames = [files retain];
363    [self performSelectorOnMainThread: @selector(cantFindAName:)
364                withObject: NULL waitUntilDone: NO];
365}
366
367- (void) openShowSheet: (id) sender
368{
369    NSOpenPanel * panel;
370    NSArray     * fileTypes;
371
372    panel     = [NSOpenPanel openPanel];
373    fileTypes = [NSArray arrayWithObject: @"torrent"];
374
375    [panel setAllowsMultipleSelection: YES];
376    [panel setCanChooseFiles:          YES];
377    [panel setCanChooseDirectories:    NO];
378
379    [panel beginSheetForDirectory: NULL file: NULL types: fileTypes
380        modalForWindow: fWindow modalDelegate: self didEndSelector:
381        @selector( openSheetClosed:returnCode:contextInfo: )
382        contextInfo: NULL];
383}
384
385- (void) cantFindAName: (id) sender
386{
387    [self application: NSApp openFiles: fFilenames];
388    [fFilenames release];
389}
390
391- (void) openSheetClosed: (NSOpenPanel *) s returnCode: (int) code
392    contextInfo: (void *) info
393{
394    if( code != NSOKButton )
395    {
396        return;
397    }
398
399    fFilenames = [[s filenames] retain];
400
401    [self performSelectorOnMainThread: @selector(cantFindAName:)
402                withObject: NULL waitUntilDone: NO];
403}
404
405- (void) resumeTorrent: (id) sender
406{
407    [self resumeTorrentWithIndex: [fTableView selectedRow]];
408}
409
410- (void) resumeAllTorrents: (id) sender
411{
412    Torrent * torrent;
413    NSEnumerator * enumerator = [fTorrents objectEnumerator];
414    while( ( torrent = [enumerator nextObject] ) )
415    {
416        [torrent start];
417    }
418    [self updateUI: nil];
419    [self updateTorrentHistory];
420}
421
422- (void) resumeTorrentWithIndex: (int) idx
423{
424    Torrent * torrent = [fTorrents objectAtIndex: idx];
425    [torrent start];
426    [self updateUI: nil];
427    [self updateTorrentHistory];
428}
429
430- (void) stopTorrent: (id) sender
431{
432    [self stopTorrentWithIndex: [fTableView selectedRow]];
433}
434
435- (void) stopAllTorrents: (id) sender
436{
437    Torrent * torrent;
438    NSEnumerator * enumerator = [fTorrents objectEnumerator];
439    while( ( torrent = [enumerator nextObject] ) )
440    {
441        [torrent stop];
442    }
443    [self updateUI: nil];
444    [self updateTorrentHistory];
445}
446
447- (void) stopTorrentWithIndex: (int) idx
448{
449    Torrent * torrent = [fTorrents objectAtIndex: idx];
450    [torrent stop];
451    [self updateUI: nil];
452    [self updateTorrentHistory];
453}
454
455- (void) removeTorrentWithIndex: (int) idx
456                  deleteTorrent: (BOOL) deleteTorrent
457                     deleteData: (BOOL) deleteData
458{
459    Torrent * torrent = [fTorrents objectAtIndex: idx];
460
461    if( [torrent isActive] && [fDefaults boolForKey: @"CheckRemove"] )
462    {
463        NSDictionary * dict = [NSDictionary dictionaryWithObjectsAndKeys:
464            [NSString stringWithInt: idx], @"Index",
465            [NSString stringWithInt: deleteTorrent], @"DeleteTorrent",
466            [NSString stringWithInt: deleteData], @"DeleteData",
467            nil];
468        [dict retain];
469
470        NSBeginAlertSheet(@"Confirm Remove",
471            @"Remove", @"Cancel", nil, fWindow, self,
472            @selector(removeSheetDidEnd:returnCode:contextInfo:),  NULL, dict,
473            @"This torrent is active. Do you really want to remove it?");
474    }
475    else
476    {
477        [self confirmRemoveTorrentWithIndex: idx
478                deleteTorrent: deleteTorrent
479                deleteData: deleteData];
480    }
481}
482
483- (void) removeSheetDidEnd:(NSWindow *)sheet returnCode:(int)returnCode
484                        contextInfo:(NSDictionary  *)dict
485{
486    [NSApp stopModal];
487
488    if( returnCode == NSAlertDefaultReturn )
489    {
490        int idx = [[dict objectForKey:@"Index"] intValue];
491
492        [self confirmRemoveTorrentWithIndex: idx
493            deleteTorrent: [[dict objectForKey:@"DeleteTorrent"] intValue]
494            deleteData: [[dict objectForKey:@"DeleteData"] intValue]];
495    }
496
497    [dict release];
498}
499
500- (void) confirmRemoveTorrentWithIndex: (int) idx
501            deleteTorrent: (BOOL) deleteTorrent
502            deleteData: (BOOL) deleteData
503{
504    Torrent * torrent = [fTorrents objectAtIndex: idx];
505
506    /* Pause if not paused already */
507    [torrent stop];
508
509    if( deleteData )
510    {
511        [torrent trashData];
512    }
513    if( deleteTorrent )
514    {
515        [torrent trashTorrent];
516    }
517
518    [fTorrents removeObject: torrent];
519    [torrent release];
520
521    [self updateUI: nil];
522    [self updateTorrentHistory];
523}
524
525- (void) removeTorrent: (id) sender
526{
527    [self removeTorrentWithIndex: [fTableView selectedRow] deleteTorrent: NO deleteData: NO ];
528}
529
530- (void) removeTorrentDeleteFile: (id) sender
531{
532    [self removeTorrentWithIndex: [fTableView selectedRow] deleteTorrent: YES deleteData: NO];
533}
534
535- (void) removeTorrentDeleteData: (id) sender
536{
537    [self removeTorrentWithIndex: [fTableView selectedRow] deleteTorrent: NO deleteData: YES];
538}
539
540- (void) removeTorrentDeleteBoth: (id) sender
541{
542    [self removeTorrentWithIndex: [fTableView selectedRow] deleteTorrent: YES deleteData: YES];
543}
544
545- (void) showInfo: (id) sender
546{
547    if( [fInfoPanel isVisible] )
548    {
549        [fInfoPanel close];
550    }
551    else
552    {
553        [fInfoPanel orderFront: sender];
554    }
555}
556
557- (void) updateInfoPanel
558{
559    int row = [fTableView selectedRow];
560
561    if( row < 0 )
562    {
563        [fInfoImageView setImage: fAppIcon];
564        [fInfoName setStringValue: @"No Torrent Selected"];
565        [fInfoSize setStringValue: @""];
566        [fInfoTracker setStringValue: @""];
567        [fInfoAnnounce setStringValue: @""];
568        [fInfoPieceSize setStringValue: @""];
569        [fInfoPieces setStringValue: @""];
570        [fInfoHash1 setStringValue: @""];
571        [fInfoHash2 setStringValue: @""];
572        [fInfoSeeders setStringValue: @""];
573        [fInfoLeechers setStringValue: @""];
574        [fInfoDownloaded setStringValue: @""];
575        [fInfoUploaded setStringValue: @""];
576        return;
577    }
578
579    Torrent * torrent = [fTorrents objectAtIndex: row];
580    [fInfoImageView setImage: [torrent iconNonFlipped]];
581    [fInfoName setStringValue: [torrent name]];
582    [fInfoSize setStringValue: [NSString
583        stringForFileSize: [torrent size]]];
584    [fInfoTracker setStringValue: [torrent tracker]];
585    [fInfoAnnounce setStringValue: [torrent announce]];
586    [fInfoPieceSize setStringValue: [NSString
587        stringForFileSize: [torrent pieceSize]]];
588    [fInfoPieces setIntValue: [torrent pieceCount]];
589    [fInfoHash1 setStringValue: [torrent hash1]];
590    [fInfoHash2 setStringValue: [torrent hash2]];
591    int seeders = [torrent seeders], leechers = [torrent leechers];
592    [fInfoSeeders setStringValue: seeders < 0 ?
593        @"?" : [NSString stringWithInt: seeders]];
594    [fInfoLeechers setStringValue: leechers < 0 ?
595        @"?" : [NSString stringWithInt: leechers]];
596    [fInfoDownloaded setStringValue: [NSString
597        stringForFileSize: [torrent downloaded]]];
598    [fInfoUploaded setStringValue: [NSString
599        stringForFileSize: [torrent uploaded]]];
600}
601
602- (void) updateUI: (NSTimer *) t
603{
604    float dl, ul;
605    NSEnumerator * enumerator;
606    Torrent * torrent;
607
608    /* Update the TableView */
609    enumerator = [fTorrents objectEnumerator];
610    while( ( torrent = [enumerator nextObject] ) )
611    {
612        [torrent update];
613
614        if( [torrent justFinished] )
615        {
616            /* Notifications */
617            [self notifyGrowl: [torrent name]];
618            if( ![fWindow isKeyWindow] )
619            {
620                fCompleted++;
621            }
622        }
623    }
624    [fTableView reloadData];
625
626    //Update the global DL/UL rates
627    tr_torrentRates( fLib, &dl, &ul );
628    NSString * downloadRate = [NSString stringForSpeed: dl];
629    NSString * uploadRate = [NSString stringForSpeed: ul];
630    [fTotalDLField setStringValue: downloadRate];
631    [fTotalULField setStringValue: uploadRate];
632
633    [self updateInfoPanel];
634
635    //badge dock
636    [fBadger updateBadgeWithCompleted: fCompleted
637        uploadRate: ul >= 0.1 && [fDefaults boolForKey: @"BadgeUploadRate"]
638          ? [NSString stringForSpeedAbbrev: ul] : nil
639        downloadRate: dl >= 0.1 && [fDefaults boolForKey: @"BadgeDownloadRate"]
640          ? [NSString stringForSpeedAbbrev: dl] : nil];
641}
642
643- (void) updateTorrentHistory
644{
645    NSMutableArray * history = [NSMutableArray
646        arrayWithCapacity: [fTorrents count]];
647
648    NSEnumerator * enumerator = [fTorrents objectEnumerator];
649    Torrent * torrent;
650    while( ( torrent = [enumerator nextObject] ) )
651    {
652        [history addObject: [NSDictionary dictionaryWithObjectsAndKeys:
653            [torrent path],                      @"TorrentPath",
654            [torrent getFolder],                 @"DownloadFolder",
655            [torrent isActive] ? @"NO" : @"YES", @"Paused",
656            nil]];
657    }
658
659    [fDefaults setObject: history forKey: @"History"];
660}
661
662- (int) numberOfRowsInTableView: (NSTableView *) t
663{
664    return [fTorrents count];
665}
666
667- (id) tableView: (NSTableView *) t objectValueForTableColumn:
668    (NSTableColumn *) tableColumn row: (int) rowIndex
669{
670    return nil;
671}
672
673
674- (void) tableView: (NSTableView *) t willDisplayCell: (id) cell
675    forTableColumn: (NSTableColumn *) tableColumn row: (int) rowIndex
676{
677    [cell setTorrent: [fTorrents objectAtIndex: rowIndex]];
678    [cell setTextColor: ( [fWindow isKeyWindow] &&
679        rowIndex == [fTableView selectedRow] ) ?
680        [NSColor whiteColor] : [NSColor blackColor]];
681}
682
683- (BOOL) tableView: (NSTableView *) t acceptDrop:
684    (id <NSDraggingInfo>) info row: (int) row dropOperation:
685    (NSTableViewDropOperation) operation
686{
687    [self application: NSApp openFiles: [[[info draggingPasteboard]
688        propertyListForType: NSFilenamesPboardType]
689        pathsMatchingExtensions: [NSArray arrayWithObject: @"torrent"]]];
690    return YES;
691}
692
693- (NSDragOperation) tableView: (NSTableView *) t validateDrop:
694    (id <NSDraggingInfo>) info proposedRow: (int) row
695    proposedDropOperation: (NSTableViewDropOperation) operation
696{
697    NSPasteboard * pasteboard = [info draggingPasteboard];
698
699    if (![[pasteboard types] containsObject: NSFilenamesPboardType]
700            || [[[pasteboard propertyListForType: NSFilenamesPboardType]
701                pathsMatchingExtensions: [NSArray arrayWithObject: @"torrent"]]
702                count] == 0)
703        return NSDragOperationNone;
704
705    [fTableView setDropRow: [fTorrents count]
706        dropOperation: NSTableViewDropAbove];
707    return NSDragOperationGeneric;
708}
709
710- (void) tableViewSelectionDidChange: (NSNotification *) n
711{
712    [self updateInfoPanel];
713}
714
715- (NSToolbarItem *) toolbar: (NSToolbar *) t itemForItemIdentifier:
716    (NSString *) ident willBeInsertedIntoToolbar: (BOOL) flag
717{
718    NSToolbarItem * item = [[NSToolbarItem alloc] initWithItemIdentifier: ident];
719
720    if( [ident isEqualToString: TOOLBAR_OPEN] )
721    {
722        [item setLabel: @"Open"];
723        [item setPaletteLabel: [item label]];
724        [item setToolTip: @"Open a torrent"];
725        [item setImage: [NSImage imageNamed: @"Open.png"]];
726        [item setTarget: self];
727        [item setAction: @selector( openShowSheet: )];
728    }
729    else if( [ident isEqualToString: TOOLBAR_REMOVE] )
730    {
731        [item setLabel: @"Remove"];
732        [item setPaletteLabel: [item label]];
733        [item setToolTip: @"Remove torrent from list"];
734        [item setImage: [NSImage imageNamed: @"Remove.png"]];
735        [item setTarget: self];
736        [item setAction: @selector( removeTorrent: )];
737    }
738    else if( [ident isEqualToString: TOOLBAR_INFO] )
739    {
740        [item setLabel: @"Info"];
741        [item setPaletteLabel: [item label]];
742        [item setToolTip: @"Information"];
743        [item setImage: [NSImage imageNamed: @"Info.png"]];
744        [item setTarget: self];
745        [item setAction: @selector( showInfo: )];
746    }
747    else if( [ident isEqualToString: TOOLBAR_RESUME_ALL] )
748    {
749        [item setLabel: @"Resume All"];
750        [item setPaletteLabel: [item label]];
751        [item setToolTip: @"Resume all torrents"];
752        [item setImage: [NSImage imageNamed: @"ResumeAll.png"]];
753        [item setTarget: self];
754        [item setAction: @selector( resumeAllTorrents: )];
755    }
756    else if( [ident isEqualToString: TOOLBAR_PAUSE_ALL] )
757    {
758        [item setLabel: @"Pause All"];
759        [item setPaletteLabel: [item label]];
760        [item setToolTip: @"Pause all torrents"];
761        [item setImage: [NSImage imageNamed: @"PauseAll.png"]];
762        [item setTarget: self];
763        [item setAction: @selector( stopAllTorrents: )];
764    }
765    else
766    {
767        [item release];
768        return NULL;
769    }
770
771    return item;
772}
773
774- (NSArray *) toolbarAllowedItemIdentifiers: (NSToolbar *) t
775{
776    return [NSArray arrayWithObjects:
777            TOOLBAR_OPEN, TOOLBAR_REMOVE,
778            TOOLBAR_RESUME_ALL, TOOLBAR_PAUSE_ALL,
779            TOOLBAR_INFO,
780            NSToolbarSeparatorItemIdentifier,
781            NSToolbarSpaceItemIdentifier,
782            NSToolbarFlexibleSpaceItemIdentifier,
783            NSToolbarCustomizeToolbarItemIdentifier,
784            NULL];
785}
786
787- (NSArray *) toolbarDefaultItemIdentifiers: (NSToolbar *) t
788{
789    return [NSArray arrayWithObjects:
790            TOOLBAR_OPEN, TOOLBAR_REMOVE,
791            NSToolbarSeparatorItemIdentifier,
792            TOOLBAR_RESUME_ALL, TOOLBAR_PAUSE_ALL,
793            NSToolbarFlexibleSpaceItemIdentifier,
794            TOOLBAR_INFO,
795            NULL];
796}
797
798- (void) runCustomizationPalette: (id) sender
799{
800    [fToolbar runCustomizationPalette:sender];
801}
802
803- (void) showHideToolbar: (id) sender
804{
805    [fWindow toggleToolbarShown:sender];
806}
807
808- (BOOL)validateToolbarItem:(NSToolbarItem *)toolbarItem
809{
810    SEL action = [toolbarItem action];
811
812    //enable remove item
813    if (action == @selector(removeTorrent:))
814        return [fTableView selectedRow] >= 0;
815
816    Torrent * torrent;
817    NSEnumerator * enumerator;
818
819    //enable resume all item
820    if (action == @selector(resumeAllTorrents:))
821    {
822        enumerator = [fTorrents objectEnumerator];
823        while( ( torrent = [enumerator nextObject] ) )
824            if( [torrent isPaused] )
825                return YES;
826        return NO;
827    }
828
829    //enable pause all item
830    if (action == @selector(stopAllTorrents:))
831    {
832        enumerator = [fTorrents objectEnumerator];
833        while( ( torrent = [enumerator nextObject] ) )
834            if( [torrent isActive] )
835                return YES;
836        return NO;
837    }
838
839    return YES;
840}
841
842- (BOOL)validateMenuItem:(NSMenuItem *)menuItem
843{
844    SEL action = [menuItem action];
845
846    //disable menus if customize sheet is active
847    if ([fToolbar customizationPaletteIsRunning])
848        return NO;
849
850    //enable customize toolbar item
851    if (action == @selector(showHideToolbar:))
852    {
853        [menuItem setTitle: [fToolbar isVisible] ? @"Hide Toolbar" : @"Show Toolbar"];
854        return YES;
855    }
856
857    //enable show info
858    if (action == @selector(showInfo:))
859    {
860        [menuItem setTitle: [fInfoPanel isVisible] ? @"Hide Info" : @"Show Info"];
861        return YES;
862    }
863
864    Torrent * torrent;
865    NSEnumerator * enumerator;
866
867    //enable resume all item
868    if (action == @selector(resumeAllTorrents:))
869    {
870        enumerator = [fTorrents objectEnumerator];
871        while( ( torrent = [enumerator nextObject] ) )
872            if( [torrent isPaused] )
873                return YES;
874        return NO;
875    }
876
877    //enable pause all item
878    if (action == @selector(stopAllTorrents:))
879    {
880        enumerator = [fTorrents objectEnumerator];
881        while( ( torrent = [enumerator nextObject] ) )
882            if( [torrent isActive] )
883                return YES;
884        return NO;
885    }
886
887    int row = [fTableView selectedRow];
888    torrent = ( row < 0 ) ? nil : [fTorrents objectAtIndex: row];
889
890    if (action == @selector(revealFromMenu:))
891    {
892        return ( torrent != nil );
893    }
894
895    //enable remove items
896    if (action == @selector(removeTorrent:)
897        || action == @selector(removeTorrentDeleteFile:)
898        || action == @selector(removeTorrentDeleteData:)
899        || action == @selector(removeTorrentDeleteBoth:))
900    {
901        //append or remove ellipsis when needed
902        if( torrent && [torrent isActive] &&
903            [fDefaults boolForKey: @"CheckRemove"] )
904        {
905            if (![[menuItem title] hasSuffix:NS_ELLIPSIS])
906                [menuItem setTitle:[[menuItem title] stringByAppendingString:NS_ELLIPSIS]];
907        }
908        else
909        {
910            if ([[menuItem title] hasSuffix:NS_ELLIPSIS])
911                [menuItem setTitle:[[menuItem title] substringToIndex:[[menuItem title] length]-[NS_ELLIPSIS length]]];
912        }
913        return ( torrent != nil );
914    }
915
916    //enable and change pause / remove item
917    if( action == @selector(resumeTorrent:) ||
918        action == @selector(stopTorrent:) )
919    {
920        if( !torrent || [torrent isActive] )
921        {
922            [menuItem setTitle: @"Pause"];
923            [menuItem setAction: @selector( stopTorrent: )];
924        }
925        else
926        {
927            [menuItem setTitle: @"Resume"];
928            [menuItem setAction: @selector( resumeTorrent: )];
929        }
930        return ( torrent != nil );
931    }
932
933    return YES;
934}
935
936- (void) sleepCallBack: (natural_t) messageType argument:
937                          (void *) messageArgument
938{
939    NSEnumerator * enumerator;;
940    Torrent * torrent;
941
942    switch( messageType )
943    {
944        case kIOMessageSystemWillSleep:
945            /* Close all connections before going to sleep and remember
946               we should resume when we wake up */
947            enumerator = [fTorrents objectEnumerator];
948            while( ( torrent = [enumerator nextObject] ) )
949            {
950                [torrent sleep];
951            }
952
953            /* Wait for torrents to stop (5 seconds timeout) */
954            NSDate * start = [NSDate date];
955            enumerator = [fTorrents objectEnumerator];
956            while( ( torrent = [enumerator nextObject] ) )
957            {
958                while( [[NSDate date] timeIntervalSinceDate: start] < 5 &&
959                        ![torrent isPaused] )
960                {
961                    usleep( 100000 );
962                    [torrent update];
963                }
964            }
965
966            IOAllowPowerChange( fRootPort, (long) messageArgument );
967            break;
968
969        case kIOMessageCanSystemSleep:
970            /* Prevent idle sleep unless all paused */
971            if (fDownloading > 0 || fSeeding > 0)
972                IOCancelPowerChange( fRootPort, (long) messageArgument );
973            else
974                IOAllowPowerChange( fRootPort, (long) messageArgument );
975            break;
976
977        case kIOMessageSystemHasPoweredOn:
978            /* Resume download after we wake up */
979            enumerator = [fTorrents objectEnumerator];
980            while( ( torrent = [enumerator nextObject] ) )
981            {
982                [torrent wakeUp];
983            }
984            break;
985    }
986}
987
988- (NSRect) windowWillUseStandardFrame: (NSWindow *) w
989    defaultFrame: (NSRect) defaultFrame
990{
991    NSRect rectWin, rectView;
992    float  foo;
993
994    rectWin  = [fWindow frame];
995    rectView = [fScrollView frame];
996    foo      = 25.0 - rectView.size.height + MAX( 1U, [fTorrents count] ) *
997        ( [fTableView rowHeight] + [fTableView intercellSpacing].height );
998
999    rectWin.size.height += foo;
1000    rectWin.origin.y    -= foo;
1001
1002    return rectWin;
1003}
1004
1005- (void) showMainWindow: (id) sender
1006{
1007    [fWindow makeKeyAndOrderFront: nil];
1008}
1009
1010- (void) linkHomepage: (id) sender
1011{
1012    [[NSWorkspace sharedWorkspace] openURL: [NSURL
1013        URLWithString: WEBSITE_URL]];
1014}
1015
1016- (void) linkForums: (id) sender
1017{
1018    [[NSWorkspace sharedWorkspace] openURL: [NSURL
1019        URLWithString: FORUM_URL]];
1020}
1021
1022- (void) notifyGrowl: (NSString * ) file
1023{
1024    NSString * growlScript;
1025    NSAppleScript * appleScript;
1026    NSDictionary * error;
1027
1028    if( !fHasGrowl )
1029        return;
1030
1031    growlScript = [NSString stringWithFormat:
1032        @"tell application \"System Events\"\n"
1033         "  if exists application process \"GrowlHelperApp\" then\n"
1034         "    tell application \"GrowlHelperApp\"\n "
1035         "      notify with name \"Download Complete\""
1036         "        title \"Download Complete\""
1037         "        description \"%@\""
1038         "        application name \"Transmission\"\n"
1039         "    end tell\n"
1040         "  end if\n"
1041         "end tell", file];
1042    appleScript = [[NSAppleScript alloc] initWithSource: growlScript];
1043    if( ![appleScript executeAndReturnError: &error] )
1044    {
1045        printf( "Growl notify failed\n" );
1046    }
1047    [appleScript release];
1048}
1049
1050- (void) growlRegister: (id) sender
1051{
1052    NSString * growlScript;
1053    NSAppleScript * appleScript;
1054    NSDictionary * error;
1055
1056    if( !fHasGrowl )
1057        return;
1058
1059    growlScript = [NSString stringWithFormat:
1060        @"tell application \"System Events\"\n"
1061         "  if exists application process \"GrowlHelperApp\" then\n"
1062         "    tell application \"GrowlHelperApp\"\n"
1063         "      register as application \"Transmission\" "
1064         "        all notifications {\"Download Complete\"}"
1065         "        default notifications {\"Download Complete\"}"
1066         "        icon of application \"Transmission\"\n"
1067         "    end tell\n"
1068         "  end if\n"
1069         "end tell"];
1070
1071    appleScript = [[NSAppleScript alloc] initWithSource: growlScript];
1072    if( ![appleScript executeAndReturnError: &error] )
1073    {
1074        printf( "Growl registration failed\n" );
1075    }
1076    [appleScript release];
1077}
1078
1079- (void) revealFromMenu: (id) sender
1080{
1081    Torrent * torrent;
1082    torrent = [fTorrents objectAtIndex: [fTableView selectedRow]];
1083    [torrent reveal];
1084}
1085
1086- (void) checkForUpdate: (id) sender
1087{
1088    [self checkForUpdateAuto: NO];
1089}
1090
1091- (void) checkForUpdateTimer: (NSTimer *) timer
1092{
1093    NSString * check = [fDefaults stringForKey: @"VersionCheck"];
1094    if( [check isEqualToString: @"Never"] )
1095        return;
1096
1097    NSTimeInterval interval;
1098    if( [check isEqualToString: @"Daily"] )
1099        interval = 24 * 3600;
1100    else if( [check isEqualToString: @"Weekly"] )
1101        interval = 7 * 24 * 3600;
1102    else
1103        return;
1104
1105    id lastObject = [fDefaults objectForKey: @"VersionCheckLast"];
1106    NSDate * lastDate = [lastObject isKindOfClass: [NSDate class]] ?
1107        lastObject : nil;
1108    if( lastDate )
1109    {
1110        NSTimeInterval actualInterval =
1111            [[NSDate date] timeIntervalSinceDate: lastDate];
1112        if( actualInterval > 0 && actualInterval < interval )
1113        {
1114            return;
1115        }
1116    }
1117
1118    [self checkForUpdateAuto: YES];
1119    [fDefaults setObject: [NSDate date] forKey: @"VersionCheckLast"];
1120}
1121
1122- (void) checkForUpdateAuto: (BOOL) automatic
1123{
1124    fCheckIsAutomatic = automatic;
1125    [[NSURL URLWithString: VERSION_PLIST_URL]
1126            loadResourceDataNotifyingClient: self usingCache: NO];
1127}
1128
1129- (void) URLResourceDidFinishLoading: (NSURL *) sender
1130{
1131    NSDictionary * dict = [NSPropertyListSerialization
1132                            propertyListFromData: [sender resourceDataUsingCache: NO]
1133                            mutabilityOption: NSPropertyListImmutable
1134                            format: nil errorDescription: nil];
1135
1136    //check if plist was actually found and contains a version
1137    NSString * webVersion = nil;
1138    if (dict)
1139        webVersion = [dict objectForKey: @"Version"];
1140    if (!webVersion)
1141    {
1142        if (!fCheckIsAutomatic)
1143        {
1144            NSAlert * dialog = [[NSAlert alloc] init];
1145            [dialog addButtonWithTitle: @"OK"];
1146            [dialog setMessageText: @"Error checking for updates."];
1147            [dialog setInformativeText:
1148                    @"Transmission was not able to check the latest version available."];
1149            [dialog setAlertStyle: NSInformationalAlertStyle];
1150
1151            [dialog runModal];
1152            [dialog release];
1153        }
1154        return;
1155    }
1156
1157    NSString * currentVersion = [[[NSBundle mainBundle] infoDictionary]
1158                                objectForKey: (NSString *)kCFBundleVersionKey];
1159
1160    NSEnumerator * webEnum = [[webVersion componentsSeparatedByString: @"."] objectEnumerator],
1161            * currentEnum = [[currentVersion componentsSeparatedByString: @"."] objectEnumerator];
1162    NSString * webSub, * currentSub;
1163
1164    BOOL webGreater = NO;
1165    NSComparisonResult result;
1166    while ((webSub = [webEnum nextObject]))
1167    {
1168        if (!(currentSub = [currentEnum nextObject]))
1169        {
1170            webGreater = YES;
1171            break;
1172        }
1173
1174        result = [currentSub compare: webSub options: NSNumericSearch];
1175        if (result != NSOrderedSame)
1176        {
1177            if (result == NSOrderedAscending)
1178                webGreater = YES;
1179            break;
1180        }
1181    }
1182
1183    if (webGreater)
1184    {
1185        NSAlert * dialog = [[NSAlert alloc] init];
1186        [dialog addButtonWithTitle: @"Go to Website"];
1187        [dialog addButtonWithTitle:@"Cancel"];
1188        [dialog setMessageText: @"New version is available!"];
1189        [dialog setInformativeText: [NSString stringWithFormat:
1190            @"A newer version (%@) is available for download from the Transmission website.", webVersion]];
1191        [dialog setAlertStyle: NSInformationalAlertStyle];
1192
1193        if ([dialog runModal] == NSAlertFirstButtonReturn)
1194            [self linkHomepage: nil];
1195
1196        [dialog release];
1197    }
1198    else if (!fCheckIsAutomatic)
1199    {
1200        NSAlert * dialog = [[NSAlert alloc] init];
1201        [dialog addButtonWithTitle: @"OK"];
1202        [dialog setMessageText: @"No new versions are available."];
1203        [dialog setInformativeText: [NSString stringWithFormat:
1204            @"You are running the most current version of Transmission (%@).", currentVersion]];
1205        [dialog setAlertStyle: NSInformationalAlertStyle];
1206
1207        [dialog runModal];
1208        [dialog release];
1209    }
1210    else;
1211}
1212
1213@end
Note: See TracBrowser for help on using the repository browser.