source: trunk/macosx/Controller.m @ 165

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

Fixed the info panel and refined its look

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