1/****************************************************************************** 2 * Copyright (c) 2007-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 "StatsWindowController.h" 24#import "Controller.h" 25#import "NSApplicationAdditions.h" 26#import "NSStringAdditions.h" 27 28#define UPDATE_SECONDS 1.0 29 30@interface StatsWindowController (Private) 31 32- (void) updateStats; 33 34- (void) performResetStats; 35- (void) resetSheetClosed: (NSAlert *) alert returnCode: (NSInteger) code contextInfo: (void *) info; 36 37@end 38 39@implementation StatsWindowController 40 41StatsWindowController * fStatsWindowInstance = nil; 42tr_session * fLib = NULL; 43+ (StatsWindowController *) statsWindow 44{ 45 if (!fStatsWindowInstance) 46 { 47 if ((fStatsWindowInstance = [[self alloc] init])) 48 { 49 fLib = [(Controller *)[NSApp delegate] sessionHandle]; 50 } 51 } 52 return fStatsWindowInstance; 53} 54 55- (id) init 56{ 57 return [super initWithWindowNibName: @"StatsWindow"]; 58} 59 60- (void) awakeFromNib 61{ 62 [self updateStats]; 63 64 fTimer = [NSTimer scheduledTimerWithTimeInterval: UPDATE_SECONDS target: self selector: @selector(updateStats) userInfo: nil repeats: YES]; 65 [[NSRunLoop currentRunLoop] addTimer: fTimer forMode: NSModalPanelRunLoopMode]; 66 [[NSRunLoop currentRunLoop] addTimer: fTimer forMode: NSEventTrackingRunLoopMode]; 67 68 [[self window] setRestorationClass: [self class]]; 69 70 [[self window] setTitle: NSLocalizedString(@"Statistics", "Stats window -> title")]; 71 72 //set label text 73 [fUploadedLabelField setStringValue: [NSLocalizedString(@"Uploaded", "Stats window -> label") stringByAppendingString: @":"]]; 74 [fDownloadedLabelField setStringValue: [NSLocalizedString(@"Downloaded", "Stats window -> label") stringByAppendingString: @":"]]; 75 [fRatioLabelField setStringValue: [NSLocalizedString(@"Ratio", "Stats window -> label") stringByAppendingString: @":"]]; 76 [fTimeLabelField setStringValue: [NSLocalizedString(@"Running Time", "Stats window -> label") stringByAppendingString: @":"]]; 77 [fNumOpenedLabelField setStringValue: [NSLocalizedString(@"Program Started", "Stats window -> label") stringByAppendingString: @":"]]; 78 79 //size of all labels 80 const CGFloat oldWidth = [fUploadedLabelField frame].size.width; 81 82 NSArray * labels = @[fUploadedLabelField, fDownloadedLabelField, fRatioLabelField, fTimeLabelField, fNumOpenedLabelField]; 83 84 CGFloat maxWidth = CGFLOAT_MIN; 85 for (NSTextField * label in labels) 86 { 87 [label sizeToFit]; 88 89 const CGFloat width = [label frame].size.width; 90 maxWidth = MAX(maxWidth, width); 91 } 92 93 for (NSTextField * label in labels) 94 { 95 NSRect frame = [label frame]; 96 frame.size.width = maxWidth; 97 [label setFrame: frame]; 98 } 99 100 //resize window for new label width - fields are set in nib to adjust correctly 101 NSRect windowRect = [[self window] frame]; 102 windowRect.size.width += maxWidth - oldWidth; 103 [[self window] setFrame: windowRect display: YES]; 104 105 //resize reset button 106 const CGFloat oldButtonWidth = [fResetButton frame].size.width; 107 108 [fResetButton setTitle: NSLocalizedString(@"Reset", "Stats window -> reset button")]; 109 [fResetButton sizeToFit]; 110 111 NSRect buttonFrame = [fResetButton frame]; 112 buttonFrame.size.width += 10.0; 113 buttonFrame.origin.x -= buttonFrame.size.width - oldButtonWidth; 114 [fResetButton setFrame: buttonFrame]; 115} 116 117- (void) windowWillClose: (id) sender 118{ 119 [fTimer invalidate]; 120 fTimer = nil; 121 fStatsWindowInstance = nil; 122} 123 124+ (void) restoreWindowWithIdentifier: (NSString *) identifier state: (NSCoder *) state completionHandler: (void (^)(NSWindow *, NSError *)) completionHandler 125{ 126 NSAssert1([identifier isEqualToString: @"StatsWindow"], @"Trying to restore unexpected identifier %@", identifier); 127 128 completionHandler([[StatsWindowController statsWindow] window], nil); 129} 130 131- (void) resetStats: (id) sender 132{ 133 if (![[NSUserDefaults standardUserDefaults] boolForKey: @"WarningResetStats"]) 134 { 135 [self performResetStats]; 136 return; 137 } 138 139 NSAlert * alert = [[NSAlert alloc] init]; 140 [alert setMessageText: NSLocalizedString(@"Are you sure you want to reset usage statistics?", "Stats reset -> title")]; 141 [alert setInformativeText: NSLocalizedString(@"This will clear the global statistics displayed by Transmission." 142 " Individual transfer statistics will not be affected.", "Stats reset -> message")]; 143 [alert setAlertStyle: NSWarningAlertStyle]; 144 [alert addButtonWithTitle: NSLocalizedString(@"Reset", "Stats reset -> button")]; 145 [alert addButtonWithTitle: NSLocalizedString(@"Cancel", "Stats reset -> button")]; 146 [alert setShowsSuppressionButton: YES]; 147 148 [alert beginSheetModalForWindow: [self window] modalDelegate: self 149 didEndSelector: @selector(resetSheetClosed:returnCode:contextInfo:) contextInfo: nil]; 150} 151 152- (NSString *) windowFrameAutosaveName 153{ 154 return @"StatsWindow"; 155} 156 157@end 158 159@implementation StatsWindowController (Private) 160 161- (void) updateStats 162{ 163 tr_session_stats statsAll, statsSession; 164 tr_sessionGetCumulativeStats(fLib, &statsAll); 165 tr_sessionGetStats(fLib, &statsSession); 166 167 NSByteCountFormatter * byteFormatter = [[NSByteCountFormatter alloc] init]; 168 [byteFormatter setAllowedUnits: NSByteCountFormatterUseBytes]; 169 170 [fUploadedField setStringValue: [NSString stringForFileSize: statsSession.uploadedBytes]]; 171 [fUploadedField setToolTip: [byteFormatter stringFromByteCount: statsSession.uploadedBytes]]; 172 [fUploadedAllField setStringValue: [NSString stringWithFormat: NSLocalizedString(@"%@ total", "stats total"), [NSString stringForFileSize: statsAll.uploadedBytes]]]; 173 [fUploadedAllField setToolTip: [byteFormatter stringFromByteCount: statsAll.uploadedBytes]]; 174 175 [fDownloadedField setStringValue: [NSString stringForFileSize: statsSession.downloadedBytes]]; 176 [fDownloadedField setToolTip: [byteFormatter stringFromByteCount: statsSession.downloadedBytes]]; 177 [fDownloadedAllField setStringValue: [NSString stringWithFormat: NSLocalizedString(@"%@ total", "stats total"), [NSString stringForFileSize: statsAll.downloadedBytes]]]; 178 [fDownloadedAllField setToolTip: [byteFormatter stringFromByteCount: statsAll.downloadedBytes]]; 179 180 181 [fRatioField setStringValue: [NSString stringForRatio: statsSession.ratio]]; 182 183 NSString * totalRatioString = statsAll.ratio != TR_RATIO_NA 184 ? [NSString stringWithFormat: NSLocalizedString(@"%@ total", "stats total"), [NSString stringForRatio: statsAll.ratio]] 185 : NSLocalizedString(@"Total N/A", "stats total"); 186 [fRatioAllField setStringValue: totalRatioString]; 187 188 if ([NSApp isOnYosemiteOrBetter]) { 189 static NSDateComponentsFormatter *timeFormatter; 190 static dispatch_once_t onceToken; 191 dispatch_once(&onceToken, ^{ 192 timeFormatter = [NSDateComponentsFormatter new]; 193 timeFormatter.unitsStyle = NSDateComponentsFormatterUnitsStyleFull; 194 timeFormatter.maximumUnitCount = 3; 195 timeFormatter.allowedUnits = NSCalendarUnitYear | NSCalendarUnitMonth | NSCalendarUnitWeekOfMonth | NSCalendarUnitDay | NSCalendarUnitHour | NSCalendarUnitMinute; 196 }); 197 198 [fTimeField setStringValue: [timeFormatter stringFromTimeInterval:statsSession.secondsActive]]; 199 [fTimeAllField setStringValue: [NSString stringWithFormat: NSLocalizedString(@"%@ total", "stats total"), [timeFormatter stringFromTimeInterval:statsAll.secondsActive]]]; 200 } 201 else { 202 [fTimeField setStringValue: [NSString timeString: statsSession.secondsActive includesTimeRemainingPhrase:NO showSeconds: NO]]; 203 [fTimeAllField setStringValue: [NSString stringWithFormat: NSLocalizedString(@"%@ total", "stats total"), [NSString timeString: statsAll.secondsActive includesTimeRemainingPhrase:NO showSeconds: NO]]]; 204 } 205 206 if (statsAll.sessionCount == 1) 207 [fNumOpenedField setStringValue: NSLocalizedString(@"1 time", "stats window -> times opened")]; 208 else 209 [fNumOpenedField setStringValue: [NSString stringWithFormat: NSLocalizedString(@"%@ times", "stats window -> times opened"), [NSString formattedUInteger: statsAll.sessionCount]]]; 210} 211 212- (void) performResetStats 213{ 214 tr_sessionClearStats(fLib); 215 [self updateStats]; 216} 217 218- (void) resetSheetClosed: (NSAlert *) alert returnCode: (NSInteger) code contextInfo: (void *) info 219{ 220 [[alert window] orderOut: nil]; 221 222 if ([[alert suppressionButton] state] == NSOnState) 223 [[NSUserDefaults standardUserDefaults] setBool: NO forKey: @"WarningResetStats"]; 224 225 if (code == NSAlertFirstButtonReturn) 226 [self performResetStats]; 227} 228 229@end 230