1/******************************************************************************
2 * Copyright (c) 2010-2012 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 "InfoOptionsViewController.h"
24#import "NSStringAdditions.h"
25#import "Torrent.h"
26
27#define OPTION_POPUP_GLOBAL 0
28#define OPTION_POPUP_NO_LIMIT 1
29#define OPTION_POPUP_LIMIT 2
30
31#define OPTION_POPUP_PRIORITY_HIGH 0
32#define OPTION_POPUP_PRIORITY_NORMAL 1
33#define OPTION_POPUP_PRIORITY_LOW 2
34
35#define INVALID -99
36
37@interface InfoOptionsViewController (Private)
38
39- (void) setupInfo;
40- (void) setGlobalLabels;
41- (void) updateOptionsNotification: (NSNotification *) notification;
42
43@end
44
45@implementation InfoOptionsViewController
46
47- (id) init
48{
49    if ((self = [super initWithNibName: @"InfoOptionsView" bundle: nil]))
50    {
51        [self setTitle: NSLocalizedString(@"Options", "Inspector view -> title")];
52    }
53
54    return self;
55}
56
57- (void) awakeFromNib
58{
59    [self setGlobalLabels];
60
61    [[NSNotificationCenter defaultCenter] addObserver: self selector: @selector(setGlobalLabels) name: @"UpdateGlobalOptions" object: nil];
62    [[NSNotificationCenter defaultCenter] addObserver: self selector: @selector(updateOptionsNotification:) name: @"UpdateOptionsNotification" object: nil];
63}
64
65- (void) dealloc
66{
67    [[NSNotificationCenter defaultCenter] removeObserver: self];
68}
69
70- (void) setInfoForTorrents: (NSArray *) torrents
71{
72    //don't check if it's the same in case the metadata changed
73    fTorrents = torrents;
74
75    fSet = NO;
76}
77
78- (void) updateInfo
79{
80    if (!fSet)
81        [self setupInfo];
82
83    fSet = YES;
84}
85
86- (void) updateOptions
87{
88    if ([fTorrents count] == 0)
89        return;
90
91    //get bandwidth info
92    NSEnumerator * enumerator = [fTorrents objectEnumerator];
93    Torrent * torrent = [enumerator nextObject]; //first torrent
94
95    NSInteger uploadUseSpeedLimit = [torrent usesSpeedLimit: YES] ? NSOnState : NSOffState,
96                uploadSpeedLimit = [torrent speedLimit: YES],
97                downloadUseSpeedLimit = [torrent usesSpeedLimit: NO] ? NSOnState : NSOffState,
98                downloadSpeedLimit = [torrent speedLimit: NO],
99                globalUseSpeedLimit = [torrent usesGlobalSpeedLimit] ? NSOnState : NSOffState;
100
101    while ((torrent = [enumerator nextObject])
102            && (uploadUseSpeedLimit != NSMixedState || uploadSpeedLimit != INVALID
103                || downloadUseSpeedLimit != NSMixedState || downloadSpeedLimit != INVALID
104                || globalUseSpeedLimit != NSMixedState))
105    {
106        if (uploadUseSpeedLimit != NSMixedState && uploadUseSpeedLimit != ([torrent usesSpeedLimit: YES] ? NSOnState : NSOffState))
107            uploadUseSpeedLimit = NSMixedState;
108
109        if (uploadSpeedLimit != INVALID && uploadSpeedLimit != [torrent speedLimit: YES])
110            uploadSpeedLimit = INVALID;
111
112        if (downloadUseSpeedLimit != NSMixedState && downloadUseSpeedLimit != ([torrent usesSpeedLimit: NO] ? NSOnState : NSOffState))
113            downloadUseSpeedLimit = NSMixedState;
114
115        if (downloadSpeedLimit != INVALID && downloadSpeedLimit != [torrent speedLimit: NO])
116            downloadSpeedLimit = INVALID;
117
118        if (globalUseSpeedLimit != NSMixedState && globalUseSpeedLimit != ([torrent usesGlobalSpeedLimit] ? NSOnState : NSOffState))
119            globalUseSpeedLimit = NSMixedState;
120    }
121
122    //set upload view
123    [fUploadLimitCheck setState: uploadUseSpeedLimit];
124    [fUploadLimitCheck setEnabled: YES];
125
126    [fUploadLimitLabel setEnabled: uploadUseSpeedLimit == NSOnState];
127    [fUploadLimitField setEnabled: uploadUseSpeedLimit == NSOnState];
128    if (uploadSpeedLimit != INVALID)
129        [fUploadLimitField setIntValue: uploadSpeedLimit];
130    else
131        [fUploadLimitField setStringValue: @""];
132
133    //set download view
134    [fDownloadLimitCheck setState: downloadUseSpeedLimit];
135    [fDownloadLimitCheck setEnabled: YES];
136
137    [fDownloadLimitLabel setEnabled: downloadUseSpeedLimit == NSOnState];
138    [fDownloadLimitField setEnabled: downloadUseSpeedLimit == NSOnState];
139    if (downloadSpeedLimit != INVALID)
140        [fDownloadLimitField setIntValue: downloadSpeedLimit];
141    else
142        [fDownloadLimitField setStringValue: @""];
143
144    //set global check
145    [fGlobalLimitCheck setState: globalUseSpeedLimit];
146    [fGlobalLimitCheck setEnabled: YES];
147
148    //get ratio and idle info
149    enumerator = [fTorrents objectEnumerator];
150    torrent = [enumerator nextObject]; //first torrent
151
152    NSInteger checkRatio = [torrent ratioSetting], checkIdle = [torrent idleSetting],
153            removeWhenFinishSeeding = [torrent removeWhenFinishSeeding] ? NSOnState : NSOffState;
154    CGFloat ratioLimit = [torrent ratioLimit];
155    NSUInteger idleLimit = [torrent idleLimitMinutes];
156
157    while ((torrent = [enumerator nextObject])
158            && (checkRatio != INVALID || ratioLimit != INVALID || checkIdle != INVALID || idleLimit != INVALID))
159    {
160        if (checkRatio != INVALID && checkRatio != [torrent ratioSetting])
161            checkRatio = INVALID;
162
163        if (ratioLimit != INVALID && ratioLimit != [torrent ratioLimit])
164            ratioLimit = INVALID;
165
166        if (checkIdle != INVALID && checkIdle != [torrent idleSetting])
167            checkIdle = INVALID;
168
169        if (idleLimit != INVALID && idleLimit != [torrent idleLimitMinutes])
170            idleLimit = INVALID;
171
172        if (removeWhenFinishSeeding != NSMixedState && removeWhenFinishSeeding != ([torrent removeWhenFinishSeeding] ? NSOnState : NSOffState))
173            removeWhenFinishSeeding = NSMixedState;
174    }
175
176    //set ratio view
177    NSInteger index;
178    if (checkRatio == TR_RATIOLIMIT_SINGLE)
179        index = OPTION_POPUP_LIMIT;
180    else if (checkRatio == TR_RATIOLIMIT_UNLIMITED)
181        index = OPTION_POPUP_NO_LIMIT;
182    else if (checkRatio == TR_RATIOLIMIT_GLOBAL)
183        index = OPTION_POPUP_GLOBAL;
184    else
185        index = -1;
186    [fRatioPopUp selectItemAtIndex: index];
187    [fRatioPopUp setEnabled: YES];
188
189    [fRatioLimitField setHidden: checkRatio != TR_RATIOLIMIT_SINGLE];
190    if (ratioLimit != INVALID)
191        [fRatioLimitField setFloatValue: ratioLimit];
192    else
193        [fRatioLimitField setStringValue: @""];
194
195    [fRatioLimitGlobalLabel setHidden: checkRatio != TR_RATIOLIMIT_GLOBAL];
196
197    //set idle view
198    if (checkIdle == TR_IDLELIMIT_SINGLE)
199        index = OPTION_POPUP_LIMIT;
200    else if (checkIdle == TR_IDLELIMIT_UNLIMITED)
201        index = OPTION_POPUP_NO_LIMIT;
202    else if (checkIdle == TR_IDLELIMIT_GLOBAL)
203        index = OPTION_POPUP_GLOBAL;
204    else
205        index = -1;
206    [fIdlePopUp selectItemAtIndex: index];
207    [fIdlePopUp setEnabled: YES];
208
209    [fIdleLimitField setHidden: checkIdle != TR_IDLELIMIT_SINGLE];
210    if (idleLimit != INVALID)
211        [fIdleLimitField setIntegerValue: idleLimit];
212    else
213        [fIdleLimitField setStringValue: @""];
214    [fIdleLimitLabel setHidden: checkIdle != TR_IDLELIMIT_SINGLE];
215
216    [fIdleLimitGlobalLabel setHidden: checkIdle != TR_IDLELIMIT_GLOBAL];
217
218    //set remove transfer when seeding finishes
219    [fRemoveSeedingCompleteCheck setState: removeWhenFinishSeeding];
220    [fRemoveSeedingCompleteCheck setEnabled: YES];
221
222    //get priority info
223    enumerator = [fTorrents objectEnumerator];
224    torrent = [enumerator nextObject]; //first torrent
225
226    NSInteger priority = [torrent priority];
227
228    while ((torrent = [enumerator nextObject]) && priority != INVALID)
229    {
230        if (priority != INVALID && priority != [torrent priority])
231            priority = INVALID;
232    }
233
234    //set priority view
235    if (priority == TR_PRI_HIGH)
236        index = OPTION_POPUP_PRIORITY_HIGH;
237    else if (priority == TR_PRI_NORMAL)
238        index = OPTION_POPUP_PRIORITY_NORMAL;
239    else if (priority == TR_PRI_LOW)
240        index = OPTION_POPUP_PRIORITY_LOW;
241    else
242        index = -1;
243    [fPriorityPopUp selectItemAtIndex: index];
244    [fPriorityPopUp setEnabled: YES];
245
246    //get peer info
247    enumerator = [fTorrents objectEnumerator];
248    torrent = [enumerator nextObject]; //first torrent
249
250    NSInteger maxPeers = [torrent maxPeerConnect];
251
252    while ((torrent = [enumerator nextObject]))
253    {
254        if (maxPeers != [torrent maxPeerConnect])
255        {
256            maxPeers = INVALID;
257            break;
258        }
259    }
260
261    //set peer view
262    [fPeersConnectField setEnabled: YES];
263    [fPeersConnectLabel setEnabled: YES];
264    if (maxPeers != INVALID)
265        [fPeersConnectField setIntValue: maxPeers];
266    else
267        [fPeersConnectField setStringValue: @""];
268}
269
270- (void) setUseSpeedLimit: (id) sender
271{
272    const BOOL upload = sender == fUploadLimitCheck;
273
274    if ([(NSButton *)sender state] == NSMixedState)
275        [sender setState: NSOnState];
276    const BOOL limit = [(NSButton *)sender state] == NSOnState;
277
278    for (Torrent * torrent in fTorrents)
279        [torrent setUseSpeedLimit: limit upload: upload];
280
281    NSTextField * field = upload ? fUploadLimitField : fDownloadLimitField;
282    [field setEnabled: limit];
283    if (limit)
284    {
285        [field selectText: self];
286        [[[self view] window] makeKeyAndOrderFront: self];
287    }
288
289    NSTextField * label = upload ? fUploadLimitLabel : fDownloadLimitLabel;
290    [label setEnabled: limit];
291
292    [[NSNotificationCenter defaultCenter] postNotificationName: @"UpdateOptionsNotification" object: self];
293}
294
295- (void) setUseGlobalSpeedLimit: (id) sender
296{
297    if ([(NSButton *)sender state] == NSMixedState)
298        [sender setState: NSOnState];
299    const BOOL limit = [(NSButton *)sender state] == NSOnState;
300
301    for (Torrent * torrent in fTorrents)
302        [torrent setUseGlobalSpeedLimit: limit];
303
304    [[NSNotificationCenter defaultCenter] postNotificationName: @"UpdateOptionsNotification" object: self];
305}
306
307- (void) setSpeedLimit: (id) sender
308{
309    const BOOL upload = sender == fUploadLimitField;
310    const NSInteger limit = [sender intValue];
311
312    for (Torrent * torrent in fTorrents)
313        [torrent setSpeedLimit: limit upload: upload];
314
315    [[NSNotificationCenter defaultCenter] postNotificationName: @"UpdateOptionsNotification" object: self];
316}
317
318- (void) setRatioSetting: (id) sender
319{
320    NSInteger setting;
321    BOOL single = NO;
322    switch ([sender indexOfSelectedItem])
323    {
324        case OPTION_POPUP_LIMIT:
325            setting = TR_RATIOLIMIT_SINGLE;
326            single = YES;
327            break;
328        case OPTION_POPUP_NO_LIMIT:
329            setting = TR_RATIOLIMIT_UNLIMITED;
330            break;
331        case OPTION_POPUP_GLOBAL:
332            setting = TR_RATIOLIMIT_GLOBAL;
333            break;
334        default:
335            NSAssert1(NO, @"Unknown option selected in ratio popup: %ld", [sender indexOfSelectedItem]);
336            return;
337    }
338
339    for (Torrent * torrent in fTorrents)
340        [torrent setRatioSetting: setting];
341
342    [fRatioLimitField setHidden: !single];
343    if (single)
344    {
345        [fRatioLimitField selectText: self];
346        [[[self view] window] makeKeyAndOrderFront: self];
347    }
348
349    [fRatioLimitGlobalLabel setHidden: setting != TR_RATIOLIMIT_GLOBAL];
350
351    [[NSNotificationCenter defaultCenter] postNotificationName: @"UpdateOptionsNotification" object: self];
352}
353
354- (void) setRatioLimit: (id) sender
355{
356    const CGFloat limit = [sender floatValue];
357
358    for (Torrent * torrent in fTorrents)
359        [torrent setRatioLimit: limit];
360
361    [[NSNotificationCenter defaultCenter] postNotificationName: @"UpdateOptionsNotification" object: self];
362}
363
364- (void) setIdleSetting: (id) sender
365{
366    NSInteger setting;
367    BOOL single = NO;
368    switch ([sender indexOfSelectedItem])
369    {
370        case OPTION_POPUP_LIMIT:
371            setting = TR_IDLELIMIT_SINGLE;
372            single = YES;
373            break;
374        case OPTION_POPUP_NO_LIMIT:
375            setting = TR_IDLELIMIT_UNLIMITED;
376            break;
377        case OPTION_POPUP_GLOBAL:
378            setting = TR_IDLELIMIT_GLOBAL;
379            break;
380        default:
381            NSAssert1(NO, @"Unknown option selected in idle popup: %ld", [sender indexOfSelectedItem]);
382            return;
383    }
384
385    for (Torrent * torrent in fTorrents)
386        [torrent setIdleSetting: setting];
387
388    [fIdleLimitField setHidden: !single];
389    [fIdleLimitLabel setHidden: !single];
390    if (single)
391    {
392        [fIdleLimitField selectText: self];
393        [[[self view] window] makeKeyAndOrderFront: self];
394    }
395
396    [fIdleLimitGlobalLabel setHidden: setting != TR_IDLELIMIT_GLOBAL];
397
398    [[NSNotificationCenter defaultCenter] postNotificationName: @"UpdateOptionsNotification" object: self];
399}
400
401- (void) setIdleLimit: (id) sender
402{
403    const NSUInteger limit = [sender integerValue];
404
405    for (Torrent * torrent in fTorrents)
406        [torrent setIdleLimitMinutes: limit];
407
408    [[NSNotificationCenter defaultCenter] postNotificationName: @"UpdateOptionsNotification" object: self];
409}
410
411- (IBAction) setRemoveWhenSeedingCompletes: (id) sender
412{
413    if ([(NSButton *)sender state] == NSMixedState)
414        [sender setState: NSOnState];
415    const BOOL enable = [(NSButton *)sender state] == NSOnState;
416
417    for (Torrent * torrent in fTorrents)
418        [torrent setRemoveWhenFinishSeeding: enable];
419
420    [[NSNotificationCenter defaultCenter] postNotificationName: @"UpdateOptionsNotification" object: self];
421}
422
423- (void) setPriority: (id) sender
424{
425    tr_priority_t priority;
426    switch ([sender indexOfSelectedItem])
427    {
428        case OPTION_POPUP_PRIORITY_HIGH:
429            priority = TR_PRI_HIGH;
430            break;
431        case OPTION_POPUP_PRIORITY_NORMAL:
432            priority = TR_PRI_NORMAL;
433            break;
434        case OPTION_POPUP_PRIORITY_LOW:
435            priority = TR_PRI_LOW;
436            break;
437        default:
438            NSAssert1(NO, @"Unknown option selected in priority popup: %ld", [sender indexOfSelectedItem]);
439            return;
440    }
441
442    for (Torrent * torrent in fTorrents)
443        [torrent setPriority: priority];
444
445    [[NSNotificationCenter defaultCenter] postNotificationName: @"UpdateUI" object: nil];
446
447    [[NSNotificationCenter defaultCenter] postNotificationName: @"UpdateOptionsNotification" object: self];
448}
449
450- (void) setPeersConnectLimit: (id) sender
451{
452    NSInteger limit = [sender intValue];
453
454    for (Torrent * torrent in fTorrents)
455        [torrent setMaxPeerConnect: limit];
456
457    [[NSNotificationCenter defaultCenter] postNotificationName: @"UpdateOptionsNotification" object: self];
458}
459
460- (BOOL) control: (NSControl *) control textShouldBeginEditing: (NSText *) fieldEditor
461{
462    fInitialString = [control stringValue];
463
464    return YES;
465}
466
467- (BOOL) control: (NSControl *) control didFailToFormatString: (NSString *) string errorDescription: (NSString *) error
468{
469    NSBeep();
470    if (fInitialString)
471    {
472        [control setStringValue: fInitialString];
473        fInitialString = nil;
474    }
475    return NO;
476}
477
478@end
479
480@implementation InfoOptionsViewController (Private)
481
482- (void) setupInfo
483{
484    if ([fTorrents count] == 0)
485    {
486        [fUploadLimitCheck setEnabled: NO];
487        [fUploadLimitCheck setState: NSOffState];
488        [fUploadLimitField setEnabled: NO];
489        [fUploadLimitLabel setEnabled: NO];
490        [fUploadLimitField setStringValue: @""];
491
492        [fDownloadLimitCheck setEnabled: NO];
493        [fDownloadLimitCheck setState: NSOffState];
494        [fDownloadLimitField setEnabled: NO];
495        [fDownloadLimitLabel setEnabled: NO];
496        [fDownloadLimitField setStringValue: @""];
497
498        [fGlobalLimitCheck setEnabled: NO];
499        [fGlobalLimitCheck setState: NSOffState];
500
501        [fPriorityPopUp setEnabled: NO];
502        [fPriorityPopUp selectItemAtIndex: -1];
503
504        [fRatioPopUp setEnabled: NO];
505        [fRatioPopUp selectItemAtIndex: -1];
506        [fRatioLimitField setHidden: YES];
507        [fRatioLimitField setStringValue: @""];
508        [fRatioLimitGlobalLabel setHidden: YES];
509
510        [fIdlePopUp setEnabled: NO];
511        [fIdlePopUp selectItemAtIndex: -1];
512        [fIdleLimitField setHidden: YES];
513        [fIdleLimitField setStringValue: @""];
514        [fIdleLimitLabel setHidden: YES];
515        [fIdleLimitGlobalLabel setHidden: YES];
516
517        [fRemoveSeedingCompleteCheck setEnabled: NO];
518        [fRemoveSeedingCompleteCheck setState: NSOffState];
519
520        [fPeersConnectField setEnabled: NO];
521        [fPeersConnectField setStringValue: @""];
522        [fPeersConnectLabel setEnabled: NO];
523    }
524    else
525        [self updateOptions];
526}
527
528- (void) setGlobalLabels
529{
530    NSString * global = [[NSUserDefaults standardUserDefaults] boolForKey: @"RatioCheck"]
531        ? [NSString stringForRatio: [[NSUserDefaults standardUserDefaults] floatForKey: @"RatioLimit"]]
532        : NSLocalizedString(@"disabled", "Info options -> global setting");
533    [fRatioLimitGlobalLabel setStringValue: global];
534
535    //idle field
536    NSString * globalIdle;
537    if ([[NSUserDefaults standardUserDefaults] boolForKey: @"IdleLimitCheck"])
538    {
539        const NSInteger globalMin = [[NSUserDefaults standardUserDefaults] integerForKey: @"IdleLimitMinutes"];
540        globalIdle = globalMin == 1 ? NSLocalizedString(@"1 minute", "Info options -> global setting")
541            : [NSString localizedStringWithFormat: NSLocalizedString(@"%d minutes", "Info options -> global setting"), globalMin];
542    }
543    else
544        globalIdle = NSLocalizedString(@"disabled", "Info options -> global setting");
545    [fIdleLimitGlobalLabel setStringValue: globalIdle];
546}
547
548- (void) updateOptionsNotification: (NSNotification *) notification
549{
550    if ([notification object] != self)
551        [self updateOptions];
552}
553
554@end
555