source: trunk/macosx/Controller.m @ 120

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

Simplification

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