Merged [13645] and [13646]: Fixed one of the longest-standing Adium bugs: The Color...
[adiumx.git] / Source / ESFileTransferProgressRow.m
blobdbead559eb57dfc90eec06474d899d73ee022bbd
1 /* 
2  * Adium is the legal property of its developers, whose names are listed in the copyright file included
3  * with this source distribution.
4  * 
5  * This program is free software; you can redistribute it and/or modify it under the terms of the GNU
6  * General Public License as published by the Free Software Foundation; either version 2 of the License,
7  * or (at your option) any later version.
8  * 
9  * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
10  * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General
11  * Public License for more details.
12  * 
13  * You should have received a copy of the GNU General Public License along with this program; if not,
14  * write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
15  */
17 #import "ESFileTransferProgressRow.h"
18 #import "ESFileTransferProgressView.h"
19 #import "ESFileTransferProgressWindowController.h"
20 #import <AIUtilities/AIMenuAdditions.h>
21 #import <Adium/AIListObject.h>
22 #import <Adium/AIUserIcons.h>
23 #import <Adium/ESFileTransfer.h>
25 #define BYTES_RECEIVED          [NSString stringWithFormat:AILocalizedString(@"%@ received","(a bytes string) received"),bytesString]
26 #define BYTES_SENT                      [NSString stringWithFormat:AILocalizedString(@"%@ sent","(a bytes string) sent"),bytesString]
28 @interface ESFileTransferProgressRow (PRIVATE)
29 - (NSString *)stringForSize:(unsigned long long)size;
30 - (NSString *)stringForSize:(unsigned long long)inSize of:(unsigned long long)totalSize ofString:(NSString *)totalSizeString;
31 - (NSString *)readableTimeForSecs:(NSTimeInterval)secs inLongFormat:(BOOL)longFormat;
32 - (id)initForFileTransfer:(ESFileTransfer *)inFileTransfer withOwner:(id)owner;
33 - (void)updateIconImage;
34 - (void)updateSourceAndDestination;
35 @end
37 @implementation ESFileTransferProgressRow
39 + (ESFileTransferProgressRow *)rowForFileTransfer:(ESFileTransfer *)inFileTransfer withOwner:(id)inOwner
41         return([[[ESFileTransferProgressRow alloc] initForFileTransfer:inFileTransfer withOwner:inOwner] autorelease]);
44 - (id)initForFileTransfer:(ESFileTransfer *)inFileTransfer withOwner:(id)inOwner
46         if((self = [super init])) {
47                 sizeString = nil;
48                 forceUpdate = NO;
50                 fileTransfer = [inFileTransfer retain];
51                 [fileTransfer setDelegate:self];
53                 owner = inOwner;
55                 [NSBundle loadNibNamed:@"ESFileTransferProgressView" owner:self];
56         }
57         
58         return(self);
61 - (void)dealloc
63         owner = nil;
64         [fileTransfer setDelegate:nil];
65         [fileTransfer release];
66         [view release]; view = nil;
67         [sizeString release]; sizeString = nil;
69         [super dealloc];
72 - (ESFileTransfer *)fileTransfer
74         return(fileTransfer);
77 - (ESFileTransferProgressView *)view
78 {       
79         return(view);
82 - (void)awakeFromNib
83 {       
84         //If we already know something about this file transfer, update since we missed delegate calls
85         [self fileTransfer:fileTransfer didSetSize:[fileTransfer size]];
86         [self fileTransfer:fileTransfer didSetLocalFilename:[fileTransfer localFilename]];
87         [self fileTransfer:fileTransfer didSetType:[fileTransfer type]];
89         //This always calls gotUpdate and display, so do it last
90         [self fileTransfer:fileTransfer didSetStatus:[fileTransfer status]];
92         //Once we've set up some basic information, tell our owner it can add the view
93         [owner progressRowDidAwakeFromNib:self];
94         /*
95         [self performSelector:@selector(informOfAwakefromNib)
96                            withObject:nil
97                            afterDelay:0.000001];
98          */
101 - (void)informOfAwakefromNib
103         //Once we've set up some basic information, tell our owner it can add the view
104         [owner progressRowDidAwakeFromNib:self];
108 - (void)fileTransfer:(ESFileTransfer *)inFileTransfer didSetType:(FileTransferType)type
110         [self updateSourceAndDestination];
111         [self updateIconImage];
112         
113         [owner progressRowDidChangeType:self];
116 - (FileTransferType)type
118         return([fileTransfer type]);
121 - (void)fileTransfer:(ESFileTransfer *)inFileTransfer didSetSize:(unsigned long long)inSize
123         size = inSize;
124         
125         [sizeString release];
126         sizeString = [[[adium fileTransferController] stringForSize:size] retain];
129 - (void)fileTransfer:(ESFileTransfer *)inFileTransfer didSetLocalFilename:(NSString *)inLocalFilename
131         NSString        *filename = [inLocalFilename lastPathComponent];
132         
133         //If we don't have a local file name, try to use the remote file name.
134         if(!filename) filename = [[inFileTransfer remoteFilename] lastPathComponent];
135         
136         [view setFileName:filename];
138         [self updateIconImage];
141 - (void)fileTransfer:(ESFileTransfer *)inFileTransfer didSetStatus:(FileTransferStatus)inStatus
143         forceUpdate = YES;
144         [self gotUpdateForFileTransfer:inFileTransfer];
145         
146         [view setAllowsCancel:![inFileTransfer isStopped]];
147         [owner progressRowDidChangeStatus:self];
148         
149         [[view window] display];
150         forceUpdate = NO;
153 //Handle progress, bytes transferred/bytes total, rate, and time remaining
154 - (void)gotUpdateForFileTransfer:(ESFileTransfer *)inFileTransfer
156         UInt32                          updateTick = TickCount();
157         FileTransferStatus      status = [inFileTransfer status];
158         
159         //Don't update continously; on a LAN transfer, for instance, we'll get almost constant updates
160         if(lastUpdateTick && (((updateTick - lastUpdateTick) / 60.0) < 0.2) && (status == In_Progress_FileTransfer) && !forceUpdate){
161                 return;
162         }
164         unsigned long long      bytesSent = [inFileTransfer bytesSent];
165         NSString                        *transferBytesStatus = nil, *transferSpeedStatus = nil, *transferRemainingStatus = nil;
166         FileTransferType        type = [inFileTransfer type];
167         
168         if(!size){
169                 size = [inFileTransfer size];
170                 
171                 [sizeString release];
172                 sizeString = [[[adium fileTransferController] stringForSize:size] retain];              
173         }
175         switch(status){
176                 case Unknown_Status_FileTransfer:
177                 case Not_Started_FileTransfer:
178                 case Accepted_FileTransfer:
179                         [view setProgressIndeterminate:YES];
180                         [view setProgressAnimation:YES];
181                         transferSpeedStatus = AILocalizedString(@"Waiting to start.","waiting to begin a file transfer status");
182                         
183                         break;
184                 case In_Progress_FileTransfer:
185                         [view setProgressIndeterminate:NO];
186                         [view setProgressDoubleValue:[inFileTransfer percentDone]];
187                         break;
188                 case Complete_FileTransfer:
189                         [view setProgressVisible:NO];
190                         transferSpeedStatus = AILocalizedString(@"Complete",nil);
191                         break;
192                 case Canceled_Local_FileTransfer:
193                 case Canceled_Remote_FileTransfer:
194                         [view setProgressVisible:NO];
195                         transferSpeedStatus = AILocalizedString(@"Stopped",nil);
196                         break;
197         }
199         if(type == Unknown_FileTransfer || status == Unknown_Status_FileTransfer || status == Not_Started_FileTransfer){
200                 transferBytesStatus = [NSString stringWithFormat:AILocalizedString(@"Initiating file transfer...",nil)];                
201         }else{          
202                 switch(status){
203                         case Accepted_FileTransfer:
204                                 transferBytesStatus = [NSString stringWithFormat:AILocalizedString(@"Accepted file transfer...",nil)];          
205                         break;
206                         case In_Progress_FileTransfer:
207                         {
208                                 NSString                        *bytesString = [[adium fileTransferController] stringForSize:bytesSent
209                                                                                                                                                                                           of:size
210                                                                                                                                                                                 ofString:sizeString];
212                                 switch(type){
213                                         case Incoming_FileTransfer:
214                                                 transferBytesStatus = BYTES_RECEIVED;
215                                                 break;
216                                         case Outgoing_FileTransfer:
217                                                 transferBytesStatus = BYTES_SENT;
218                                                 break;
219                                         default:
220                                                 break;
221                                 }
222                                 
223                                 break;
224                         }
225                         case Complete_FileTransfer:
226                         {
227                                 NSString                        *bytesString = [[adium fileTransferController] stringForSize:bytesSent];
228                                 switch(type){
229                                         case Incoming_FileTransfer:
230                                                 transferBytesStatus = BYTES_RECEIVED;
231                                                 break;
232                                         case Outgoing_FileTransfer:
233                                                 transferBytesStatus = BYTES_SENT;
234                                                 break;
235                                         default:
236                                                 break;
237                                 }
238                                 
239                                 break;
240                         }
241                         case Canceled_Local_FileTransfer:
242                                 transferBytesStatus = AILocalizedString(@"Canceled","File transfer canceled locally status description");
243                                 break;
244                         case Canceled_Remote_FileTransfer:
245                                 transferBytesStatus = AILocalizedString(@"Remote contact canceled","File transfer canceled remotely status description");
246                                 break;
247                         default: 
248                                 break;
249                 }
250         }
251         
252         if((status == In_Progress_FileTransfer) && lastUpdateTick && lastBytesSent){
253                 if(updateTick != lastUpdateTick){
254                         unsigned long long      rate;
255                         
256                         rate = ((bytesSent - lastBytesSent) / ((updateTick - lastUpdateTick) / 60.0));
257                         transferSpeedStatus = [NSString stringWithFormat:AILocalizedString(@"%@ per sec.",nil),[[adium fileTransferController] stringForSize:rate]];
258                         
259                         if(rate > 0){
260                                 unsigned long long secsRemaining = ((size - bytesSent) / rate);
261                                 transferRemainingStatus = [NSString stringWithFormat:AILocalizedString(@"%@ remaining.",nil),[self readableTimeForSecs:secsRemaining inLongFormat:YES]];
262                                 
263                         }else{
264                                 transferRemainingStatus = AILocalizedString(@"Stalled","file transfer is stalled status message");
265                         }
266                 }
267         }
268         
269         [view setTransferBytesStatus:transferBytesStatus
270                                  remainingStatus:transferRemainingStatus
271                                          speedStatus:transferSpeedStatus];
272         [view setNeedsDisplay:YES];
274         lastBytesSent = bytesSent;
275         lastUpdateTick = updateTick;
278 - (void)updateIconImage
280         NSImage *iconImage;
282         if(iconImage = [fileTransfer iconImage]){
283                 [view setIconImage:iconImage];          
284         }
287 - (void)updateSourceAndDestination
288 {       
289         AIListObject    *source = [fileTransfer source];
290         AIListObject    *destination = [fileTransfer destination];
291         
292         [view setSourceName:[source formattedUID]];
293         [view setSourceIcon:[AIUserIcons menuUserIconForObject:source]];
294         
295         [view setDestinationName:[destination formattedUID]];
296         [view setDestinationIcon:[AIUserIcons menuUserIconForObject:destination]];
299 //Button actions
300 #pragma mark Button actions
301 - (IBAction)stopResumeAction:(id)sender
303         [fileTransfer cancel];
305 - (IBAction)revealAction:(id)sender
307         [fileTransfer reveal];  
309 - (IBAction)openFileAction:(id)sender
311         if([fileTransfer status] == Complete_FileTransfer){
312                 [fileTransfer openFile];
313         }
315 - (void)removeRowAction:(id)sender
317         if([fileTransfer isStopped]){
318                 [owner _removeFileTransferRow:self];
319         }
322 #pragma mark Contextual menu
323 - (NSMenu *)menuForEvent:(NSEvent *)theEvent
325         NSMenu          *contextualMenu = [[NSMenu allocWithZone:[NSMenu menuZone]] init];
326         NSMenuItem  *menuItem;
327         
328         //Allow open and show in finder on complete incoming transfers and all outgoing transfers
329         if(([fileTransfer status] == Complete_FileTransfer) ||
330            ([fileTransfer type] == Outgoing_FileTransfer)){
331                 menuItem = [[[NSMenuItem allocWithZone:[NSMenu menuZone]] initWithTitle:AILocalizedString(@"Open",nil)
332                                                                                                                                                  target:self
333                                                                                                                                                  action:@selector(openFileAction:)
334                                                                                                                                   keyEquivalent:@""] autorelease];
335                 [contextualMenu addItem:menuItem];
337                 menuItem = [[[NSMenuItem allocWithZone:[NSMenu menuZone]] initWithTitle:AILocalizedString(@"Show in Finder",nil)
338                                                                                                                                                  target:self
339                                                                                                                                                  action:@selector(revealAction:)
340                                                                                                                                   keyEquivalent:@""] autorelease];
341                 [contextualMenu addItem:menuItem];
342                 
343         }       
345         if([fileTransfer isStopped]){
346                 menuItem = [[[NSMenuItem allocWithZone:[NSMenu menuZone]] initWithTitle:AILocalizedString(@"Remove from List",nil)
347                                                                                                                                                  target:self
348                                                                                                                                                  action:@selector(removeRowAction:)
349                                                                                                                                   keyEquivalent:@""] autorelease];
350                 [contextualMenu addItem:menuItem];      
351         }else{
352                 menuItem = [[[NSMenuItem allocWithZone:[NSMenu menuZone]] initWithTitle:AILocalizedString(@"Cancel",nil)
353                                                                                                                                                  target:self
354                                                                                                                                                  action:@selector(stopResumeAction:)
355                                                                                                                                   keyEquivalent:@""] autorelease];
356                 [contextualMenu addItem:menuItem];
357         }       
358         
359         return([contextualMenu autorelease]);
362 //Pass height change information on to our owner
363 - (void)fileTransferProgressView:(ESFileTransferProgressView *)inView
364                            heightChangedFrom:(float)oldHeight
365                                                          to:(float)newHeight
367         [owner fileTransferProgressRow:self
368                                  heightChangedFrom:oldHeight
369                                                                 to:newHeight];
372 #pragma mark Localized readable values
373 //From Colloquy
374 - (NSString *)readableTimeForSecs:(NSTimeInterval)secs inLongFormat:(BOOL)longFormat
376         unsigned int i = 0, stop = 0;
377         NSDictionary *desc = [NSDictionary dictionaryWithObjectsAndKeys:AILocalizedString( @"second", "singular second" ), [NSNumber numberWithUnsignedInt:1], AILocalizedString( @"minute", "singular minute" ), [NSNumber numberWithUnsignedInt:60], AILocalizedString( @"hour", "singular hour" ), [NSNumber numberWithUnsignedInt:3600], AILocalizedString( @"day", "singular day" ), [NSNumber numberWithUnsignedInt:86400], AILocalizedString( @"week", "singular week" ), [NSNumber numberWithUnsignedInt:604800], AILocalizedString( @"month", "singular month" ), [NSNumber numberWithUnsignedInt:2628000], AILocalizedString( @"year", "singular year" ), [NSNumber numberWithUnsignedInt:31536000], nil];
378         NSDictionary *plural = [NSDictionary dictionaryWithObjectsAndKeys:AILocalizedString( @"seconds", "plural seconds" ), [NSNumber numberWithUnsignedInt:1], AILocalizedString( @"minutes", "plural minutes" ), [NSNumber numberWithUnsignedInt:60], AILocalizedString( @"hours", "plural hours" ), [NSNumber numberWithUnsignedInt:3600], AILocalizedString( @"days", "plural days" ), [NSNumber numberWithUnsignedInt:86400], AILocalizedString( @"weeks", "plural weeks" ), [NSNumber numberWithUnsignedInt:604800], AILocalizedString( @"months", "plural months" ), [NSNumber numberWithUnsignedInt:2628000], AILocalizedString( @"years", "plural years" ), [NSNumber numberWithUnsignedInt:31536000], nil];
379         NSDictionary *use = nil;
380         NSMutableArray *breaks = nil;
381         unsigned int val = 0.;
382         NSString *retval = nil;
383         
384         if( secs < 0 ) secs *= -1;
385         
386         breaks = [[[desc allKeys] mutableCopy] autorelease];
387         [breaks sortUsingSelector:@selector( compare: )];
388         
389         while( i < [breaks count] && secs >= (NSTimeInterval) [[breaks objectAtIndex:i] unsignedIntValue] ) i++;
390         if( i > 0 ) i--;
391         stop = [[breaks objectAtIndex:i] unsignedIntValue];
392         
393         val = (unsigned int) ( secs / stop );
394         use = ( val > 1 ? plural : desc );
395         retval = [NSString stringWithFormat:@"%d %@", val, [use objectForKey:[NSNumber numberWithUnsignedInt:stop]]];
396         if( longFormat && i > 0 ) {
397                 unsigned int rest = (unsigned int) ( (unsigned int) secs % stop );
398                 stop = [[breaks objectAtIndex:--i] unsignedIntValue];
399                 rest = (unsigned int) ( rest / stop );
400                 if( rest > 0 ) {
401                         use = ( rest > 1 ? plural : desc );
402                         retval = [retval stringByAppendingFormat:@" %d %@", rest, [use objectForKey:[breaks objectAtIndex:i]]];
403                 }
404         }
405         
406         return(retval);
409 @end