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

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

OS X design changes: merged the two cells into one

File size: 38.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    [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 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: NULL];
179    return NO;
180}
181
182- (BOOL) applicationShouldHandleReopen: (NSApplication *) app
183    hasVisibleWindows: (BOOL) flag
184{
185    [self showMainWindow: NULL];
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] )
462    {
463        if ([fDefaults boolForKey: @"CheckRemove"])
464        {
465            NSDictionary * dict = [NSDictionary dictionaryWithObjectsAndKeys:
466                        [NSString stringWithFormat: @"%d", idx], @"Index",
467                        [NSString stringWithFormat: @"%d", deleteTorrent], @"DeleteTorrent",
468                        [NSString stringWithFormat: @"%d", deleteData], @"DeleteData",
469                        nil];
470            [dict retain];
471
472            NSBeginAlertSheet(@"Confirm Remove",
473                                @"Remove", @"Cancel", nil,
474                                fWindow, self,
475                                @selector(removeSheetDidEnd:returnCode:contextInfo:),
476                                NULL, dict, @"This torrent is active. Do you really want to remove it?");
477            return;
478        }
479        //stop if not stopped
480        else
481            [self stopTorrentWithIndex:idx];
482    }
483
484    [self confirmRemoveTorrentWithIndex: idx
485            deleteTorrent: deleteTorrent
486            deleteData: deleteData];
487}
488
489- (void) removeSheetDidEnd:(NSWindow *)sheet returnCode:(int)returnCode
490                        contextInfo:(NSDictionary  *)dict
491{
492    [NSApp stopModal];
493    if (returnCode != NSAlertDefaultReturn)
494    {
495        [dict release];
496        return;
497    }
498
499    int idx = [[dict objectForKey:@"Index"] intValue];
500
501    [self stopTorrentWithIndex:idx];
502
503    [self confirmRemoveTorrentWithIndex: idx
504        deleteTorrent: [[dict objectForKey:@"DeleteTorrent"] intValue]
505        deleteData: [[dict objectForKey:@"DeleteData"] intValue]];
506    [dict release];
507}
508
509- (void) confirmRemoveTorrentWithIndex: (int) idx
510            deleteTorrent: (BOOL) deleteTorrent
511            deleteData: (BOOL) deleteData
512{
513#if 0
514    if( deleteData )
515    {
516        [self finderTrash: [NSString stringWithFormat: @"%@/%@",
517            [NSString stringWithUTF8String: fStat[idx].folder],
518            [NSString stringWithUTF8String: fStat[idx].info.name]]];
519    }
520    if( deleteTorrent )
521    {
522        [self finderTrash: [NSString stringWithUTF8String:
523            fStat[idx].info.torrent]];
524    }
525
526    tr_torrentClose( fHandle, idx );
527    [self updateUI: NULL];
528    [self updateTorrentHistory];
529#endif
530}
531
532- (void) removeTorrent: (id) sender
533{
534    [self removeTorrentWithIndex: [fTableView selectedRow] deleteTorrent: NO deleteData: NO ];
535}
536
537- (void) removeTorrentDeleteFile: (id) sender
538{
539    [self removeTorrentWithIndex: [fTableView selectedRow] deleteTorrent: YES deleteData: NO];
540}
541
542- (void) removeTorrentDeleteData: (id) sender
543{
544    [self removeTorrentWithIndex: [fTableView selectedRow] deleteTorrent: NO deleteData: YES];
545}
546
547- (void) removeTorrentDeleteBoth: (id) sender
548{
549    [self removeTorrentWithIndex: [fTableView selectedRow] deleteTorrent: YES deleteData: YES];
550}
551
552- (void) showInfo: (id) sender
553{
554    if( [fInfoPanel isVisible] )
555    {
556        [fInfoPanel close];
557    }
558    else
559    {
560        [fInfoPanel orderFront: sender];
561    }
562}
563
564- (void) updateUI: (NSTimer *) t
565{
566    float dl, ul;
567    NSEnumerator * enumerator;
568    Torrent * torrent;
569
570    /* Update the TableView */
571    enumerator = [fTorrents objectEnumerator];
572    while( ( torrent = [enumerator nextObject] ) )
573    {
574        [torrent update];
575
576        if( [torrent justFinished] )
577        {
578            /* Notifications */
579            [self notifyGrowl: [torrent name]];
580            if( ![fWindow isKeyWindow] )
581            {
582                fCompleted++;
583            }
584        }
585    }
586    [fTableView reloadData];
587
588    //Update the global DL/UL rates
589    tr_torrentRates( fLib, &dl, &ul );
590    NSString * downloadRate = [NSString stringForSpeed: dl];
591    NSString * uploadRate = [NSString stringForSpeed: ul];
592    [fTotalDLField setStringValue: downloadRate];
593    [fTotalULField setStringValue: uploadRate];
594
595#if 0
596    //Update DL/UL totals in the Info panel
597    row = [fTableView selectedRow];
598    if( row >= 0 )
599    {
600        [fInfoDownloaded setStringValue:
601            [NSString stringForFileSize: fStat[row].downloaded]];
602        [fInfoUploaded setStringValue:
603            [NSString stringForFileSize: fStat[row].uploaded]];
604    }
605#endif
606
607    //badge dock
608    [fBadger updateBadgeWithCompleted: fCompleted
609        uploadRate: ul >= 0.1 && [fDefaults boolForKey: @"BadgeUploadRate"]
610          ? [NSString stringForSpeedAbbrev: ul] : nil
611        downloadRate: dl >= 0.1 && [fDefaults boolForKey: @"BadgeDownloadRate"]
612          ? [NSString stringForSpeedAbbrev: dl] : nil];
613}
614
615- (void) updateTorrentHistory
616{
617    if( [fTorrents count] < 1 )
618        return;
619
620    NSMutableArray * history = [NSMutableArray
621        arrayWithCapacity: [fTorrents count]];
622
623    NSEnumerator * enumerator = [fTorrents objectEnumerator];
624    Torrent * torrent;
625    while( ( torrent = [enumerator nextObject] ) )
626    {
627        [history addObject: [NSDictionary dictionaryWithObjectsAndKeys:
628            [torrent path],                      @"TorrentPath",
629            [torrent getFolder],                 @"DownloadFolder",
630            [torrent isActive] ? @"NO" : @"YES", @"Paused",
631            nil]];
632    }
633
634    [fDefaults setObject: history forKey: @"History"];
635}
636
637- (int) numberOfRowsInTableView: (NSTableView *) t
638{
639    return [fTorrents count];
640}
641
642- (id) tableView: (NSTableView *) t objectValueForTableColumn:
643    (NSTableColumn *) tableColumn row: (int) rowIndex
644{
645    return nil;
646}
647
648
649- (void) tableView: (NSTableView *) t willDisplayCell: (id) cell
650    forTableColumn: (NSTableColumn *) tableColumn row: (int) rowIndex
651{
652    [cell setTorrent: [fTorrents objectAtIndex: rowIndex]];
653    [cell setTextColor: ( [fWindow isKeyWindow] &&
654        rowIndex == [fTableView selectedRow] ) ?
655        [NSColor whiteColor] : [NSColor blackColor]];
656}
657
658- (BOOL) tableView: (NSTableView *) t acceptDrop:
659    (id <NSDraggingInfo>) info row: (int) row dropOperation:
660    (NSTableViewDropOperation) operation
661{
662    [self application: NSApp openFiles: [[[info draggingPasteboard]
663        propertyListForType: NSFilenamesPboardType]
664        pathsMatchingExtensions: [NSArray arrayWithObject: @"torrent"]]];
665    return YES;
666}
667
668- (NSDragOperation) tableView: (NSTableView *) t validateDrop:
669    (id <NSDraggingInfo>) info proposedRow: (int) row
670    proposedDropOperation: (NSTableViewDropOperation) operation
671{
672    NSPasteboard * pasteboard = [info draggingPasteboard];
673
674    if (![[pasteboard types] containsObject: NSFilenamesPboardType]
675            || [[[pasteboard propertyListForType: NSFilenamesPboardType]
676                pathsMatchingExtensions: [NSArray arrayWithObject: @"torrent"]]
677                count] == 0)
678        return NSDragOperationNone;
679
680    [fTableView setDropRow: [fTorrents count]
681        dropOperation: NSTableViewDropAbove];
682    return NSDragOperationGeneric;
683}
684
685- (void) tableViewSelectionDidChange: (NSNotification *) n
686{
687    int row = [fTableView selectedRow];
688
689    if( row < 0 )
690    {
691        [fInfoTitle      setStringValue: @"No torrent selected"];
692        [fInfoTracker    setStringValue: @""];
693        [fInfoAnnounce   setStringValue: @""];
694        [fInfoSize       setStringValue: @""];
695        [fInfoPieces     setStringValue: @""];
696        [fInfoPieceSize  setStringValue: @""];
697        [fInfoFolder     setStringValue: @""];
698        [fInfoDownloaded setStringValue: @""];
699        [fInfoUploaded   setStringValue: @""];
700        [fInfoSeeders    setStringValue: @""];
701        [fInfoLeechers   setStringValue: @""];
702        return;
703    }
704
705#if 0
706    /* Update info window */
707    [fInfoTitle setStringValue: [NSString stringWithUTF8String:
708        fStat[row].info.name]];
709    [fInfoTracker setStringValue: [NSString stringWithFormat:
710        @"%s:%d", fStat[row].info.trackerAddress, fStat[row].info.trackerPort]];
711    [fInfoAnnounce setStringValue: [NSString stringWithCString:
712        fStat[row].info.trackerAnnounce]];
713    [fInfoSize setStringValue:
714        [NSString stringForFileSize: fStat[row].info.totalSize]];
715    [fInfoPieces setStringValue: [NSString stringWithFormat: @"%d",
716        fStat[row].info.pieceCount]];
717    [fInfoPieceSize setStringValue:
718        [NSString stringForFileSize: fStat[row].info.pieceSize]];
719    [fInfoFolder setStringValue: [[NSString stringWithUTF8String:
720        tr_torrentGetFolder( fHandle, row )] lastPathComponent]];
721
722    if ( fStat[row].seeders == -1 ) {
723        [fInfoSeeders setStringValue: [NSString stringWithUTF8String: "?"]];
724    } else {
725        [fInfoSeeders setStringValue: [NSString stringWithFormat: @"%d",
726            fStat[row].seeders]];
727    }
728    if ( fStat[row].leechers == -1 ) {
729        [fInfoLeechers setStringValue: [NSString stringWithUTF8String: "?"]];
730    } else {
731        [fInfoLeechers setStringValue: [NSString stringWithFormat: @"%d",
732            fStat[row].leechers]];
733    }
734#endif
735}
736
737- (NSToolbarItem *) toolbar: (NSToolbar *) t itemForItemIdentifier:
738    (NSString *) ident willBeInsertedIntoToolbar: (BOOL) flag
739{
740    NSToolbarItem * item = [[NSToolbarItem alloc] initWithItemIdentifier: ident];
741
742    if( [ident isEqualToString: TOOLBAR_OPEN] )
743    {
744        [item setLabel: @"Open"];
745        [item setPaletteLabel: [item label]];
746        [item setToolTip: @"Open a torrent"];
747        [item setImage: [NSImage imageNamed: @"Open.png"]];
748        [item setTarget: self];
749        [item setAction: @selector( openShowSheet: )];
750    }
751    else if( [ident isEqualToString: TOOLBAR_REMOVE] )
752    {
753        [item setLabel: @"Remove"];
754        [item setPaletteLabel: [item label]];
755        [item setToolTip: @"Remove torrent from list"];
756        [item setImage: [NSImage imageNamed: @"Remove.png"]];
757        [item setTarget: self];
758        [item setAction: @selector( removeTorrent: )];
759    }
760    else if( [ident isEqualToString: TOOLBAR_INFO] )
761    {
762        [item setLabel: @"Info"];
763        [item setPaletteLabel: [item label]];
764        [item setToolTip: @"Information"];
765        [item setImage: [NSImage imageNamed: @"Info.png"]];
766        [item setTarget: self];
767        [item setAction: @selector( showInfo: )];
768    }
769    else if( [ident isEqualToString: TOOLBAR_RESUME_ALL] )
770    {
771        [item setLabel: @"Resume All"];
772        [item setPaletteLabel: [item label]];
773        [item setToolTip: @"Resume all torrents"];
774        [item setImage: [NSImage imageNamed: @"ResumeAll.png"]];
775        [item setTarget: self];
776        [item setAction: @selector( resumeAllTorrents: )];
777    }
778    else if( [ident isEqualToString: TOOLBAR_PAUSE_ALL] )
779    {
780        [item setLabel: @"Pause All"];
781        [item setPaletteLabel: [item label]];
782        [item setToolTip: @"Pause all torrents"];
783        [item setImage: [NSImage imageNamed: @"PauseAll.png"]];
784        [item setTarget: self];
785        [item setAction: @selector( stopAllTorrents: )];
786    }
787    else
788    {
789        [item release];
790        return NULL;
791    }
792
793    return item;
794}
795
796- (NSArray *) toolbarAllowedItemIdentifiers: (NSToolbar *) t
797{
798    return [NSArray arrayWithObjects:
799            TOOLBAR_OPEN, TOOLBAR_REMOVE,
800            TOOLBAR_RESUME_ALL, TOOLBAR_PAUSE_ALL,
801            TOOLBAR_INFO,
802            NSToolbarSeparatorItemIdentifier,
803            NSToolbarSpaceItemIdentifier,
804            NSToolbarFlexibleSpaceItemIdentifier,
805            NSToolbarCustomizeToolbarItemIdentifier,
806            NULL];
807}
808
809- (NSArray *) toolbarDefaultItemIdentifiers: (NSToolbar *) t
810{
811    return [NSArray arrayWithObjects:
812            TOOLBAR_OPEN, TOOLBAR_REMOVE,
813            NSToolbarSeparatorItemIdentifier,
814            TOOLBAR_RESUME_ALL, TOOLBAR_PAUSE_ALL,
815            NSToolbarFlexibleSpaceItemIdentifier,
816            TOOLBAR_INFO,
817            NULL];
818}
819
820- (void) runCustomizationPalette: (id) sender
821{
822    [fToolbar runCustomizationPalette:sender];
823}
824
825- (void) showHideToolbar: (id) sender
826{
827    [fWindow toggleToolbarShown:sender];
828}
829
830- (BOOL)validateToolbarItem:(NSToolbarItem *)toolbarItem
831{
832    SEL action = [toolbarItem action];
833
834    //enable remove item
835    if (action == @selector(removeTorrent:))
836        return [fTableView selectedRow] >= 0;
837
838    Torrent * torrent;
839    NSEnumerator * enumerator;
840
841    //enable resume all item
842    if (action == @selector(resumeAllTorrents:))
843    {
844        enumerator = [fTorrents objectEnumerator];
845        while( ( torrent = [enumerator nextObject] ) )
846            if( [torrent isPaused] )
847                return YES;
848        return NO;
849    }
850
851    //enable pause all item
852    if (action == @selector(stopAllTorrents:))
853    {
854        enumerator = [fTorrents objectEnumerator];
855        while( ( torrent = [enumerator nextObject] ) )
856            if( [torrent isActive] )
857                return YES;
858        return NO;
859    }
860
861    return YES;
862}
863
864- (BOOL)validateMenuItem:(NSMenuItem *)menuItem
865{
866    SEL action = [menuItem action];
867
868    //disable menus if customize sheet is active
869    if ([fToolbar customizationPaletteIsRunning])
870        return NO;
871
872    //enable customize toolbar item
873    if (action == @selector(showHideToolbar:))
874    {
875        [menuItem setTitle: [fToolbar isVisible] ? @"Hide Toolbar" : @"Show Toolbar"];
876        return YES;
877    }
878
879    //enable show info
880    if (action == @selector(showInfo:))
881    {
882        [menuItem setTitle: [fInfoPanel isVisible] ? @"Hide Info" : @"Show Info"];
883        return YES;
884    }
885
886    Torrent * torrent;
887    NSEnumerator * enumerator;
888
889    //enable resume all item
890    if (action == @selector(resumeAllTorrents:))
891    {
892        enumerator = [fTorrents objectEnumerator];
893        while( ( torrent = [enumerator nextObject] ) )
894            if( [torrent isPaused] )
895                return YES;
896        return NO;
897    }
898
899    //enable pause all item
900    if (action == @selector(stopAllTorrents:))
901    {
902        enumerator = [fTorrents objectEnumerator];
903        while( ( torrent = [enumerator nextObject] ) )
904            if( [torrent isActive] )
905                return YES;
906        return NO;
907    }
908
909    int row = [fTableView selectedRow];
910    torrent = ( row < 0 ) ? nil : [fTorrents objectAtIndex: row];
911
912    if (action == @selector(revealFromMenu:))
913    {
914        return ( torrent != nil );
915    }
916
917    //enable remove items
918    if (action == @selector(removeTorrent:)
919        || action == @selector(removeTorrentDeleteFile:)
920        || action == @selector(removeTorrentDeleteData:)
921        || action == @selector(removeTorrentDeleteBoth:))
922    {
923        if( !torrent )
924            return NO;
925
926        //append or remove ellipsis when needed
927        if( [torrent isActive] && [fDefaults boolForKey: @"CheckRemove"] )
928        {
929            if (![[menuItem title] hasSuffix:NS_ELLIPSIS])
930                [menuItem setTitle:[[menuItem title] stringByAppendingString:NS_ELLIPSIS]];
931        }
932        else
933        {
934            if ([[menuItem title] hasSuffix:NS_ELLIPSIS])
935                [menuItem setTitle:[[menuItem title] substringToIndex:[[menuItem title] length]-[NS_ELLIPSIS length]]];
936        }
937        return YES;
938    }
939
940    //enable and change pause / remove item
941    if( action == @selector(resumeTorrent:) ||
942        action == @selector(stopTorrent:) )
943    {
944        if( !torrent )
945            return NO;
946
947        if( [torrent isActive] )
948        {
949            [menuItem setTitle: @"Pause"];
950            [menuItem setAction: @selector( stopTorrent: )];
951        }
952        else
953        {
954            [menuItem setTitle: @"Resume"];
955            [menuItem setAction: @selector( resumeTorrent: )];
956        }
957    }
958
959    return YES;
960}
961
962- (void) sleepCallBack: (natural_t) messageType argument:
963                          (void *) messageArgument
964{
965    NSEnumerator * enumerator;;
966    Torrent * torrent;
967
968    switch( messageType )
969    {
970        case kIOMessageSystemWillSleep:
971            /* Close all connections before going to sleep and remember
972               we should resume when we wake up */
973            enumerator = [fTorrents objectEnumerator];
974            while( ( torrent = [enumerator nextObject] ) )
975            {
976                [torrent sleep];
977            }
978
979            /* Wait for torrents to stop (5 seconds timeout) */
980            NSDate * start = [NSDate date];
981            enumerator = [fTorrents objectEnumerator];
982            while( ( torrent = [enumerator nextObject] ) )
983            {
984                while( [[NSDate date] timeIntervalSinceDate: start] < 5 &&
985                        ![torrent isPaused] )
986                {
987                    usleep( 100000 );
988                    [torrent update];
989                }
990            }
991
992            IOAllowPowerChange( fRootPort, (long) messageArgument );
993            break;
994
995        case kIOMessageCanSystemSleep:
996            /* Prevent idle sleep unless all paused */
997            if (fDownloading > 0 || fSeeding > 0)
998                IOCancelPowerChange( fRootPort, (long) messageArgument );
999            else
1000                IOAllowPowerChange( fRootPort, (long) messageArgument );
1001            break;
1002
1003        case kIOMessageSystemHasPoweredOn:
1004            /* Resume download after we wake up */
1005            enumerator = [fTorrents objectEnumerator];
1006            while( ( torrent = [enumerator nextObject] ) )
1007            {
1008                [torrent wakeUp];
1009            }
1010            break;
1011    }
1012}
1013
1014- (NSRect) windowWillUseStandardFrame: (NSWindow *) w
1015    defaultFrame: (NSRect) defaultFrame
1016{
1017    NSRect rectWin, rectView;
1018    float  foo;
1019
1020    rectWin  = [fWindow frame];
1021    rectView = [fScrollView frame];
1022    foo      = 25.0 - rectView.size.height + MAX( 1U, [fTorrents count] ) *
1023        ( [fTableView rowHeight] + [fTableView intercellSpacing].height );
1024
1025    rectWin.size.height += foo;
1026    rectWin.origin.y    -= foo;
1027
1028    return rectWin;
1029}
1030
1031- (void) showMainWindow: (id) sender
1032{
1033    [fWindow makeKeyAndOrderFront: nil];
1034}
1035
1036- (void) linkHomepage: (id) sender
1037{
1038    [[NSWorkspace sharedWorkspace] openURL: [NSURL
1039        URLWithString: WEBSITE_URL]];
1040}
1041
1042- (void) linkForums: (id) sender
1043{
1044    [[NSWorkspace sharedWorkspace] openURL: [NSURL
1045        URLWithString: FORUM_URL]];
1046}
1047
1048- (void) notifyGrowl: (NSString * ) file
1049{
1050    NSString * growlScript;
1051    NSAppleScript * appleScript;
1052    NSDictionary * error;
1053
1054    if( !fHasGrowl )
1055        return;
1056
1057    growlScript = [NSString stringWithFormat:
1058        @"tell application \"System Events\"\n"
1059         "  if exists application process \"GrowlHelperApp\" then\n"
1060         "    tell application \"GrowlHelperApp\"\n "
1061         "      notify with name \"Download Complete\""
1062         "        title \"Download Complete\""
1063         "        description \"%@\""
1064         "        application name \"Transmission\"\n"
1065         "    end tell\n"
1066         "  end if\n"
1067         "end tell", file];
1068    appleScript = [[NSAppleScript alloc] initWithSource: growlScript];
1069    if( ![appleScript executeAndReturnError: &error] )
1070    {
1071        printf( "Growl notify failed\n" );
1072    }
1073    [appleScript release];
1074}
1075
1076- (void) growlRegister: (id) sender
1077{
1078    NSString * growlScript;
1079    NSAppleScript * appleScript;
1080    NSDictionary * error;
1081
1082    if( !fHasGrowl )
1083        return;
1084
1085    growlScript = [NSString stringWithFormat:
1086        @"tell application \"System Events\"\n"
1087         "  if exists application process \"GrowlHelperApp\" then\n"
1088         "    tell application \"GrowlHelperApp\"\n"
1089         "      register as application \"Transmission\" "
1090         "        all notifications {\"Download Complete\"}"
1091         "        default notifications {\"Download Complete\"}"
1092         "        icon of application \"Transmission\"\n"
1093         "    end tell\n"
1094         "  end if\n"
1095         "end tell"];
1096
1097    appleScript = [[NSAppleScript alloc] initWithSource: growlScript];
1098    if( ![appleScript executeAndReturnError: &error] )
1099    {
1100        printf( "Growl registration failed\n" );
1101    }
1102    [appleScript release];
1103}
1104
1105- (void) revealFromMenu: (id) sender
1106{
1107#if 0
1108    int row = [fTableView selectedRow];
1109    if (row >= 0)
1110    {
1111        [self finderReveal: [NSString stringWithFormat: @"%@/%@",
1112            [NSString stringWithUTF8String: fStat[row].folder],
1113            [NSString stringWithUTF8String: fStat[row].info.name]]];
1114    }
1115#endif
1116}
1117
1118- (void) finderReveal: (NSString *) path
1119{
1120    NSString * string;
1121    NSAppleScript * appleScript;
1122    NSDictionary * error;
1123
1124    string = [NSString stringWithFormat:
1125        @"tell application \"Finder\"\n"
1126         "  activate\n"
1127         "  reveal (POSIX file \"%@\")\n"
1128         "end tell", path];
1129
1130    appleScript = [[NSAppleScript alloc] initWithSource: string];
1131    if( ![appleScript executeAndReturnError: &error] )
1132    {
1133        printf( "finderReveal failed\n" );
1134    }
1135    [appleScript release];
1136}
1137
1138- (void) finderTrash: (NSString *) path
1139{
1140    NSString * string;
1141    NSAppleScript * appleScript;
1142    NSDictionary * error;
1143
1144    string = [NSString stringWithFormat:
1145        @"tell application \"Finder\"\n"
1146         "  move (POSIX file \"%@\") to trash\n"
1147         "end tell", path];
1148
1149    appleScript = [[NSAppleScript alloc] initWithSource: string];
1150    if( ![appleScript executeAndReturnError: &error] )
1151    {
1152        printf( "finderTrash failed\n" );
1153    }
1154    [appleScript release];
1155}
1156
1157- (void) checkForUpdate: (id) sender
1158{
1159    [self checkForUpdateAuto: NO];
1160}
1161
1162- (void) checkForUpdateTimer: (NSTimer *) timer
1163{
1164    NSString * check = [fDefaults stringForKey: @"VersionCheck"];
1165    if( [check isEqualToString: @"Never"] )
1166        return;
1167
1168    NSTimeInterval interval;
1169    if( [check isEqualToString: @"Daily"] )
1170        interval = 24 * 3600;
1171    else if( [check isEqualToString: @"Weekly"] )
1172        interval = 7 * 24 * 3600;
1173    else
1174        return;
1175
1176    id lastObject = [fDefaults objectForKey: @"VersionCheckLast"];
1177    NSDate * lastDate = [lastObject isKindOfClass: [NSDate class]] ?
1178        lastObject : nil;
1179    if( lastDate )
1180    {
1181        NSTimeInterval actualInterval =
1182            [[NSDate date] timeIntervalSinceDate: lastDate];
1183        if( actualInterval > 0 && actualInterval < interval )
1184        {
1185            return;
1186        }
1187    }
1188
1189    [self checkForUpdateAuto: YES];
1190    [fDefaults setObject: [NSDate date] forKey: @"VersionCheckLast"];
1191}
1192
1193- (void) checkForUpdateAuto: (BOOL) automatic
1194{
1195    fCheckIsAutomatic = automatic;
1196    [[NSURL URLWithString: VERSION_PLIST_URL]
1197            loadResourceDataNotifyingClient: self usingCache: NO];
1198}
1199
1200- (void) URLResourceDidFinishLoading: (NSURL *) sender
1201{
1202    NSDictionary * dict = [NSPropertyListSerialization
1203                            propertyListFromData: [sender resourceDataUsingCache: NO]
1204                            mutabilityOption: NSPropertyListImmutable
1205                            format: nil errorDescription: nil];
1206
1207    //check if plist was actually found and contains a version
1208    NSString * webVersion = nil;
1209    if (dict)
1210        webVersion = [dict objectForKey: @"Version"];
1211    if (!webVersion)
1212    {
1213        if (!fCheckIsAutomatic)
1214        {
1215            NSAlert * dialog = [[NSAlert alloc] init];
1216            [dialog addButtonWithTitle: @"OK"];
1217            [dialog setMessageText: @"Error checking for updates."];
1218            [dialog setInformativeText:
1219                    @"Transmission was not able to check the latest version available."];
1220            [dialog setAlertStyle: NSInformationalAlertStyle];
1221
1222            [dialog runModal];
1223            [dialog release];
1224        }
1225        return;
1226    }
1227
1228    NSString * currentVersion = [[[NSBundle mainBundle] infoDictionary]
1229                                objectForKey: (NSString *)kCFBundleVersionKey];
1230
1231    NSEnumerator * webEnum = [[webVersion componentsSeparatedByString: @"."] objectEnumerator],
1232            * currentEnum = [[currentVersion componentsSeparatedByString: @"."] objectEnumerator];
1233    NSString * webSub, * currentSub;
1234
1235    BOOL webGreater = NO;
1236    NSComparisonResult result;
1237    while ((webSub = [webEnum nextObject]))
1238    {
1239        if (!(currentSub = [currentEnum nextObject]))
1240        {
1241            webGreater = YES;
1242            break;
1243        }
1244
1245        result = [currentSub compare: webSub options: NSNumericSearch];
1246        if (result != NSOrderedSame)
1247        {
1248            if (result == NSOrderedAscending)
1249                webGreater = YES;
1250            break;
1251        }
1252    }
1253
1254    if (webGreater)
1255    {
1256        NSAlert * dialog = [[NSAlert alloc] init];
1257        [dialog addButtonWithTitle: @"Go to Website"];
1258        [dialog addButtonWithTitle:@"Cancel"];
1259        [dialog setMessageText: @"New version is available!"];
1260        [dialog setInformativeText: [NSString stringWithFormat:
1261            @"A newer version (%@) is available for download from the Transmission website.", webVersion]];
1262        [dialog setAlertStyle: NSInformationalAlertStyle];
1263
1264        if ([dialog runModal] == NSAlertFirstButtonReturn)
1265            [self linkHomepage: nil];
1266
1267        [dialog release];
1268    }
1269    else if (!fCheckIsAutomatic)
1270    {
1271        NSAlert * dialog = [[NSAlert alloc] init];
1272        [dialog addButtonWithTitle: @"OK"];
1273        [dialog setMessageText: @"No new versions are available."];
1274        [dialog setInformativeText: [NSString stringWithFormat:
1275            @"You are running the most current version of Transmission (%@).", currentVersion]];
1276        [dialog setAlertStyle: NSInformationalAlertStyle];
1277
1278        [dialog runModal];
1279        [dialog release];
1280    }
1281    else;
1282}
1283
1284@end
Note: See TracBrowser for help on using the repository browser.