source: branches/new_api/macosx/Controller.m @ 154

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

Fixes history with no torrents, always remove ellipis to menu items if
no torrent is selected

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