1/* 2** MailboxManagerController.m 3** 4** Copyright (C) 2001-2007 Ludovic Marcotte 5** Copyright (c) 2017-2018 Riccardo Mottola 6** 7** Author: Ludovic Marcotte <ludovic@Sophos.ca> 8** Riccardo Mottola <rm@gnu.org> 9** 10** This program is free software; you can redistribute it and/or modify 11** it under the terms of the GNU General Public License as published by 12** the Free Software Foundation; either version 2 of the License, or 13** (at your option) any later version. 14** 15** This program is distributed in the hope that it will be useful, 16** but WITHOUT ANY WARRANTY; without even the implied warranty of 17** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18** GNU General Public License for more details. 19** 20** You should have received a copy of the GNU General Public License 21** along with this program. If not, see <http://www.gnu.org/licenses/>. 22*/ 23 24#import "MailboxManagerController.h" 25 26#import "ConsoleWindowController.h" 27#import "Constants.h" 28#import "EditWindowController.h" 29#import "ExtendedMenuItem.h" 30#import "ExtendedOutlineView.h" 31#import "Filter.h" 32#import "FilterManager.h" 33#import "GNUMail.h" 34#import "Task.h" 35#import "TaskManager.h" 36 37#ifndef MACOSX 38#import "ImageTextCell.h" 39#import "MailboxManager.h" 40#endif 41 42#import "FolderNode.h" 43#import "MailboxManagerCache.h" 44#import "MailWindowController.h" 45#import "MessageViewWindowController.h" 46#import "NewMailboxPanelController.h" 47#import "NSUserDefaults+Extensions.h" 48#import "Utilities.h" 49 50#import <Pantomime/CWConstants.h> 51#import <Pantomime/CWFlags.h> 52#import <Pantomime/CWFolderInformation.h> 53#import <Pantomime/CWIMAPCacheManager.h> 54#import <Pantomime/CWIMAPFolder.h> 55#import <Pantomime/CWIMAPStore.h> 56#import <Pantomime/CWLocalFolder.h> 57#import <Pantomime/CWLocalStore.h> 58#import <Pantomime/CWMessage.h> 59#import <Pantomime/CWTCPConnection.h> 60#import <Pantomime/CWURLName.h> 61#import <Pantomime/CWVirtualFolder.h> 62#import <Pantomime/NSData+Extensions.h> 63#import <Pantomime/NSString+Extensions.h> 64 65#include <limits.h> 66 67#define SET_DRAFTS 0 68#define SET_SENT 1 69#define SET_TRASH 2 70#define TAKE_OFFLINE 0x100 71 72#define UPDATE_PATH(name, theOldPath, thePath) ({ \ 73 if ([[allValues objectForKey: name] isEqualToString: theOldPath]) \ 74 { \ 75 [allValues setObject: thePath forKey: name]; \ 76 } \ 77}) 78 79static MailboxManagerController *singleInstance = nil; 80 81 82// 83// Private methods 84// 85@interface MailboxManagerController (Private) 86- (void) _accountsHaveChanged: (id) sender; 87- (BOOL) _deletingDefaultMailbox: (NSString **) theMailboxName 88 usingURLNameAsString: (NSString *) theURLNameAsString; 89- (void) _folderCreateCompleted: (NSNotification *) theNotification; 90- (void) _folderCreateFailed: (NSNotification *) theNotification; 91- (void) _folderDeleteCompleted: (NSNotification *) theNotification; 92- (void) _folderDeleteFailed: (NSNotification *) theNotification; 93- (void) _folderRenameCompleted: (NSNotification *) theNotification; 94- (void) _folderRenameFailed: (NSNotification *) theNotification; 95- (void) _folderSubscribeCompleted: (NSNotification *) theNotification; 96- (void) _folderUnsubscribeCompleted: (NSNotification *) theNotification; 97- (BOOL) _initializeIMAPStoreWithAccountName: (NSString *) theAccountName; 98- (void) _nbOfMessages: (NSUInteger *) theNbOfMessages 99 nbOfUnreadMessages: (NSUInteger *) theNbOfUnreadMessages 100 forItem: (id) theItem; 101- (void) _openLocalFolderWithName: (NSString *) theFolderName 102 sender: (id) theSender; 103- (void) _openIMAPFolderWithName: (NSString *) theFolderName 104 store: (CWIMAPStore *) theStore 105 sender: (id) theSender; 106- (void) _reloadFoldersAndExpandParentsFromNode: (FolderNode *) theNode 107 selectNodeWithPath: (NSString *) thePath; 108- (NSString *) _stringValueOfURLNameFromItem: (id) theItem 109 store: (CWStore **) theStore; 110- (void) _updateMailboxesFromOldPath: (NSString *) theOldPath 111 toPath: (NSString *) thePath; 112- (void) _updateContextMenu; 113@end 114 115// 116// Here's how it does work: 117// 118// _allFolders (NSArray) -> localNodes (FolderNode * - name == _("Local")) 119// 120// -> IMAP FolderNode 1 (FoderNode * - name == "username @ imap.server1.com") 121// 122// -> IMAP FolderNode 2 (FoderNode * - name == "username @ imap.server2.com") 123// 124// -> ... 125// 126// 127@implementation MailboxManagerController 128 129#ifndef MACOSX 130- (id) initWithWindowNibName: (NSString *) windowNibName 131{ 132 NSToolbar *aToolbar; 133 id aCell; 134 135 MailboxManager *theWindow; 136 137 if ([[NSUserDefaults standardUserDefaults] integerForKey: @"PreferredViewStyle" default: GNUMailDrawerView] == GNUMailDrawerView) 138 { 139 self = [super init]; 140 [self windowDidLoad]; 141 return self; 142 } 143 144 theWindow = [[MailboxManager alloc] initWithContentRect: NSMakeRect(200,200,220,300) 145 styleMask: NSClosableWindowMask|NSTitledWindowMask|NSMiniaturizableWindowMask|NSResizableWindowMask 146 backing: NSBackingStoreBuffered 147 defer: YES]; 148 149 self = [super initWithWindow: theWindow]; 150 151 [theWindow layoutWindow]; 152 [theWindow setDelegate: self]; 153 154 // We link our outlets 155 outlineView = theWindow->outlineView; 156 scrollView = theWindow->scrollView; 157 RELEASE(theWindow); 158 159 // We set the title of our window (causing it to be loaded under OS X) 160 [[self window] setTitle: _(@"Mailboxes")]; 161 162 aToolbar = [[NSToolbar alloc] initWithIdentifier: @"MailboxManagerToolbar"]; 163 [aToolbar setDelegate: self]; 164 [aToolbar setAllowsUserCustomization: YES]; 165 [aToolbar setAutosavesConfiguration: YES]; 166 [[self window] setToolbar: aToolbar]; 167 RELEASE(aToolbar); 168 169 // We now set our data cell for the "Mailbox" column 170 aCell = [[ImageTextCell alloc] init]; 171 [[outlineView tableColumnWithIdentifier: @"Mailbox"] setDataCell: aCell]; 172 AUTORELEASE(aCell); 173 174 // We register the outline view for dragged types 175 [outlineView registerForDraggedTypes: [NSArray arrayWithObject: MessagePboardType]]; 176 177 // We set our autosave window frame name and restore the one from the user's defaults. 178 [[self window] setFrameAutosaveName: @"MailboxManager"]; 179 [[self window] setFrameUsingName: @"MailboxManager"]; 180 181 // We set our autosave name for our outline view 182 [outlineView setAutosaveName: @"MailboxManager"]; 183 [outlineView setAutosaveTableColumns: YES]; 184 185 // We set our outline view background color 186 if ([[NSUserDefaults standardUserDefaults] colorForKey: @"MAILBOXMANAGER_OUTLINE_COLOR"]) 187 { 188 [outlineView setBackgroundColor: [[NSUserDefaults standardUserDefaults] 189 colorForKey: @"MAILBOXMANAGER_OUTLINE_COLOR"]]; 190 [scrollView setBackgroundColor: [[NSUserDefaults standardUserDefaults] 191 colorForKey: @"MAILBOXMANAGER_OUTLINE_COLOR"]]; 192 } 193 194 return self; 195} 196#else 197- (id) init 198{ 199 self = [super init]; 200 if (self) 201 { 202 // We initialize some ivars 203 [self windowDidLoad]; 204 } 205 206 return self; 207} 208#endif 209 210 211// 212// 213// 214- (void) dealloc 215{ 216 [[NSNotificationCenter defaultCenter] removeObserver: self]; 217 218 if ([[NSUserDefaults standardUserDefaults] integerForKey: @"PreferredViewStyle" default: GNUMailDrawerView] == GNUMailFloatingView) 219 { 220 [[self window] setDelegate: nil]; 221 } 222 223 RELEASE(menu); 224 RELEASE(localNodes); 225 RELEASE(_cache); 226 RELEASE(_allFolders); 227 RELEASE(allStores); 228 229 RELEASE(_open_folder); 230 RELEASE(_sort_right); 231 RELEASE(_sort_down); 232 RELEASE(_drafts); 233 RELEASE(_inbox); 234 RELEASE(_sent); 235 RELEASE(_trash); 236 237 [super dealloc]; 238} 239 240 241// 242// Datasource methods for the outline view 243// 244- (id) outlineView: (NSOutlineView *) outlineView 245 child: (NSInteger) index 246 ofItem: (id) item 247{ 248 if (!item || item == _allFolders) 249 { 250 return [_allFolders objectAtIndex: index]; 251 } 252 253 if ([item isKindOfClass: [FolderNode class]]) 254 { 255 return [(FolderNode *)item childAtIndex: index]; 256 } 257 258 return nil; 259} 260 261 262// 263// 264// 265- (BOOL) outlineView: (NSOutlineView *) outlineView 266 isItemExpandable: (id) item 267{ 268 if (item == _allFolders || [_allFolders containsObject: item]) 269 { 270 return YES; 271 } 272 273 if ([item isKindOfClass: [FolderNode class]]) 274 { 275 if ([(FolderNode *)item childCount] > 0) 276 { 277 return YES; 278 } 279 else 280 { 281 return NO; 282 } 283 } 284 285 return NO; 286} 287 288 289// 290// 291// 292- (NSInteger) outlineView: (NSOutlineView *) outlineView 293 numberOfChildrenOfItem: (id) item 294{ 295 // The root. 296 if (!item || item == _allFolders) 297 { 298 return [_allFolders count]; 299 } 300 301 // Children of our root, the Local folder and all the IMAP folders, subfolders, etc. 302 if ([item isKindOfClass: [FolderNode class]]) 303 { 304 return [(FolderNode *)item childCount]; 305 } 306 307 return 0; 308} 309 310 311// 312// 313// 314- (id) outlineView: (NSOutlineView *) outlineView 315 objectValueForTableColumn: (NSTableColumn *) tableColumn 316 byItem: (id) item 317{ 318 if ([[[tableColumn headerCell] stringValue] isEqual: _(@"Mailbox")]) 319 { 320 if ([item isKindOfClass: [FolderNode class]]) 321 { 322 return [(FolderNode *)item name]; 323 } 324 } 325 326 if ([item isKindOfClass: [FolderNode class]] && [item parent]) 327 { 328 BOOL b; 329 330 b = [[Utilities completePathForFolderNode: item separator: '/'] 331 hasPrefix: [NSString stringWithFormat: @"/%@", _(@"Local")]]; 332 333 if ([item childCount] == 0 || !b) 334 { 335 NSUInteger nbOfMessages, nbOfUnreadMessages; 336 337 [self _nbOfMessages: &nbOfMessages 338 nbOfUnreadMessages: &nbOfUnreadMessages 339 forItem: item]; 340 341 // If we have an IMAP folder AND the count is 0 AND it has children, do nothing. 342 if (!b && nbOfMessages == 0 && [item childCount] > 0) return nil; 343 344 if ([[[tableColumn headerCell] stringValue] isEqual: _(@"Messages")]) 345 { 346 if (nbOfUnreadMessages > 0) return [NSString stringWithFormat: @"(%lu) %lu", (unsigned long)nbOfUnreadMessages, (unsigned long)nbOfMessages]; 347 return [NSString stringWithFormat: @"%lu", (unsigned long)nbOfMessages]; 348 } 349 } 350 } 351 352 return nil; 353} 354 355 356// 357// 358// 359- (void) outlineView: (NSOutlineView *) theOutlineView 360 setObjectValue: (id) theObject 361 forTableColumn: (NSTableColumn *) theTableColumn 362 byItem: (id) theItem 363{ 364 NSString *aDefaultMailbox; 365 id aStore; 366 367 // If the previous name is the same as the new one, ignore it. 368 if ([[(FolderNode *)theItem name] isEqualToString: theObject]) 369 return; 370 371 // 372 // If we tried to rename a special mailbox, we warn for now. This is quite 373 // useful in IMAP since whenever we rename INBOX, a new INBOX is created 374 // automatically. So, if a user renames by mistake his INBOX, he will 375 // have to move all mails back to his INBOX as he won't be able to rename 376 // the mailbox back to "INBOX". 377 // 378 if ([self _deletingDefaultMailbox: &aDefaultMailbox 379 usingURLNameAsString: [self _stringValueOfURLNameFromItem: theItem store: &aStore]]) 380 { 381 int choice; 382 383 choice = NSRunAlertPanel(_(@"Warning!"), 384 _(@"You are about to rename the %@ special mailbox to %@.\nDo you want to proceed?"), 385 _(@"OK"), // default 386 _(@"Cancel"), // alternate 387 NULL, 388 aDefaultMailbox, 389 theObject); 390 391 if (choice == NSAlertAlternateReturn) 392 { 393 return; 394 } 395 } 396 397 { 398 NSString *pathOfFolder; 399 id aWindow; 400 401 aStore = [self storeForFolderNode: theItem]; 402 403 // 404 // pathOfFolder will hold a value like: folderA 405 // folderA/folderB 406 // folderA/folderB/folderC 407 // or: 408 // folderA.folderB 409 // folderA.folderB.folderC 410 // 411 pathOfFolder = [Utilities pathOfFolderFromFolderNode: theItem 412 separator: [(id<CWStore>)aStore folderSeparator]]; 413 414 [(id<CWStore>)aStore renameFolderWithName: [pathOfFolder stringByDeletingFirstPathSeparator: [(id<CWStore>)aStore folderSeparator]] 415 toName: [[NSString stringWithFormat: @"%@%c%@", 416 [pathOfFolder stringByDeletingLastPathComponentWithSeparator: [(id<CWStore>)aStore folderSeparator]], 417 [(id<CWStore>)aStore folderSeparator], theObject] 418 stringByDeletingFirstPathSeparator: [(id<CWStore>)aStore folderSeparator]]]; 419 420 // We select the newly renamed node and update the outline view 421 aWindow = [Utilities windowForFolderName: [[NSString stringWithFormat: @"%@%c%@", 422 [pathOfFolder stringByDeletingLastPathComponentWithSeparator: [(id<CWStore>)aStore folderSeparator]], 423 [(id<CWStore>)aStore folderSeparator], theObject] 424 stringByDeletingFirstPathSeparator: [(id<CWStore>)aStore folderSeparator]] 425 store: aStore]; 426 427 if (aWindow) 428 { 429 [[aWindow windowController] windowDidBecomeKey: nil]; 430 } 431 } 432} 433 434 435// 436// Delegate method used to prevent the user from renaming 437// "invalid" mailboxes / folders. 438// 439- (BOOL) outlineView: (NSOutlineView *) theOutlineView 440 shouldEditTableColumn: (NSTableColumn *) theTableColumn 441 item: (id) theItem 442{ 443 NSInteger row, level; 444 id item; 445 446 row = [theOutlineView selectedRow]; 447 448 if (row < 0) 449 { 450 return NO; 451 } 452 453 item = [theOutlineView itemAtRow: row]; 454 level = [theOutlineView levelForItem: item]; 455 456 if ([theOutlineView numberOfSelectedRows] != 1 || level < 1) 457 { 458 return NO; 459 } 460 461 return YES; 462} 463 464 465// 466// 467// 468- (void) outlineView: (NSOutlineView *) aOutlineView 469 willDisplayCell: (id) aCell 470 forTableColumn: (NSTableColumn *) aTableColumn 471 item: (id) item 472{ 473 // We set our default node icon, if we need to. 474 if ([[[aTableColumn headerCell] stringValue] isEqual: _(@"Mailbox")]) 475 { 476 NSInteger level; 477 478 level = [aOutlineView levelForItem: item]; 479 480 if (level > 0) 481 { 482 NSString *aString; 483 id aStore; 484 485#ifdef GNUSTEP 486 if ([(FolderNode *)item childCount] > 0) 487 { 488 [aCell setDelta: 0]; 489 } 490 else 491 { 492 [aCell setDelta: 19]; 493 } 494#endif 495 496 aStore = nil; 497 aString = [self _stringValueOfURLNameFromItem: item 498 store: &aStore]; 499 500 if ([Utilities stringValueOfURLName: aString isEqualTo: @"TRASHFOLDERNAME"]) 501 { 502 [aCell setImage: _trash]; 503 } 504 else if ([Utilities stringValueOfURLName: aString isEqualTo: @"SENTFOLDERNAME"]) 505 { 506 [aCell setImage: _sent]; 507 } 508 else if ([Utilities stringValueOfURLName: aString isEqualTo: @"DRAFTSFOLDERNAME"]) 509 { 510 [aCell setImage: _drafts]; 511 } 512 else if ([Utilities stringValueOfURLName: aString isEqualTo: @"INBOXFOLDERNAME"]) 513 { 514 [aCell setImage: _inbox]; 515 } 516 else 517 { 518 [aCell setImage: _open_folder]; 519 } 520 } 521 else 522 { 523 [aCell setImage: nil]; 524 } 525 } 526 527 // 528 // 529 // 530 if ([item isKindOfClass: [FolderNode class]] && [item parent]) 531 { 532 NSUInteger nbOfMessages, nbOfUnreadMessages; 533 534 [self _nbOfMessages: &nbOfMessages 535 nbOfUnreadMessages: &nbOfUnreadMessages 536 forItem: item]; 537 538 if (nbOfUnreadMessages > 0) 539 { 540 [aCell setFont: [NSFont boldSystemFontOfSize: _font_size]]; 541 return; 542 } 543 } 544 545 // We set our default font. 546 [aCell setFont: [NSFont systemFontOfSize: _font_size]]; 547 548 // We set the right text aligment 549 if ([[[aTableColumn headerCell] stringValue] isEqual: _(@"Mailbox")]) 550 { 551 [aCell setAlignment: NSLeftTextAlignment]; 552 } 553 else 554 { 555 [aCell setAlignment: NSRightTextAlignment]; 556 } 557} 558 559 560// 561// 562// 563- (void) outlineViewSelectionDidChange: (NSNotification *) theNotification 564{ 565 [self open: [theNotification object]]; 566} 567 568 569// 570// 571// 572- (NSMenu *) outlineView: (NSOutlineView *) aOutlineView 573 contextMenuForItem: (id) item 574{ 575 id theItem, o; 576 int i; 577 578 o = [self storeForFolderNode: [outlineView itemAtRow: [aOutlineView selectedRow]]]; 579 580 for (i = 0; i < [[menu itemArray] count]; i++) 581 { 582 theItem = [[menu itemArray] objectAtIndex: i]; 583 [theItem setEnabled: [self validateMenuItem: theItem]]; 584 585 if ([theItem tag] == TAKE_OFFLINE && [o isKindOfClass: [CWIMAPStore class]]) 586 { 587 if ([o isConnected]) 588 { 589 [theItem setTitle: _(@"Take Account Offline")]; 590 } 591 else 592 { 593 [theItem setTitle: _(@"Take Account Online")]; 594 } 595 } 596 } 597 598 [menu update]; 599 600 return menu; 601} 602 603 604// 605// 606// 607- (BOOL) validateMenuItem: (NSMenuItem *) theItem 608{ 609 NSInteger row, level; 610 BOOL aBOOL; 611 612 row = [outlineView selectedRow]; 613 level = [outlineView levelForItem: [outlineView itemAtRow: row]]; 614 615 // 616 // Validation for our "Take Account Offline" item 617 // 618 if ([theItem tag] == TAKE_OFFLINE) 619 { 620 return (level == 0 && [outlineView itemAtRow: row] != localNodes); 621 } 622 623 if ([[theItem title] isEqualToString: _(@"Delete...")] || 624 [[theItem title] isEqualToString: _(@"Rename")]) 625 { 626 aBOOL = (row > 0 && [outlineView numberOfSelectedRows] == 1 && level >= 1); 627 } 628 else 629 { 630 aBOOL = (row >= 0 && [outlineView numberOfSelectedRows] == 1 && level >= 0); 631 } 632 633 return aBOOL; 634} 635 636 637// 638// Delegate methods 639// 640- (BOOL) outlineView: (NSOutlineView *) outlineView 641 shouldExpandItem: (id) item 642{ 643 if (item == _allFolders || item == localNodes) 644 { 645 return YES; 646 } 647 648 if ([_allFolders containsObject: item]) 649 { 650 return [self _initializeIMAPStoreWithAccountName: [(FolderNode *)item name]]; 651 } 652 653 return YES; 654} 655 656 657// 658// NSOutlineViewDataSource Drag and drop 659// 660- (NSDragOperation) outlineView: (NSOutlineView*) theOutlineView 661 validateDrop: (id <NSDraggingInfo>) info 662 proposedItem: (id) item 663 proposedChildIndex: (NSInteger) index 664{ 665 if (![item respondsToSelector: @selector(childCount)] || 666 index < 0 || index >= [(FolderNode*)item childCount]) 667 { 668 return NSDragOperationNone; 669 } 670 671 // Let's get the right item.. 672 item = [item childAtIndex: index]; 673 674 if ([info draggingSourceOperationMask] & NSDragOperationGeneric) 675 { 676 [theOutlineView setDropItem: item 677 dropChildIndex: NSOutlineViewDropOnItemIndex]; 678 return NSDragOperationGeneric; 679 } 680 else if ([info draggingSourceOperationMask] & NSDragOperationCopy) 681 { 682 [theOutlineView setDropItem: item 683 dropChildIndex: NSOutlineViewDropOnItemIndex]; 684 return NSDragOperationCopy; 685 } 686 else 687 { 688 return NSDragOperationNone; 689 } 690} 691 692 693// 694// NSOutlineViewDataSource Drag and drop 695// 696- (BOOL) outlineView: (NSOutlineView*) outlineView 697 acceptDrop: (id <NSDraggingInfo>) info 698 item: (id) item 699 childIndex: (NSInteger) index 700{ 701 CWFolder *aSourceFolder, *aDestinationFolder; 702 CWStore *aSourceStore, *aDestinationStore; 703 MailWindowController *aMailWindowController; 704 705 FolderNode *aFolderNode; 706 NSString *aFolderName; 707 NSArray *propertyList; 708 709 NSMutableArray *allMessages; 710 NSUInteger i, count; 711 712 if (!item || index != NSOutlineViewDropOnItemIndex) 713 { 714 NSBeep(); 715 return NO; 716 } 717 718 aFolderNode = (FolderNode *)item; 719 720 // We get our store and our folder name 721 aDestinationStore = [self storeForFolderNode: aFolderNode]; 722 723 aFolderName = [Utilities pathOfFolderFromFolderNode: aFolderNode 724 separator: [(id<CWStore>)aDestinationStore folderSeparator]]; 725 726 // We get the MailWindowController source 727 aMailWindowController = 728 (MailWindowController *)[[info draggingSource] delegate]; 729 730 if (!aMailWindowController || 731 ![aMailWindowController isKindOfClass: [MailWindowController class]] || 732 !aFolderName || 733 [aFolderName length] == 0) 734 { 735 NSBeep(); 736 return NO; 737 } 738 739 // We verify if we aren't trying to transfer to the current mbox! 740 aSourceFolder = [aMailWindowController folder]; 741 aSourceStore = [aSourceFolder store]; 742 743 if (aSourceStore == aDestinationStore && 744 [[aSourceFolder name] isEqualToString: aFolderName]) 745 { 746 NSRunInformationalAlertPanel(_(@"Transfer error!"), 747 _(@"You cannot transfer a message inside the same mailbox!"), 748 _(@"OK"), 749 NULL, 750 NULL, 751 NULL); 752 return NO; 753 } 754 755 756 // We get a reference to our destination folder, 757 // w/o parsing it if it's not already open. 758 // or w/o selecting it if it's an IMAP store. 759 if ([(id<NSObject>)aDestinationStore isKindOfClass: [CWIMAPStore class]]) 760 { 761 aDestinationFolder = 762 (CWFolder *)[(CWIMAPStore *)aDestinationStore folderForName: aFolderName 763 select: NO]; 764 } 765 else 766 { 767 aDestinationFolder = 768 (CWFolder *)[(CWLocalStore *)aDestinationStore folderForName: 769 aFolderName]; 770 771 } 772 773 if (!aDestinationFolder) 774 { 775 NSRunAlertPanel(_(@"Error!"), 776 _(@"An error occurred while trying to open the \"%@\" mailbox.\nThe drag and drop operation has been cancelled."), 777 _(@"OK"), 778 NULL, 779 NULL, 780 aFolderName); 781 return NO; 782 } 783 784 [aDestinationFolder setProperty: [NSDate date] forKey: FolderExpireDate]; 785 786 787 // We retrieve property list of messages from paste board 788 propertyList = [[info draggingPasteboard] propertyListForType: MessagePboardType]; 789 790 if (!propertyList) 791 { 792 return NO; 793 } 794 795 allMessages = [[NSMutableArray alloc] init]; 796 count = [propertyList count]; 797 798 for (i = 0; i < count; i++) 799 { 800 [allMessages addObject: 801 [aSourceFolder->allMessages objectAtIndex: 802 (NSUInteger)[[(NSDictionary *)[propertyList objectAtIndex: i] 803 objectForKey: MessageNumber] intValue]-1]]; 804 } 805 806 [self transferMessages: allMessages 807 fromStore: aSourceStore 808 fromFolder: aSourceFolder 809 toStore: aDestinationStore 810 toFolder: aDestinationFolder 811 operation: (([info draggingSourceOperationMask]&NSDragOperationGeneric) == NSDragOperationGeneric ? MOVE_MESSAGES : COPY_MESSAGES)]; 812 813 RELEASE(allMessages); 814 815 return YES; 816} 817 818 819// 820// 821// 822#ifndef MACOSX 823- (void) outlineView: (NSOutlineView *) aOutlineView 824 willDisplayOutlineCell: (id) aCell 825 forTableColumn: (NSTableColumn *) aTbleColumn 826 item: (id)item 827{ 828 if (![aOutlineView isExpandable: item]) 829 { 830 [aCell setImage: nil]; 831 } 832 else 833 { 834 if ([aOutlineView isItemExpanded: item]) 835 { 836 [aCell setImage: _sort_down]; 837 } 838 else 839 { 840 [aCell setImage: _sort_right]; 841 } 842 } 843} 844#endif 845 846 847// 848// 849// 850- (void) windowDidLoad 851{ 852 NSMenuItem *aMenuItem; 853 NSMenu *aMenu; 854 855 menu = [[NSMenu alloc] init]; 856 [menu setAutoenablesItems: NO]; 857 858 aMenuItem = [[NSMenuItem alloc] initWithTitle: _(@"Create...") action: @selector(create:) keyEquivalent: @""]; 859 [aMenuItem setTarget: self]; 860 [menu addItem: aMenuItem]; 861 RELEASE(aMenuItem); 862 863 aMenuItem = [[NSMenuItem alloc] initWithTitle: _(@"Delete...") action: @selector(delete:) keyEquivalent: @""]; 864 [aMenuItem setTarget: self]; 865 [menu addItem: aMenuItem]; 866 RELEASE(aMenuItem); 867 868 aMenuItem = [[NSMenuItem alloc] initWithTitle: _(@"Rename") action: @selector(rename:) keyEquivalent: @""]; 869 [aMenuItem setTarget: self]; 870 [menu addItem: aMenuItem]; 871 RELEASE(aMenuItem); 872 873 aMenuItem = [[NSMenuItem alloc] initWithTitle: _(@"Take Account Offline") action: @selector(takeOffline:) keyEquivalent: @""]; 874 [aMenuItem setTag: TAKE_OFFLINE]; 875 [aMenuItem setTarget: self]; 876 [menu addItem: aMenuItem]; 877 RELEASE(aMenuItem); 878 879 // 880 // Our "Use..." menu with its three items 881 // 882 aMenuItem = [[NSMenuItem alloc] initWithTitle: _(@"Use") action: NULL keyEquivalent: @""]; 883 [menu addItem: aMenuItem]; 884 aMenu = [[NSMenu alloc] init]; 885 [aMenuItem setSubmenu: aMenu]; 886 RELEASE(aMenuItem); 887 888 aMenuItem = [[NSMenuItem alloc] initWithTitle: _(@"Small Icons") action: @selector(changeSize:) keyEquivalent: @""]; 889 [aMenuItem setTag: GNUMailSmallIconSize]; 890 [aMenuItem setTarget: self]; 891 [aMenu addItem: aMenuItem]; 892 RELEASE(aMenuItem); 893 894 aMenuItem = [[NSMenuItem alloc] initWithTitle: _(@"Standard Icons") action: @selector(changeSize:) keyEquivalent: @""]; 895 [aMenuItem setTag: GNUMailStandardIconSize]; 896 [aMenuItem setTarget: self]; 897 [aMenu addItem: aMenuItem]; 898 RELEASE(aMenuItem); 899 900 aMenuItem = [[NSMenuItem alloc] initWithTitle: _(@"Large Icons") action: @selector(changeSize:) keyEquivalent: @""]; 901 [aMenuItem setTag: GNUMailLargeIconSize]; 902 [aMenuItem setTarget: self]; 903 [aMenu addItem: aMenuItem]; 904 RELEASE(aMenuItem); 905 RELEASE(aMenu); 906 907 908 // 909 // Our "Set Mailbox as..." menu with its items 910 // 911 aMenuItem = [[NSMenuItem alloc] initWithTitle: _(@"Set Mailbox as") action: NULL keyEquivalent: @""]; 912 [menu addItem: aMenuItem]; 913 aMenu = [[NSMenu alloc] init]; 914 [aMenuItem setSubmenu: aMenu]; 915 RELEASE(aMenuItem); 916 917 aMenuItem = [[NSMenuItem alloc] initWithTitle: _(@"Drafts for Account") action: NULL keyEquivalent: @""]; 918 [aMenuItem setTag: SET_DRAFTS]; 919 [aMenu addItem: aMenuItem]; 920 RELEASE(aMenuItem); 921 922 aMenuItem = [[NSMenuItem alloc] initWithTitle: _(@"Sent for Account") action: NULL keyEquivalent: @""]; 923 [aMenuItem setTag: SET_SENT]; 924 [aMenu addItem: aMenuItem]; 925 RELEASE(aMenuItem); 926 927 aMenuItem = [[NSMenuItem alloc] initWithTitle: _(@"Trash for Account") action: NULL keyEquivalent: @""]; 928 [aMenuItem setTag: SET_TRASH]; 929 [aMenu addItem: aMenuItem]; 930 RELEASE(aMenuItem); 931 RELEASE(aMenu); 932 933 [self _updateContextMenu]; 934 [self changeSize: nil]; 935 936 ASSIGN(_cache, [MailboxManagerCache cacheFromDisk]); 937 938 // We initialize our array containing all Stores and we load of folders 939 _allFolders = [[NSMutableArray alloc] init]; 940 941 // We initialize our dictionary containing all openend CWIMAPStores 942 allStores = [[NSMutableDictionary alloc] init]; 943 944 [[NSNotificationCenter defaultCenter] 945 addObserver: self 946 selector: @selector(_accountsHaveChanged:) 947 name: AccountsHaveChanged 948 object: nil]; 949 950 [[NSNotificationCenter defaultCenter] 951 addObserver: self 952 selector: @selector(_folderCreateCompleted:) 953 name: PantomimeFolderCreateCompleted 954 object: nil]; 955 956 [[NSNotificationCenter defaultCenter] 957 addObserver: self 958 selector: @selector(_folderCreateFailed:) 959 name: PantomimeFolderCreateFailed 960 object: nil]; 961 962 [[NSNotificationCenter defaultCenter] 963 addObserver: self 964 selector: @selector(_folderDeleteCompleted:) 965 name: PantomimeFolderDeleteCompleted 966 object: nil]; 967 968 [[NSNotificationCenter defaultCenter] 969 addObserver: self 970 selector: @selector(_folderDeleteFailed:) 971 name: PantomimeFolderDeleteFailed 972 object: nil]; 973 974 [[NSNotificationCenter defaultCenter] 975 addObserver: self 976 selector: @selector(_folderRenameCompleted:) 977 name: PantomimeFolderRenameCompleted 978 object: nil]; 979 980 [[NSNotificationCenter defaultCenter] 981 addObserver: self 982 selector: @selector(_folderRenameFailed:) 983 name: PantomimeFolderRenameFailed 984 object: nil]; 985 986 [[NSNotificationCenter defaultCenter] 987 addObserver: self 988 selector: @selector(_folderSubscribeCompleted:) 989 name: PantomimeFolderSubscribeCompleted 990 object: nil]; 991 992 [[NSNotificationCenter defaultCenter] 993 addObserver: self 994 selector: @selector(_folderUnsubscribeCompleted:) 995 name: PantomimeFolderUnsubscribeCompleted 996 object: nil]; 997} 998 999 1000// 1001// action methods 1002// 1003- (IBAction) changeSize: (id) sender 1004{ 1005 int size, height; 1006 1007 if (sender) 1008 { 1009 size = [sender tag]; 1010 } 1011 else 1012 { 1013 size = [[NSUserDefaults standardUserDefaults] integerForKey: @"IconSize" default: GNUMailStandardIconSize]; 1014 } 1015 1016 ASSIGN(_sort_right, [NSImage imageNamed: @"sort_right"]); 1017 ASSIGN(_sort_down, [NSImage imageNamed: @"sort_down"]); 1018 1019 switch (size) 1020 { 1021 case GNUMailSmallIconSize: 1022 ASSIGN(_drafts, [NSImage imageNamed: @"create_12"]); 1023 ASSIGN(_inbox, [NSImage imageNamed: @"retrieve_12"]); 1024 ASSIGN(_sent, [NSImage imageNamed: @"send_12"]); 1025 ASSIGN(_trash, [NSImage imageNamed: @"trash_12"]); 1026 ASSIGN(_open_folder, [NSImage imageNamed: @"folder_12"]); 1027 _font_size = 9; 1028 height = 12; 1029 break; 1030 1031 case GNUMailLargeIconSize: 1032 ASSIGN(_drafts, [NSImage imageNamed: @"create_20"]); 1033 ASSIGN(_inbox, [NSImage imageNamed: @"retrieve_20"]); 1034 ASSIGN(_sent, [NSImage imageNamed: @"send_20"]); 1035 ASSIGN(_trash, [NSImage imageNamed: @"trash_20"]); 1036 ASSIGN(_open_folder, [NSImage imageNamed: @"folder_20"]); 1037 height = 20; 1038 1039 _font_size = [NSFont systemFontSize]; 1040 1041 break; 1042 1043 case GNUMailStandardIconSize: 1044 default: 1045 ASSIGN(_drafts, [NSImage imageNamed: @"create_16"]); 1046 ASSIGN(_inbox, [NSImage imageNamed: @"retrieve_16"]); 1047 ASSIGN(_sent, [NSImage imageNamed: @"send_16"]); 1048 ASSIGN(_trash, [NSImage imageNamed: @"trash_16"]); 1049 ASSIGN(_open_folder, [NSImage imageNamed: @"folder_16"]); 1050 height = 16; 1051#ifdef GNUSTEP 1052 _font_size = [NSFont systemFontSize]; 1053#else 1054 _font_size = [NSFont smallSystemFontSize]; 1055#endif 1056 } 1057 1058 [outlineView setRowHeight: height]; 1059 [outlineView setNeedsDisplay: YES]; 1060 1061 [[NSUserDefaults standardUserDefaults] setInteger: size forKey: @"IconSize"]; 1062} 1063 1064 1065// 1066// 1067// 1068- (IBAction) open: (id) sender 1069{ 1070 id item; 1071 NSInteger row, level; 1072 1073 row = [outlineView selectedRow]; 1074 1075 // If no row is selected and we had a last MailWindow on top, we set its folder to nil. 1076 // Otherwise, we just return. 1077 if (row < 0) 1078 { 1079 if ([GNUMail lastMailWindowOnTop]) 1080 { 1081 [[[[GNUMail lastMailWindowOnTop] windowController] folder] close]; 1082 1083 if ([[[GNUMail lastMailWindowOnTop] windowController] isKindOfClass: [MailWindowController class]]) 1084 { 1085 [[[GNUMail lastMailWindowOnTop] windowController] setFolder: nil]; 1086 } 1087 else 1088 { 1089 [[[[GNUMail lastMailWindowOnTop] windowController] mailWindowController] setFolder: nil]; 1090 } 1091 } 1092 return; 1093 } 1094 1095 item = [outlineView itemAtRow: row]; 1096 level = [outlineView levelForItem: item]; 1097 1098 // 1099 // We must verify that: 1100 // 1101 // a) we have at least one selected row 1102 // b) we haven't selected our root, or a store (local or IMAP) 1103 // 1104 if ([outlineView numberOfSelectedRows] != 1) 1105 { 1106 NSRunInformationalAlertPanel(_(@"Mailbox error!"), 1107 _(@"You must select a valid mailbox to open!"), 1108 _(@"OK"), 1109 NULL, 1110 NULL, 1111 NULL); 1112 return; 1113 } 1114 else if (level < 1) 1115 { 1116 if (![outlineView isItemExpanded: item]) 1117 { 1118 [outlineView expandItem: item]; 1119 } 1120 return; 1121 } 1122 1123 1124 // We verify if it's a local folder 1125 if ([[Utilities completePathForFolderNode: item separator: '/'] 1126 hasPrefix: [NSString stringWithFormat: @"/%@", _(@"Local")]]) 1127 { 1128 NSString *aString; 1129 1130 aString = [Utilities pathOfFolderFromFolderNode: (FolderNode *)item 1131 separator: '/']; 1132 1133 [self _openLocalFolderWithName: aString sender: sender]; 1134 } 1135 // It's an IMAP folder... 1136 else 1137 { 1138 NSString *aServerName, *aUsername, *aString; 1139 CWIMAPStore *aStore; 1140 1141 [Utilities storeKeyForFolderNode: item 1142 serverName: &aServerName 1143 username: &aUsername]; 1144 1145 aStore = (CWIMAPStore *)[self storeForName: aServerName 1146 username: aUsername]; 1147 1148 aString = [[Utilities pathOfFolderFromFolderNode: (FolderNode *)item separator: '/'] 1149 stringByReplacingOccurrencesOfCharacter: '/' 1150 withCharacter: [aStore folderSeparator]]; 1151 1152 [self _openIMAPFolderWithName: aString 1153 store: aStore 1154 sender: sender]; 1155 } 1156} 1157 1158 1159// 1160// 1161// 1162- (IBAction) create: (id) sender 1163{ 1164 NewMailboxPanelController *theController; 1165 id aStore, item; 1166 NSInteger row; 1167 1168 row = [outlineView selectedRow]; 1169 1170 if (row < 0 || row >= [outlineView numberOfRows]) 1171 { 1172 NSBeep(); 1173 return; 1174 } 1175 1176 item = [outlineView itemAtRow: row]; 1177 1178 if ([outlineView numberOfSelectedRows] != 1) 1179 { 1180 NSRunInformationalAlertPanel(_(@"Mailbox error!"), 1181 _(@"You must select a valid root where to create this new mailbox."), 1182 _(@"OK"), 1183 NULL, 1184 NULL, 1185 NULL); 1186 return; 1187 } 1188 1189 // 1190 // We create our NewMailboxPanelController object. It'll be automatically deallocated when the 1191 // window will be closed. 1192 // 1193 theController = [[NewMailboxPanelController alloc] initWithWindowNibName: @"NewMailboxPanel"]; 1194 1195 // 1196 // We get the right store and we disable our mailbox type popup button 1197 // if we are creating an IMAP mailbox. 1198 // 1199 aStore = [self storeForFolderNode: item]; 1200 1201 if ([NSApp runModalForWindow: [theController window]] == NSRunStoppedResponse) 1202 { 1203 NSString *aString, *pathOfFolder; 1204 int type; 1205 1206 pathOfFolder = [Utilities pathOfFolderFromFolderNode: item 1207 separator: [aStore folderSeparator]]; 1208 1209 if (!pathOfFolder || [pathOfFolder length] == 0) 1210 { 1211 aString = [[[theController mailboxNameField] stringValue] stringByTrimmingWhiteSpaces]; 1212 } 1213 else 1214 { 1215 aString = [NSString stringWithFormat: @"%@%c%@", 1216 pathOfFolder, 1217 [aStore folderSeparator], 1218 [[[theController mailboxNameField] stringValue] stringByTrimmingWhiteSpaces]]; 1219 } 1220 1221 if ([[NSUserDefaults standardUserDefaults] integerForKey: @"UseMaildirMailboxFormat" default: NSOffState] == NSOnState) 1222 { 1223 type = PantomimeFormatMaildir; 1224 } 1225 else 1226 { 1227 type = PantomimeFormatMbox; 1228 } 1229 1230 // We can now proceed with the creation of our new folder 1231 [aStore createFolderWithName: aString type: type contents: nil]; 1232 } 1233 1234#ifndef MACOSX 1235 [[self window] makeKeyAndOrderFront: self]; 1236#endif 1237 1238 RELEASE(theController); 1239} 1240 1241 1242// 1243// 1244// 1245- (IBAction) delete: (id) sender 1246{ 1247 NSString *aFolderName, *aString; 1248 id aStore, item; 1249 1250 int choice, row, level; 1251 1252 1253 row = [outlineView selectedRow]; 1254 1255 if (row < 0 || row >= [outlineView numberOfRows]) 1256 { 1257 NSBeep(); 1258 return; 1259 } 1260 1261 item = [outlineView itemAtRow: row]; 1262 level = [outlineView levelForItem: item]; 1263 1264 if ([outlineView numberOfSelectedRows] != 1 || level < 1) 1265 { 1266 NSRunInformationalAlertPanel(_(@"Mailbox error!"), 1267 _(@"Please select the mailbox you would like to delete."), 1268 _(@"OK"), 1269 NULL, 1270 NULL, 1271 NULL); 1272 return; 1273 } 1274 1275 aString = [self _stringValueOfURLNameFromItem: item store: &aStore]; 1276 1277 // We get our folder name, respecting the folder separator 1278 aFolderName = [Utilities pathOfFolderFromFolderNode: (FolderNode *)item 1279 separator: [(id<CWStore>)aStore folderSeparator]]; 1280 1281 // We show our prompt panel 1282 choice = NSRunAlertPanel(_(@"Delete..."), 1283 _(@"Are you sure you want to delete the \"%@\" mailbox?"), 1284 _(@"Delete"), // default 1285 _(@"Cancel"), // alternate 1286 nil, 1287 aFolderName); 1288 1289 if (choice == NSAlertDefaultReturn) 1290 { 1291 NSString *aDefaultMailbox; 1292 1293 if ([self _deletingDefaultMailbox: &aDefaultMailbox usingURLNameAsString: aString]) 1294 { 1295 NSRunAlertPanel(_(@"Error while deleting!"), 1296 _(@"You can't delete your default %@ mailbox. Use the Mailboxes tab in the\nAccount Preferences panel to change it before trying again."), 1297 _(@"OK"), // default 1298 NULL, // alternate 1299 NULL, 1300 aDefaultMailbox); 1301 return; 1302 } 1303 1304 if ([aStore folderForNameIsOpen: aFolderName]) 1305 { 1306 id aWindow; 1307 1308 // Get the associated MailWindow. 1309 aWindow = [Utilities windowForFolderName: aFolderName store: aStore]; 1310 1311 // We just close the mailbox and leave its MailWindow empty! 1312 [[[aWindow windowController] folder] close]; 1313 [[aWindow windowController] setFolder: nil]; 1314 } 1315 1316 // We now delete the mailbox... 1317 [aStore deleteFolderWithName: aFolderName]; 1318 } 1319} 1320 1321 1322// 1323// 1324// 1325- (IBAction) rename: (id) sender 1326{ 1327 NSInteger row; 1328 1329 row = [outlineView selectedRow]; 1330 1331 if (row <= 0 || row >= [outlineView numberOfRows]) 1332 { 1333 NSBeep(); 1334 return; 1335 } 1336 1337 [outlineView editColumn: 0 1338 row: row 1339 withEvent: nil 1340 select: YES]; 1341} 1342 1343 1344// 1345// 1346// 1347- (IBAction) takeOffline: (id) sender 1348{ 1349 CWIMAPStore *aStore; 1350 1351 aStore = [self storeForFolderNode: (FolderNode *)[outlineView itemAtRow: [outlineView selectedRow]]]; 1352 1353 if (aStore) 1354 { 1355 [self setStore: nil name: [aStore name] username: [aStore username]]; 1356 [self closeWindowsForStore: aStore]; 1357 } 1358 else 1359 { 1360 [self open: sender]; 1361 } 1362} 1363 1364 1365// 1366// 1367// 1368- (IBAction) setMailboxAs: (id) sender 1369{ 1370 NSMutableDictionary *theAccount, *allAccounts, *allValues; 1371 NSString *aString; 1372 CWStore *aStore; 1373 1374 allAccounts = [[NSMutableDictionary alloc] initWithDictionary: [[NSUserDefaults standardUserDefaults] 1375 objectForKey: @"ACCOUNTS"]]; 1376 theAccount = [NSMutableDictionary dictionaryWithDictionary: [allAccounts objectForKey: [sender title]]]; 1377 allValues = [NSMutableDictionary dictionaryWithDictionary: [theAccount objectForKey: @"MAILBOXES"]]; 1378 aString = [self _stringValueOfURLNameFromItem: (FolderNode *)[outlineView itemAtRow: [outlineView selectedRow]] store: &aStore]; 1379 1380 switch ([sender tag]) 1381 { 1382 case SET_DRAFTS: 1383 [allValues setObject: aString forKey: @"DRAFTSFOLDERNAME"]; 1384 break; 1385 case SET_SENT: 1386 [allValues setObject: aString forKey: @"SENTFOLDERNAME"]; 1387 break; 1388 case SET_TRASH: 1389 [allValues setObject: aString forKey: @"TRASHFOLDERNAME"]; 1390 break; 1391 } 1392 1393 [theAccount setObject: allValues forKey: @"MAILBOXES"]; 1394 [allAccounts setObject: theAccount forKey: [sender title]]; 1395 [[NSUserDefaults standardUserDefaults] setObject: allAccounts forKey: @"ACCOUNTS"]; 1396 [[NSUserDefaults standardUserDefaults] synchronize]; 1397 [outlineView setNeedsDisplay: YES]; 1398} 1399 1400 1401// 1402// access / mutation methods 1403// 1404- (NSOutlineView *) outlineView 1405{ 1406 return outlineView; 1407} 1408 1409 1410// 1411// This method returns a pointer to an open store which has 1412// theFolderNode as one of its children. 1413// 1414- (id) storeForFolderNode: (FolderNode *) theFolderNode 1415{ 1416 CWStore *aStore; 1417 1418 if ([[Utilities completePathForFolderNode: theFolderNode separator: '/'] 1419 hasPrefix: [NSString stringWithFormat: @"/%@", _(@"Local")]]) 1420 { 1421 aStore = [self storeForName: @"GNUMAIL_LOCAL_STORE" username: NSUserName()]; 1422 } 1423 else 1424 { 1425 NSString *aServerName, *aUsername; 1426 1427 [Utilities storeKeyForFolderNode: theFolderNode 1428 serverName: &aServerName 1429 username: &aUsername]; 1430 1431 aStore = [self storeForName: aServerName username: aUsername]; 1432 } 1433 1434 return aStore; 1435} 1436 1437 1438// 1439// 1440// 1441- (id) storeForName: (NSString *) theName 1442 username: (NSString *) theUsername 1443{ 1444 return [allStores objectForKey: [NSString stringWithFormat: @"%@ @ %@", theUsername, theName]]; 1445} 1446 1447 1448// 1449// 1450// 1451- (id) storeForURLName: (CWURLName *) theURLName 1452{ 1453 id aStore; 1454 1455 if ([[theURLName protocol] caseInsensitiveCompare: @"LOCAL"] == NSOrderedSame ) 1456 { 1457 aStore = [self storeForName: @"GNUMAIL_LOCAL_STORE" username: NSUserName()]; 1458 } 1459 else 1460 { 1461 if ([self _initializeIMAPStoreWithAccountName: [Utilities accountNameForServerName: [theURLName host] 1462 username: [theURLName username]]]) 1463 { 1464 aStore = [self storeForName: [theURLName host] username: [theURLName username]]; 1465 } 1466 else 1467 { 1468 aStore = nil; 1469 } 1470 } 1471 1472 return aStore; 1473} 1474 1475 1476// 1477// 1478// 1479- (id) folderForURLName: (CWURLName *) theURLName 1480{ 1481 id aStore, aFolder; 1482 1483 aStore = [self storeForURLName: theURLName]; 1484 1485 if (!aStore) return nil; 1486 1487 if ([aStore isKindOfClass: [CWIMAPStore class]]) 1488 { 1489 aFolder = [(CWIMAPStore *)aStore folderForName: [theURLName foldername] select: NO]; 1490 } 1491 else 1492 { 1493 aFolder = [(CWLocalStore *)aStore folderForName: [theURLName foldername]]; 1494 } 1495 1496 return aFolder; 1497} 1498 1499 1500// 1501// 1502// 1503- (void) setStore: (id) theStore 1504 name: (NSString *) theName 1505 username: (NSString *) theUsername 1506{ 1507 NSString *aString; 1508 1509 aString = [NSString stringWithFormat: @"%@ @ %@", theUsername, theName]; 1510 1511 // We verify if we want to remove an opened store. 1512 if (!theStore && theName && theUsername) 1513 { 1514 FolderNode *aFolderNode; 1515 int row; 1516 1517 // For an IMAP store, we remove all children of our root node 1518 aFolderNode = [self storeFolderNodeForName: [Utilities accountNameForServerName: theName username: theUsername]]; 1519 1520#ifndef MACOSX 1521 [aFolderNode setChildren: nil]; 1522#endif 1523 [outlineView collapseItem: aFolderNode]; 1524 1525 row = [outlineView rowForItem: aFolderNode]; 1526 1527 if (row >= 0 && row < [outlineView numberOfRows]) 1528 { 1529 [outlineView selectRow: row byExtendingSelection: NO]; 1530 } 1531 1532 [allStores removeObjectForKey: aString]; 1533 return; 1534 } 1535 1536 // We always first "remove" the object in case we call this method 1537 // multiple times with the same object. 1538 RETAIN(theStore); 1539 [allStores removeObjectForKey: aString]; 1540 1541 // We {re}add it to our dictionary. 1542 [allStores setObject: theStore forKey: aString]; 1543 RELEASE(theStore); 1544} 1545 1546// 1547// FIXME: support more than one MailWindow associated to the store 1548// 1549- (void) closeWindowsForStore: (id) theStore 1550{ 1551 NSWindow *aWindow; 1552 1553 if ((aWindow = [Utilities windowForFolderName: nil store: theStore])) 1554 { 1555 [aWindow close]; 1556 } 1557 1558 [allStores removeObjectForKey: [NSString stringWithFormat: @"%@ @ %@", [(CWIMAPStore *)theStore username], [(CWIMAPStore *)theStore name]]]; 1559 [theStore close]; 1560} 1561 1562 1563// 1564// 1565// 1566- (MailboxManagerCache *) cache 1567{ 1568 return _cache; 1569} 1570 1571 1572// 1573// 1574// 1575- (void) panic: (NSData *) theData 1576 folder: (NSString *) theFolder 1577{ 1578 CWLocalStore *aLocalStore; 1579 CWLocalFolder *aFolder; 1580 1581 NSRunAlertPanel(_(@"Error!"), 1582 _(@"An error occurred while trying to open the \"%@\" mailbox. This mailbox was probably\ndeleted and a filter is trying to save mails in it. Check your filters and the special mailboxes for all accounts.\nThe message has been saved in the \"Panic\" local mailbox."), 1583 _(@"OK"), 1584 NULL, 1585 NULL, 1586 theFolder); 1587 1588 1589 aLocalStore = [self storeForName: @"GNUMAIL_LOCAL_STORE" username: NSUserName()]; 1590 1591 if (![[NSFileManager defaultManager] fileExistsAtPath: [[aLocalStore path] stringByAppendingPathComponent: @"Panic"]]) 1592 { 1593 [aLocalStore createFolderWithName: @"Panic" type: PantomimeFormatMbox contents: nil]; 1594 } 1595 1596 aFolder = [aLocalStore folderForName: @"Panic"]; 1597 1598 [aFolder appendMessageFromRawSource: theData flags: nil]; 1599} 1600 1601 1602// 1603// 1604// 1605- (void) deleteSentMessageWithID: (NSString *) theID 1606{ 1607 NSMutableDictionary *allMessages; 1608 NSString *aPath; 1609 1610 aPath = [NSString stringWithFormat: @"%@/%@", GNUMailUserLibraryPath(), @"UnsentMessages"]; 1611 1612 NS_DURING 1613 { 1614 allMessages = [NSUnarchiver unarchiveObjectWithFile: aPath]; 1615 1616 if (allMessages) 1617 { 1618 [allMessages removeObjectForKey: theID]; 1619 [NSArchiver archiveRootObject: allMessages toFile: aPath]; 1620 } 1621 } 1622 NS_HANDLER 1623 { 1624 1625 } 1626 NS_ENDHANDLER; 1627} 1628 1629 1630// 1631// 1632// 1633- (void) restoreUnsentMessages 1634{ 1635 NSMutableDictionary *allMessages; 1636 NSString *aPath; 1637 1638 aPath = [NSString stringWithFormat: @"%@/%@", GNUMailUserLibraryPath(), @"UnsentMessages"]; 1639 1640 NS_DURING 1641 { 1642 allMessages = [NSUnarchiver unarchiveObjectWithFile: aPath]; 1643 1644 if (allMessages && [allMessages count]) 1645 { 1646 int choice; 1647 1648 choice = NSRunAlertPanel(_(@"Unsent messages..."), 1649 _(@"There are unsent messages, would you like to\nrestore them?"), 1650 _(@"Yes"), // default 1651 _(@"No"), // alternate 1652 NULL ); 1653 1654 if (choice == NSAlertDefaultReturn) 1655 { 1656 EditWindowController *aController; 1657 NSEnumerator *theEnumerator; 1658 CWMessage *aMessage; 1659 NSData *aData; 1660 1661 theEnumerator = [allMessages objectEnumerator]; 1662 1663 while ((aData = [theEnumerator nextObject])) 1664 { 1665 aMessage = [[CWMessage alloc] initWithData: aData]; 1666 aController = [[EditWindowController alloc] initWithWindowNibName: @"EditWindow"]; 1667 [aController setMode: GNUMailRestoreFromDrafts]; 1668 [aController setMessageFromDraftsFolder: aMessage]; 1669 [aController updateWithMessage: aMessage]; 1670 [aController showWindow: self]; 1671 RELEASE(aMessage); 1672 } 1673 } 1674 1675 [allMessages removeAllObjects]; 1676 [NSArchiver archiveRootObject: allMessages toFile: aPath]; 1677 } 1678 } 1679 NS_HANDLER 1680 { 1681 NSDebugLog(@"Exception while restoring Unsent Messages"); 1682 } 1683 NS_ENDHANDLER; 1684} 1685 1686// 1687// 1688// 1689- (void) saveUnsentMessage: (NSData *) theMessage 1690 withID: (NSString *) theID 1691{ 1692 NSMutableDictionary *allMessages; 1693 NSString *aPath; 1694 1695 aPath = [NSString stringWithFormat: @"%@/%@", GNUMailUserLibraryPath(), @"UnsentMessages"]; 1696 1697 NS_DURING 1698 { 1699 allMessages = [NSUnarchiver unarchiveObjectWithFile: aPath]; 1700 1701 if (!allMessages) 1702 { 1703 allMessages = [NSMutableDictionary dictionary]; 1704 } 1705 1706 [allMessages setObject: theMessage forKey: theID]; 1707 [NSArchiver archiveRootObject: allMessages toFile: aPath]; 1708 } 1709 NS_HANDLER 1710 { 1711 NSLog(@"An exception occurred while saving the unsent message to %@", aPath); 1712 } 1713 NS_ENDHANDLER; 1714} 1715 1716 1717// 1718// This method appends a message to the folder specified in theURLName. 1719// 1720- (void) addMessage: (NSData *) theMessage 1721 toFolder: (CWURLName *) theURLName 1722{ 1723 NSString *aFolderName; 1724 CWFolder *aFolder; 1725 1726 aFolder = [self folderForURLName: theURLName]; 1727 aFolderName = [theURLName foldername]; 1728 1729 if (!aFolder) 1730 { 1731 [self panic: theMessage folder: aFolderName]; 1732 } 1733 1734 [aFolder setProperty: [NSDate date] forKey: FolderExpireDate]; 1735 [self transferMessage: theMessage flags: nil folder: aFolder]; 1736} 1737 1738 1739// 1740// 1741// 1742- (CWMessage *) messageFromDraftsFolder 1743{ 1744 id aMailWindowController; 1745 CWMessage *aMessage; 1746 1747 aMailWindowController = [[GNUMail lastMailWindowOnTop] delegate]; 1748 1749 // We first verify if current folder is a Drafts folder. 1750 if (aMailWindowController && [aMailWindowController isKindOfClass: [MailWindowController class]]) 1751 { 1752 if (![Utilities stringValueOfURLName: [Utilities stringValueOfURLNameFromFolder: 1753 [aMailWindowController folder]] 1754 isEqualTo: @"DRAFTSFOLDERNAME"]) 1755 { 1756 return nil; 1757 } 1758 } 1759 1760 if ([[aMailWindowController folder] count] > 0 && [aMailWindowController selectedMessage]) 1761 { 1762 aMessage = [aMailWindowController selectedMessage]; 1763 } 1764 else 1765 { 1766 aMessage = nil; 1767 } 1768 1769 return aMessage; 1770} 1771 1772 1773// 1774// 1775// 1776- (NSDictionary *) allStores 1777{ 1778 return [NSDictionary dictionaryWithDictionary: allStores]; 1779} 1780 1781 1782// 1783// This method is used under OS X since we must "swap" the current 1784// outline view in the Mailbox Manager to match the one currently 1785// on top in the NSDrawer. This is needed so -open: and other methods 1786// can work properly with the "current" outline view. 1787// 1788- (void) setCurrentOutlineView: (id) theOutlineView 1789{ 1790 outlineView = theOutlineView; 1791} 1792 1793 1794// 1795// 1796// 1797- (void) updateFolderInformation: (NSDictionary *) theInformation 1798{ 1799 CWFolderInformation *aFolderInformation; 1800 NSString *aFolderName; 1801 1802 1803 aFolderInformation = [theInformation objectForKey: @"FOLDER_INFORMATION"]; 1804 aFolderName = [[theInformation objectForKey: @"FOLDER_NAME"] stringByReplacingOccurrencesOfCharacter: 1805 [[theInformation objectForKey: @"FOLDER_SEPARATOR"] characterAtIndex: 0] 1806 withCharacter: '/']; 1807 1808 [_cache setAllValuesForStoreName: [theInformation objectForKey: @"STORE_NAME"] 1809 folderName: aFolderName 1810 username: [theInformation objectForKey: @"USERNAME"] 1811 nbOfMessages: [aFolderInformation nbOfMessages] 1812 nbOfUnreadMessages: [aFolderInformation nbOfUnreadMessages]]; 1813 1814 // 1815 // We now get the right outline view item to refresh. This is considerably faster 1816 // than calling -setNeedsDisplay: on the entire outline view (which will result 1817 // in a plethora of method calls to get the "right values"). 1818 // 1819 [self updateOutlineViewForFolder: aFolderName 1820 store: [theInformation objectForKey: @"STORE_NAME"] 1821 username: [theInformation objectForKey: @"USERNAME"] 1822 controller: nil]; 1823} 1824 1825 1826 1827// 1828// This method is used to refresh ONLY the item associated with the Store/Folder. 1829// 1830- (void) updateOutlineViewForFolder: (NSString *) theFolder 1831 store: (NSString *) theStore 1832 username: (NSString *) theUsername 1833 controller: (id) theController 1834{ 1835 if (theController) 1836 { 1837 [[theController folder] updateCache]; 1838 [theController tableViewShouldReloadData]; 1839 [theController updateStatusLabel]; 1840 } 1841 else 1842 { 1843 FolderNode *aFolderNode, *aRootNode; 1844 int row; 1845 1846 if ([theStore isEqualToString: @"GNUMAIL_LOCAL_STORE"]) 1847 { 1848 aRootNode = localNodes; 1849 } 1850 else 1851 { 1852 aRootNode = [self storeFolderNodeForName: [Utilities accountNameForServerName: theStore username: theUsername]]; 1853 } 1854 1855 aFolderNode = [Utilities folderNodeForPath: theFolder 1856 using: aRootNode 1857 separator: '/']; 1858 1859 row = [outlineView rowForItem: aFolderNode]; 1860 1861 if (row >= 0 && row < [outlineView numberOfRows]) 1862 { 1863 [outlineView setNeedsDisplayInRect: [outlineView rectOfRow: row]]; 1864 } 1865 } 1866} 1867 1868 1869// 1870// class methods 1871// 1872+ (id) singleInstance 1873{ 1874 if (!singleInstance) 1875 { 1876#ifdef MACOSX 1877 singleInstance = [[MailboxManagerController alloc] init]; 1878#else 1879 singleInstance = [[MailboxManagerController alloc] initWithWindowNibName: @"MailboxManager"]; 1880#endif 1881 } 1882 1883 return singleInstance; 1884} 1885 1886 1887// 1888// Other methods 1889// 1890- (void) openFolderWithURLName: (CWURLName *) theURLName 1891 sender: (id) theSender 1892{ 1893 if ([[theURLName protocol] caseInsensitiveCompare: @"LOCAL"] == NSOrderedSame) 1894 { 1895 [self _openLocalFolderWithName: [theURLName foldername] 1896 sender: theSender]; 1897 } 1898 else if ([[theURLName protocol] caseInsensitiveCompare: @"IMAP"] == NSOrderedSame) 1899 { 1900 if ([self _initializeIMAPStoreWithAccountName: [Utilities accountNameForServerName: [theURLName host] 1901 username: [theURLName username]]]) 1902 { 1903 [self _openIMAPFolderWithName: [theURLName foldername] 1904 store: (CWIMAPStore *)[self storeForName: [theURLName host] username: [theURLName username]] 1905 sender: theSender]; 1906 } 1907 } 1908} 1909 1910 1911// 1912// 1913// 1914- (void) reloadAllFolders 1915{ 1916 DESTROY(localNodes); 1917 1918 // We remove all our elements 1919 [_allFolders removeAllObjects]; 1920 1921 // We add our local folder, if we need to 1922 localNodes = [Utilities folderNodesFromFolders: [[self storeForName: @"GNUMAIL_LOCAL_STORE" 1923 username: NSUserName()] folderEnumerator] 1924 separator: '/']; 1925 1926 [localNodes setName: _(@"Local")]; 1927 [localNodes setParent: nil]; 1928 1929 if ([localNodes childCount] > 0) 1930 { 1931 [_allFolders addObject: localNodes]; 1932 } 1933 1934 RETAIN(localNodes); 1935 1936 // We verify if the ACCOUNTS preferences have been defined. 1937 if ([[NSUserDefaults standardUserDefaults] objectForKey: @"ACCOUNTS"]) 1938 { 1939 NSMutableDictionary *allAccounts; 1940 NSDictionary *allValues; 1941 NSEnumerator *allKeys; 1942 CWIMAPStore *aStore; 1943 NSString *aKey; 1944 1945 allAccounts = [[NSMutableDictionary alloc] init]; 1946 [allAccounts addEntriesFromDictionary: [Utilities allEnabledAccounts]]; 1947 1948 allKeys = [[[allAccounts allKeys] sortedArrayUsingSelector: @selector(compare:)] objectEnumerator]; 1949 1950 // We build a correct subset of all our IMAP servers defined in all accounts 1951 while ((aKey = [allKeys nextObject])) 1952 { 1953 allValues = [[allAccounts objectForKey: aKey] objectForKey: @"RECEIVE"]; 1954 1955 // We add it only if it's an IMAP server AND if we receive mails either 1956 // manually or automatically 1957 if ([[allValues objectForKey: @"SERVERTYPE"] intValue] == IMAP && 1958 [[allValues objectForKey: @"RETRIEVEMETHOD"] intValue] != NEVER) 1959 { 1960 NSString *aServerName, *aUsername; 1961 FolderNode *aFolderNode; 1962 1963 aServerName = [allValues objectForKey: @"SERVERNAME"]; 1964 aUsername = [allValues objectForKey: @"USERNAME"]; 1965 1966 aFolderNode = [FolderNode folderNodeWithName: [NSString stringWithFormat: @"%@", aKey] 1967 parent: nil]; 1968 1969 [_allFolders addObject: aFolderNode]; 1970 1971 // If our IMAP Store has been previously initialized, we re-initialize it in order to get 1972 // the most recent values for the {subscribed} folders 1973 if ((aStore = [self storeForName: aServerName username: aUsername])) 1974 { 1975 NSNumber *aNumber; 1976 1977 aNumber = [allValues objectForKey: @"SHOW_WHICH_MAILBOXES"]; 1978 1979 if (aNumber && [aNumber intValue] == IMAP_SHOW_SUBSCRIBED_ONLY) 1980 { 1981 [self reloadFoldersForStore: aStore folders: [aStore subscribedFolderEnumerator]]; 1982 } 1983 else 1984 { 1985 [self reloadFoldersForStore: aStore folders: [aStore folderEnumerator]]; 1986 } 1987 } 1988 } 1989 } 1990 1991 RELEASE(allAccounts); 1992 } 1993 1994 [outlineView abortEditing]; 1995 1996 // We inform our outline view to reload its data. 1997 [outlineView reloadData]; 1998 1999 // We always expand our root item 2000 [outlineView expandItem: _allFolders]; 2001 2002 // We now select and expand the 'Local' folder if there's no IMAP folders defined 2003 if ([_allFolders count] == 1 && [_allFolders lastObject] == localNodes) 2004 { 2005 [outlineView expandItem: localNodes]; 2006 [outlineView selectRow: [outlineView rowForItem: localNodes] 2007 byExtendingSelection: NO]; 2008 } 2009} 2010 2011 2012// 2013// 2014// 2015- (void) transferMessage: (NSData *) theMessage 2016 flags: (CWFlags *) theFlags 2017 folder: (CWFolder *) theFolder 2018{ 2019 CWFlags *flags; 2020 2021 // 2022 // We transfer the message. If we are transferring to a Sent folder, mark it as read. 2023 // 2024 flags = theFlags; 2025 2026 if ([Utilities stringValueOfURLName: [Utilities stringValueOfURLNameFromFolder: theFolder] 2027 isEqualTo: @"SENTFOLDERNAME"]) 2028 { 2029 flags = [[CWFlags alloc] initWithFlags: PantomimeSeen]; 2030 AUTORELEASE(flags); 2031 } 2032 2033 // 2034 // For IMAP mailboxes, we show some kind of progress indicators 2035 // when performing lengthy operations. 2036 // 2037 if ([theFolder isKindOfClass: [CWIMAPFolder class]]) 2038 { 2039 Task *aTask; 2040 2041 aTask = [[TaskManager singleInstance] taskForService: [theFolder store]]; 2042 2043 if (aTask) 2044 { 2045 aTask->total_count++; 2046 aTask->total_size += (float)[theMessage length]/(float)1024; 2047 } 2048 else 2049 { 2050 aTask = [[Task alloc] init]; 2051 aTask->op = SAVE_ASYNC; 2052 [aTask setKey: [Utilities accountNameForFolder: theFolder]]; 2053 [aTask setMessage: theMessage]; 2054 aTask->total_size = (float)[theMessage length]/(float)1024; 2055 aTask->immediate = YES; 2056 aTask->service = [theFolder store]; 2057 [[TaskManager singleInstance] addTask: aTask]; 2058 RELEASE(aTask); 2059 } 2060 } 2061 2062 [theFolder appendMessageFromRawSource: theMessage flags: flags]; 2063} 2064 2065 2066// 2067// theOperation == COPY / MOVE 2068// returns the numbers of transferred messages, -1 on error. 2069// 2070- (void) transferMessages: (NSArray *) theMessages 2071 fromStore: (id) theSourceStore 2072 fromFolder: (id) theSourceFolder 2073 toStore: (id) theDestinationStore 2074 toFolder: (id) theDestinationFolder 2075 operation: (int) theOperation 2076{ 2077 if (!theMessages || [theMessages count] == 0 || !theSourceFolder || !theDestinationFolder ) 2078 { 2079 NSBeep(); 2080 } 2081 2082 // 2083 // If we are transferring messages from an IMAPFolder to an IMAPFolder on the SAME 2084 // IMAPStore, let's use Pantomime's IMAPFolder: -copyMessage: toFolder: method 2085 // since the operation is gonna be server-side - so MUCH FASTER. 2086 // 2087 if ([theSourceStore isKindOfClass: [CWIMAPStore class]] && theSourceStore == theDestinationStore) 2088 { 2089 [theSourceFolder copyMessages: theMessages 2090 toFolder: [[(CWIMAPFolder *)theDestinationFolder name] 2091 stringByReplacingOccurrencesOfCharacter: '/' 2092 withCharacter: [theDestinationStore folderSeparator]]]; 2093 2094 // If we are moving the messages, mark them as deleted. 2095 if (theOperation == MOVE_MESSAGES) 2096 { 2097 CWMessage *aMessage; 2098 CWFlags *theFlags; 2099 int i, count; 2100 2101 count = [theMessages count]; 2102 for (i = 0; i < count; i++) 2103 { 2104 aMessage = [theMessages objectAtIndex: i]; 2105 theFlags = [[aMessage flags] copy]; 2106 [theFlags add: PantomimeDeleted]; 2107 [aMessage setFlags: theFlags]; 2108 RELEASE(theFlags); 2109 } 2110 } 2111 } 2112 // 2113 // We are NOT doing an IMAP-to-IMAP (on the same IMAPStore) copy. 2114 // Let's grab the message's data and use it. If it's not available, 2115 // we load it asynchronously and create a corresponding task 2116 // in the TaskManager for showing progress of the operation. 2117 // 2118 else 2119 { 2120 NSMutableArray *messagesToLoad; 2121 NSAutoreleasePool *pool; 2122 CWMessage *aMessage; 2123 NSData *aData; 2124 Task *aTask; 2125 NSUInteger i; 2126 2127 messagesToLoad = [NSMutableArray array]; 2128 2129 aTask = [[Task alloc] init]; 2130 aTask->op = LOAD_ASYNC; 2131 aTask->immediate = YES; 2132 aTask->service = [theSourceFolder store]; 2133 [aTask setKey: [Utilities accountNameForFolder: theSourceFolder]]; 2134 2135 for (i = 0; i < [theMessages count]; i++) 2136 { 2137 pool = [[NSAutoreleasePool alloc] init]; 2138 2139 aMessage = [theMessages objectAtIndex: i]; 2140 [aMessage setProperty: [NSNumber numberWithInt: theOperation] forKey: MessageOperation]; 2141 aData = [aMessage rawSource]; 2142 2143 if (aData) 2144 { 2145 CWFlags *theFlags; 2146 2147 // We get our flags but we remove the PantomimeDeleted flag from them 2148 theFlags = [[aMessage flags] copy]; 2149 [theFlags remove: PantomimeDeleted]; 2150 2151 [[TaskManager singleInstance] setMessage: aMessage forHash: [aData hash]]; 2152 [self transferMessage: aData 2153 flags: AUTORELEASE([theFlags copy]) 2154 folder: theDestinationFolder]; 2155 RELEASE(theFlags); 2156 } 2157 else 2158 { 2159 // The raw source is NOT available right now. We write the properties 2160 // so we know we must transfer it once it's loaded. 2161 [aMessage setProperty: [NSNumber numberWithBool: YES] forKey: MessageLoading]; 2162 [aMessage setProperty: theDestinationStore forKey: MessageDestinationStore]; 2163 [aMessage setProperty: theDestinationFolder forKey: MessageDestinationFolder]; 2164 [messagesToLoad addObject: aMessage]; 2165 aTask->total_size += (float)[aMessage size]/(float)1024; 2166 } 2167 2168 RELEASE(pool); 2169 } 2170 2171 if ([messagesToLoad count]) 2172 { 2173 [aTask setMessage: messagesToLoad]; 2174 aTask->total_count = [messagesToLoad count]; 2175 [[TaskManager singleInstance] addTask: aTask]; 2176 } 2177 2178 RELEASE(aTask); 2179 } 2180} 2181 2182 2183// 2184// 2185// 2186- (void) reloadFoldersForStore: (id) theStore 2187 folders: (NSEnumerator *) theFolders 2188{ 2189 NSMutableDictionary *allAccounts, *allValues, *theAccount; 2190 FolderNode *aFolderNode, *nodes; 2191 NSString *theAccountName; 2192 NSArray *allFolders; 2193 2194 aFolderNode = [self storeFolderNodeForName: [Utilities accountNameForServerName: [(CWService *)theStore name] 2195 username: [theStore username]]]; 2196 2197 allFolders = [NSArray arrayWithArray: [theFolders allObjects]]; 2198 nodes = [Utilities folderNodesFromFolders: [allFolders objectEnumerator] 2199 separator: [theStore folderSeparator]]; 2200 RETAIN(nodes); 2201 [aFolderNode setChildren: [nodes children]]; 2202 RELEASE(nodes); 2203 2204#warning optimize by reloading only the item 2205 [outlineView reloadData]; 2206 [outlineView expandItem: aFolderNode]; 2207 2208 // 2209 // We finally cache all/subscribed folders in the user's defaults for this account 2210 // 2211 theAccountName = [Utilities accountNameForServerName: [(CWIMAPStore *)theStore name] username: [theStore username]]; 2212 allAccounts = [[NSMutableDictionary alloc] initWithDictionary: [[NSUserDefaults standardUserDefaults] 2213 objectForKey: @"ACCOUNTS"]]; 2214 theAccount = [[NSMutableDictionary alloc] initWithDictionary: [allAccounts objectForKey: theAccountName]]; 2215 allValues = [[NSMutableDictionary alloc] initWithDictionary: [theAccount objectForKey: @"RECEIVE"]]; 2216 2217 // 2218 // We write back the information 2219 // 2220 [allValues setObject: allFolders forKey: @"SUBSCRIBED_FOLDERS"]; 2221 [theAccount setObject: allValues forKey: @"RECEIVE"]; 2222 [allAccounts setObject: theAccount forKey: theAccountName]; 2223 [[NSUserDefaults standardUserDefaults] setObject: allAccounts forKey: @"ACCOUNTS"]; 2224 [[NSUserDefaults standardUserDefaults] synchronize]; 2225 2226 RELEASE(allValues); 2227 RELEASE(theAccount); 2228 RELEASE(allAccounts); 2229} 2230 2231 2232// 2233// 2234// 2235- (FolderNode *) storeFolderNodeForName: (NSString *) theName 2236{ 2237 NSUInteger i, count; 2238 2239 count = [_allFolders count]; 2240 2241 for (i = 0; i < count; i++) 2242 { 2243 FolderNode *aFolderNode; 2244 2245 aFolderNode = [_allFolders objectAtIndex: i]; 2246 2247 if ([theName isEqualToString: [aFolderNode name]]) 2248 { 2249 return aFolderNode; 2250 } 2251 } 2252 2253 return nil; 2254} 2255 2256 2257// 2258// 2259// 2260- (void) saveMessageInDraftsFolderForController: (EditWindowController *) theEditWindowController 2261{ 2262 NSString *theAccountName, *aString; 2263 CWURLName *theURLName; 2264 2265 // 2266 // We first update the current message content with the current 2267 // content of the view and we synchronize our popup button. 2268 // 2269 [theEditWindowController updateMessageContentFromTextView]; 2270 [[theEditWindowController accountPopUpButton] synchronizeTitleAndSelectedItem]; 2271 2272 theAccountName = [(ExtendedMenuItem *)[[theEditWindowController accountPopUpButton] selectedItem] key]; 2273 2274 // We get the value of the Drafts folder in the user defaults. If the Drafts folder isn't set 2275 // for this particular account, we are the user about this and return immediately. 2276 aString = [[[[[NSUserDefaults standardUserDefaults] objectForKey: @"ACCOUNTS"] objectForKey: theAccountName] 2277 objectForKey: @"MAILBOXES"] objectForKey: @"DRAFTSFOLDERNAME"]; 2278 2279 if (!aString) 2280 { 2281 NSRunAlertPanel(_(@"Error!"), 2282 _(@"The Drafts mailbox is not set for the %@ account.\nPlease set it from the mailboxes list using\nthe contextual menu."), 2283 _(@"OK"), 2284 NULL, 2285 NULL, 2286 theAccountName); 2287 return; 2288 } 2289 2290 theURLName = [[CWURLName alloc] initWithString: aString 2291 path: [[NSUserDefaults standardUserDefaults] objectForKey: @"LOCALMAILDIR"]]; 2292 2293 [self addMessage: [[theEditWindowController message] dataValue] toFolder: theURLName]; 2294 2295 // If this message is already in the Drafts folder, set the "deleted" flag 2296 // of the original message. 2297 if ([theEditWindowController mode] == GNUMailRestoreFromDrafts) 2298 { 2299 CWFlags *theFlags; 2300 2301 theFlags = [[[theEditWindowController message] flags] copy]; 2302 [theFlags add: PantomimeDeleted]; 2303 [[theEditWindowController message] setFlags: theFlags]; 2304 RELEASE(theFlags); 2305 2306 [[NSNotificationCenter defaultCenter] postNotificationName: ReloadMessageList 2307 object: nil 2308 userInfo: nil]; 2309 } 2310 2311 // We mark the window's document as non-edited 2312 [[theEditWindowController window] setDocumentEdited: NO]; 2313 2314 RELEASE(theURLName); 2315} 2316 2317@end 2318 2319 2320// 2321// Private methods 2322// 2323@implementation MailboxManagerController (Private) 2324 2325- (void) _accountsHaveChanged: (id) sender 2326{ 2327 NSEnumerator *theEnumerator; 2328 NSArray *allAccounts; 2329 id aStore; 2330 2331 [self _updateContextMenu]; 2332 [self reloadAllFolders]; 2333 2334 allAccounts = [[Utilities allEnabledAccounts] allKeys]; 2335 theEnumerator = [allStores objectEnumerator]; 2336 2337 while ((aStore = [theEnumerator nextObject])) 2338 { 2339 if ([aStore isKindOfClass: [CWIMAPStore class]] && 2340 ![allAccounts containsObject: [Utilities accountNameForServerName: [(CWIMAPStore *)aStore name] username: [(CWIMAPStore *)aStore username]]]) 2341 { 2342 [self closeWindowsForStore: aStore]; 2343 } 2344 } 2345} 2346 2347 2348// 2349// 2350// 2351- (BOOL) _deletingDefaultMailbox: (NSString **) theMailboxName 2352 usingURLNameAsString: (NSString *) theURLNameAsString 2353{ 2354 if ([Utilities stringValueOfURLName: theURLNameAsString isEqualTo: @"INBOXFOLDERNAME"]) 2355 { 2356 *theMailboxName = _(@"Inbox"); 2357 return YES; 2358 } 2359 else if ([Utilities stringValueOfURLName: theURLNameAsString isEqualTo: @"SENTFOLDERNAME"]) 2360 { 2361 *theMailboxName = _(@"Sent"); 2362 return YES; 2363 } 2364 else if ([Utilities stringValueOfURLName: theURLNameAsString isEqualTo: @"DRAFTSFOLDERNAME"]) 2365 { 2366 *theMailboxName = _(@"Drafts"); 2367 return YES; 2368 } 2369 else if ([Utilities stringValueOfURLName: theURLNameAsString isEqualTo: @"TRASHFOLDERNAME"]) 2370 { 2371 *theMailboxName = _(@"Trash"); 2372 return YES; 2373 } 2374 2375 return NO; 2376} 2377 2378 2379// 2380// 2381// 2382- (void) _folderCreateCompleted: (NSNotification *) theNotification 2383{ 2384 NSString *aString, *aStoreName, *aUsername; 2385 id o; 2386 2387 // We get the account in order to verify if we must subscribe to the IMAP mailbox 2388 o = [theNotification object]; 2389 2390 aStoreName = @"GNUMAIL_LOCAL_STORE"; 2391 aUsername = NSUserName(); 2392 2393 if ([o isKindOfClass: [CWIMAPStore class]]) 2394 { 2395 aStoreName = [(CWIMAPStore *)o name]; 2396 aUsername = [o username]; 2397 2398 aString = [Utilities accountNameForServerName: aStoreName username: aUsername]; 2399 2400 if ([[[[[Utilities allEnabledAccounts] objectForKey: aString] objectForKey: @"RECEIVE"] 2401 objectForKey: @"SHOW_WHICH_MAILBOXES"] intValue] == IMAP_SHOW_SUBSCRIBED_ONLY) 2402 { 2403 [o subscribeToFolderWithName: [[theNotification userInfo] objectForKey: @"Name"]]; 2404 return; 2405 } 2406 } 2407 2408 // We update the cache in case we imported messages when creating the mailbox. 2409 [_cache setAllValuesForStoreName: aStoreName 2410 folderName: [[theNotification userInfo] objectForKey: @"Name"] 2411 username: aUsername 2412 nbOfMessages: ([[theNotification userInfo] objectForKey: @"Count"] ? [[[theNotification userInfo] objectForKey: @"Count"] unsignedIntValue] : 0) 2413 nbOfUnreadMessages: 0]; 2414 2415 [self _folderSubscribeCompleted: theNotification]; 2416} 2417 2418 2419// 2420// 2421// 2422- (void) _folderCreateFailed: (NSNotification *) theNotification 2423{ 2424 NSRunInformationalAlertPanel(_(@"Mailbox error!"), 2425 _(@"An error occurred while creating the %@ mailbox. This mailbox probably already exists\nor you don't have permission to create it."), 2426 _(@"OK"), 2427 NULL, 2428 NULL, 2429 [[theNotification userInfo] objectForKey: @"Name"]); 2430} 2431 2432 2433// 2434// 2435// 2436- (void) _folderDeleteCompleted: (NSNotification *) theNotification 2437{ 2438 NSString *aStoreName, *aUsername; 2439 id aStore, item; 2440 2441 aStoreName = @"GNUMAIL_LOCAL_STORE"; 2442 aUsername = NSUserName(); 2443 aStore = [theNotification object]; 2444 2445#warning FIXME get the right item in case the selection has changed 2446 item = [outlineView itemAtRow: [outlineView selectedRow]]; 2447 2448 // Delete cache files, ONLY if this is an IMAP folder! 2449 if ([aStore isKindOfClass: [CWIMAPStore class]] ) 2450 { 2451 NSString *aKey, *cacheFilePath; 2452 FolderNode *node; 2453 NSUInteger i; 2454 2455 aStoreName = [(CWIMAPStore *)aStore name]; 2456 aUsername = [(CWIMAPStore *)aStore username]; 2457 2458 aKey = [NSString stringWithFormat: @"%@ @ %@", aUsername, aStoreName]; 2459 2460 cacheFilePath = [NSString stringWithFormat: @"%@/IMAPCache_%@_%@", 2461 GNUMailUserLibraryPath(), 2462 [Utilities flattenPathFromString: aKey 2463 separator: '/'], 2464 [Utilities flattenPathFromString: 2465 [Utilities pathOfFolderFromFolderNode: (FolderNode *)item 2466 separator: [(id<CWStore>)aStore folderSeparator]] 2467 separator: '/']]; 2468 2469 // We remove the file 2470 NS_DURING 2471 [[NSFileManager defaultManager] removeFileAtPath: cacheFilePath 2472 handler: nil]; 2473 NS_HANDLER 2474 // Under GNUstep, if we pass something that can't be converted to a cString 2475 // to -removeFileAtPath, it throws an exception. 2476 NSDebugLog(@"Exception occurred while removing the cache file."); 2477 NS_ENDHANDLER; 2478 2479 // We remove the cache file of the children of this folder, if any. 2480 for (i = 0; i < [(FolderNode *)item childCount]; i++) 2481 { 2482 node = [(FolderNode *)item childAtIndex: i]; 2483 cacheFilePath = [NSString stringWithFormat: @"%@/IMAPCache_%@_%@", 2484 GNUMailUserLibraryPath(), 2485 [Utilities flattenPathFromString: aKey 2486 separator: '/'], 2487 [Utilities flattenPathFromString: 2488 [Utilities pathOfFolderFromFolderNode: node 2489 separator: [(id<CWStore>)aStore folderSeparator]] 2490 separator: '/']]; 2491 2492 NS_DURING 2493 [[NSFileManager defaultManager] removeFileAtPath: cacheFilePath 2494 handler: nil]; 2495 NS_HANDLER 2496 // Under GNUstep, if we pass something that can't be converted to a cString 2497 // to -removeFileAtPath, it throws an exception. 2498 NSDebugLog(@"Exception occurred while removing the cache file."); 2499 NS_ENDHANDLER; 2500 } 2501 2502 // We get the account in order to verify if we must unsubscribe to the IMAP mailbox 2503 aKey = [Utilities accountNameForServerName: aStoreName username: aUsername]; 2504 2505 if ([[[[[Utilities allEnabledAccounts] objectForKey: aKey] objectForKey: @"RECEIVE"] 2506 objectForKey: @"SHOW_WHICH_MAILBOXES"] intValue] == IMAP_SHOW_SUBSCRIBED_ONLY) 2507 { 2508 [aStore unsubscribeToFolderWithName: [[theNotification userInfo] objectForKey: @"Name"]]; 2509 return; 2510 } 2511 } 2512 2513 // We delete our cache entries 2514 [_cache removeAllValuesForStoreName: aStoreName 2515 folderName: [Utilities pathOfFolderFromFolderNode: (FolderNode *)item separator: '/'] 2516 username: aUsername]; 2517 2518 [self _reloadFoldersAndExpandParentsFromNode: [item parent] 2519 selectNodeWithPath: [Utilities completePathForFolderNode: [item parent] 2520 separator: '/']]; 2521} 2522 2523 2524// 2525// 2526// 2527- (void) _folderDeleteFailed: (NSNotification *) theNotification 2528{ 2529 NSRunInformationalAlertPanel(_(@"Mailbox error!"), 2530 _(@"An error occurred while deleting the %@ mailbox. This mailbox is probably already deleted\nor the server does not support deleting open mailboxes."), 2531 _(@"OK"), 2532 NULL, 2533 NULL, 2534 [[theNotification userInfo] objectForKey: @"Name"]); 2535} 2536 2537 2538// 2539// 2540// 2541- (void) _folderRenameCompleted: (NSNotification *) theNotification 2542{ 2543 NSString *aSourceURL, *aDestinationURL, *aString, *aName, *aNewName; 2544 id aWindow, aStore; 2545 2546 aStore = [theNotification object]; 2547 aName = [[theNotification userInfo] objectForKey: @"Name"]; 2548 aNewName = [[theNotification userInfo] objectForKey: @"NewName"]; 2549 2550 // We build our right URLs 2551 if ( [(id<NSObject>)aStore isKindOfClass: [CWLocalStore class]] ) 2552 { 2553 aSourceURL = [NSString stringWithFormat: @"local://%@/%@", 2554 [[NSUserDefaults standardUserDefaults] objectForKey: @"LOCALMAILDIR"], 2555 aName]; 2556 aDestinationURL = [NSString stringWithFormat: @"local://%@/%@", 2557 [[NSUserDefaults standardUserDefaults] objectForKey: @"LOCALMAILDIR"], 2558 aNewName]; 2559 } 2560 else 2561 { 2562 aSourceURL = [NSString stringWithFormat: @"imap://%@@%@/%@", 2563 [(CWIMAPStore *)aStore username], 2564 [(CWIMAPStore *)aStore name], 2565 aName]; 2566 aDestinationURL = [NSString stringWithFormat: @"imap://%@@%@/%@", 2567 [(CWIMAPStore *)aStore username], 2568 [(CWIMAPStore *)aStore name], 2569 aNewName]; 2570 } 2571 2572 // We update our filters. 2573 [[FilterManager singleInstance] updateFiltersFromOldPath: aSourceURL toPath: aDestinationURL]; 2574 2575 // We update our "MAILBOXES" for all accounts 2576 [self _updateMailboxesFromOldPath: aSourceURL toPath: aDestinationURL]; 2577 2578 2579 // 2580 // Then, we must verify if we must rename our IMAP cache file. 2581 // 2582 if ([(id<NSObject>)aStore isKindOfClass: [CWIMAPStore class]]) 2583 { 2584 NSString *aKey, *aSourcePath, *aDestinationPath; 2585 2586 // FIXME - buggy? IMAPStore must be updated to update the pathToCache ivar in IMAPCacheManager 2587 // for an open IMAPFolder. 2588 aKey = [NSString stringWithFormat: @"%@ @ %@", 2589 [(CWIMAPStore *)aStore username], 2590 [(CWIMAPStore *)aStore name]]; 2591 2592 aSourcePath = [NSString stringWithFormat: @"%@/IMAPCache_%@_%@", 2593 GNUMailUserLibraryPath(), 2594 [Utilities flattenPathFromString: aKey 2595 separator: '/'], 2596 [Utilities flattenPathFromString: aName 2597 separator: [(id<CWStore>)aStore folderSeparator]] ]; 2598 2599 aDestinationPath = [NSString stringWithFormat: @"%@/IMAPCache_%@_%@", 2600 GNUMailUserLibraryPath(), 2601 [Utilities flattenPathFromString: aKey 2602 separator: '/'], 2603 [Utilities flattenPathFromString: aNewName 2604 separator: [(id<CWStore>)aStore folderSeparator]] ]; 2605 2606 [[NSFileManager defaultManager] movePath: aSourcePath 2607 toPath: aDestinationPath 2608 handler: nil]; 2609 2610 } 2611 2612 // Success! Let's refresh our MM. The _reloadFoldersAndExpandParentsFromNode:: 2613 // method expects to have the store name before the node's path. 2614 if ( [(id<NSObject>)aStore isKindOfClass: [CWLocalStore class]] ) 2615 { 2616 aString = [NSString stringWithFormat: @"/%@/%@", _(@"Local"), aNewName]; 2617 } 2618 else 2619 { 2620 aString = [NSString stringWithFormat: @"/%@/%@", 2621 [Utilities accountNameForServerName: [(CWIMAPStore *)aStore name] username: [(CWIMAPStore *)aStore username]], 2622 aNewName]; 2623 } 2624 2625 [self _reloadFoldersAndExpandParentsFromNode: [[outlineView itemAtRow: [outlineView selectedRow]] parent] 2626 selectNodeWithPath: aString]; 2627 2628 // We also refresh our window's title 2629 aWindow = [Utilities windowForFolderName: aNewName store: aStore]; 2630 2631 if (aWindow) 2632 { 2633 [[aWindow windowController] updateWindowTitle]; 2634 } 2635} 2636 2637 2638// 2639// 2640// 2641- (void) _folderRenameFailed: (NSNotification *) theNotification 2642{ 2643 NSRunInformationalAlertPanel(_(@"Mailbox error!"), 2644 _(@"An error occurred while renaming the %@ mailbox to %@. This mailbox probably already exists\nor you don't have permission to rename it."), 2645 _(@"OK"), 2646 NULL, 2647 NULL, 2648 [[theNotification userInfo] objectForKey: @"Name"], 2649 [[theNotification userInfo] objectForKey: @"NewName"]); 2650} 2651 2652 2653// 2654// 2655// 2656- (void) _folderSubscribeCompleted: (NSNotification *) theNotification 2657{ 2658 NSString *aString; 2659 id item; 2660 NSInteger row; 2661 2662#warning FIXME get the right item in case the selection has changed 2663 row = [outlineView selectedRow]; 2664 2665 if (row < 0) return; 2666 item = [outlineView itemAtRow: row]; 2667 2668 aString = [NSString stringWithFormat: @"%@/%@", [Utilities completePathForFolderNode: item separator: '/'], 2669 [[theNotification userInfo] objectForKey: @"Name"]]; 2670 2671 [self _reloadFoldersAndExpandParentsFromNode: item selectNodeWithPath: aString]; 2672} 2673 2674 2675// 2676// 2677// 2678- (void) _folderUnsubscribeCompleted: (NSNotification *) theNotification 2679{ 2680 NSString *aString; 2681 id item; 2682 NSInteger row; 2683 2684#warning FIXME get the right item in case the selection has changed 2685 row = [outlineView selectedRow]; 2686 2687 if (row < 0) return; 2688 item = [outlineView itemAtRow: row]; 2689 2690 aString = [NSString stringWithFormat: @"%@/%@", [Utilities completePathForFolderNode: item separator: '/'], 2691 [[theNotification userInfo] objectForKey: @"Name"]]; 2692 2693 [self _reloadFoldersAndExpandParentsFromNode: item 2694 selectNodeWithPath: aString]; 2695} 2696 2697 2698// 2699// 2700// 2701- (BOOL) _initializeIMAPStoreWithAccountName: (NSString *) theAccountName 2702{ 2703 NSString *aServerName, *aUsername; 2704 NSDictionary *allValues; 2705 CWIMAPStore *aStore; 2706 NSNumber *portValue; 2707 Task *aTask; 2708 2709 // We begin by searching in our ACCOUNTS values for the right account. 2710 // Now, let's get all the receive values 2711 allValues = [[[[NSUserDefaults standardUserDefaults] objectForKey: @"ACCOUNTS"] 2712 objectForKey: theAccountName] objectForKey: @"RECEIVE"]; 2713 2714 portValue = [allValues objectForKey: @"PORT"]; 2715 2716 // We use the default IMAP port if it's not defined. 2717 if (!portValue) 2718 { 2719 portValue = [NSNumber numberWithInt: 143]; 2720 } 2721 2722 // We get our username 2723 aUsername = [allValues objectForKey: @"USERNAME"]; 2724 2725 // We get our servername 2726 aServerName = [allValues objectForKey: @"SERVERNAME"]; 2727 2728 // We first verify if we haven't already cached our store. If so, 2729 // we simply return since the Store has already been initialized. 2730 if ([self storeForName: aServerName username: aUsername]) 2731 { 2732 return YES; 2733 } 2734 2735 aStore = [[CWIMAPStore alloc] initWithName: aServerName 2736 port: [portValue intValue]]; 2737 [aStore addRunLoopMode: NSEventTrackingRunLoopMode]; 2738 [aStore addRunLoopMode: NSModalPanelRunLoopMode]; 2739 [aStore setUsername: [allValues objectForKey: @"USERNAME"]]; 2740 [aStore setDelegate: [TaskManager singleInstance]]; 2741 2742 [self setStore: aStore name: aServerName username: aUsername]; 2743 2744 aTask = [[Task alloc] init]; 2745 aTask->op = CONNECT_ASYNC; 2746 [aTask setKey: theAccountName]; 2747 aTask->immediate = YES; 2748 aTask->service = aStore; 2749 [[TaskManager singleInstance] addTask: aTask]; 2750 RELEASE(aTask); 2751 2752 [aStore connectInBackgroundAndNotify]; 2753 2754 if ([[NSUserDefaults standardUserDefaults] integerForKey: @"PreferredViewStyle" default: GNUMailDrawerView] == GNUMailFloatingView && 2755 [[self window] isVisible]) 2756 { 2757 [[self window] makeKeyAndOrderFront: self]; 2758 } 2759 2760 return YES; 2761} 2762 2763 2764// 2765// 2766// 2767- (void) _nbOfMessages: (NSUInteger *) theNbOfMessages 2768 nbOfUnreadMessages: (NSUInteger *) theNbOfUnreadMessages 2769 forItem: (id) theItem 2770{ 2771 NSString *aString, *aStoreName, *aFolderName, *aUsername; 2772 2773 aString = [Utilities completePathForFolderNode: theItem 2774 separator: '/']; 2775 2776 if ([aString hasPrefix: [NSString stringWithFormat: @"/%@", _(@"Local")]]) 2777 { 2778 aStoreName = @"GNUMAIL_LOCAL_STORE"; 2779 aFolderName = [Utilities pathOfFolderFromFolderNode: (FolderNode *)theItem 2780 separator: '/']; 2781 aUsername = NSUserName(); 2782 } 2783 else 2784 { 2785 [Utilities storeKeyForFolderNode: theItem 2786 serverName: &aStoreName 2787 username: &aUsername]; 2788 2789 aFolderName = [Utilities pathOfFolderFromFolderNode: (FolderNode *)theItem 2790 separator: '/']; 2791 } 2792 2793 [_cache allValuesForStoreName: aStoreName 2794 folderName: aFolderName 2795 username: aUsername 2796 nbOfMessages: theNbOfMessages 2797 nbOfUnreadMessages: theNbOfUnreadMessages]; 2798} 2799 2800// 2801// Called when the user presses Tab in the mailbox window 2802// 2803- (void) _switchWindows:(id)sender 2804{ 2805 [[GNUMail lastMailWindowOnTop] makeKeyAndOrderFront:self]; 2806} 2807 2808// 2809// 2810// 2811- (void) _openLocalFolderWithName: (NSString *) theFolderName 2812 sender: (id) theSender 2813{ 2814 MailWindowController *aMailWindowController; 2815 CWLocalStore *localStore; 2816 CWLocalFolder *aFolder; 2817 2818 BOOL reusingLastMailWindowOnTop, aMask; 2819 2820 // We get out local store and our folder. 2821 localStore = [self storeForName: @"GNUMAIL_LOCAL_STORE" username: NSUserName()]; 2822 aFolder = nil; 2823 2824 // We first verify if the folder is still valid. For example, it could have been 2825 // deleted (the file) manually while GNUMail was running. 2826 if (![[NSFileManager defaultManager] fileExistsAtPath: [[localStore path] stringByAppendingPathComponent: theFolderName]]) 2827 { 2828 NSRunInformationalAlertPanel(_(@"Mailbox error!"), 2829 _(@"The local mailbox %@ does not exist!"), 2830 _(@"OK"), 2831 NULL, 2832 NULL, 2833 theFolderName); 2834 return; 2835 } 2836 2837 // We now verify if it's not a directory (a folder holding folders) 2838 if (([localStore folderTypeForFolderName: theFolderName] & PantomimeHoldsFolders) == PantomimeHoldsFolders) 2839 { 2840#warning remove that code or fix it 2841#if 0 2842 FolderNode *item; 2843 NSInteger i; 2844 2845 item = [outlineView itemAtRow: [outlineView selectedRow]]; 2846 2847 aFolder = AUTORELEASE([[CWVirtualFolder alloc] initWithName: theFolderName]); 2848 2849 // We add all direct sub-mailboxes 2850 for (i = 0; i < [item childCount]; i++) 2851 { 2852 id o; 2853 2854 o = [localStore folderForName: [Utilities pathOfFolderFromFolderNode: [item childAtIndex: i] separator: '/']]; 2855 [o parse: NO]; 2856 [aFolder addFolder: o]; 2857 } 2858#else 2859 NSBeep(); 2860 return; 2861#endif 2862 } 2863 2864 // If the folder is already open, we "focus" that window 2865 if ([localStore folderForNameIsOpen: theFolderName]) 2866 { 2867 NSWindow *aWindow; 2868 2869 aWindow = (NSWindow *)[Utilities windowForFolderName: theFolderName 2870 store: (id<CWStore>)localStore]; 2871 if (aWindow) 2872 { 2873 [aWindow orderFrontRegardless]; 2874 return; 2875 } 2876 } 2877 2878 // We must open (or get an open folder) the folder. 2879 if (!aFolder) 2880 { 2881 aFolder = [localStore folderForName: theFolderName]; 2882 } 2883 2884 if (!aFolder) 2885 { 2886 NSRunAlertPanel(_(@"Error!"), 2887 _(@"Unable to open local mailbox %@."), 2888 _(@"OK"), 2889 NULL, 2890 NULL, 2891 theFolderName); 2892 return; 2893 } 2894 2895#ifdef MACOSX 2896 aMask = ([[NSApp currentEvent] modifierFlags] & NSAlternateKeyMask) == NSAlternateKeyMask; 2897#else 2898 aMask = ([[NSApp currentEvent] modifierFlags] & NSControlKeyMask) == NSControlKeyMask; 2899#endif 2900 2901 // If we reuse our window controller... 2902 if ([theSender isKindOfClass: [NSMenuItem class]] || 2903 [GNUMail lastMailWindowOnTop] == nil || 2904 theSender == [NSApp delegate] || 2905 aMask) 2906 { 2907 aMailWindowController = [[MailWindowController alloc] initWithWindowNibName: @"MailWindow"]; 2908 reusingLastMailWindowOnTop = NO; 2909 } 2910 else 2911 { 2912 aMailWindowController = [[GNUMail lastMailWindowOnTop] windowController]; 2913 reusingLastMailWindowOnTop = YES; 2914 2915 // We must NOT assume that we got a MailWindowController 2916 if ([aMailWindowController isKindOfClass: [MessageViewWindowController class]]) 2917 { 2918 aMailWindowController = [(MessageViewWindowController *)aMailWindowController mailWindowController]; 2919 } 2920 2921 // We close the previous folder. 2922 [[aMailWindowController folder] close]; 2923 } 2924 2925 // We set the new folder 2926 [aMailWindowController setFolder: aFolder]; 2927 2928 // We we are reusing our window controller, we must always reload the table view 2929 if (reusingLastMailWindowOnTop && [GNUMail lastMailWindowOnTop]) 2930 { 2931 [aMailWindowController tableViewShouldReloadData]; 2932 } 2933 2934 // And we show the window.. 2935 [[aMailWindowController window] orderFrontRegardless]; 2936 [[aMailWindowController window] makeKeyAndOrderFront: nil]; 2937 2938 ADD_CONSOLE_MESSAGE(_(@"Local folder %@ opened."), theFolderName); 2939 2940 // We must restore the image here... it's important if we switch from 2941 // an IMAP mailbox (over SSL) to a local mailbox (to hide the secure icon) 2942 [[ConsoleWindowController singleInstance] restoreImage]; 2943 2944 // If the "Local" node was collapsed in our MailboxManager, we now expend it 2945 if (![outlineView isItemExpanded: [self storeFolderNodeForName: _(@"Local")]]) 2946 { 2947 [outlineView expandItem: [self storeFolderNodeForName: _(@"Local")]]; 2948 } 2949} 2950 2951 2952// 2953// 2954// 2955- (void) _openIMAPFolderWithName: (NSString *) theFolderName 2956 store: (CWIMAPStore *) theStore 2957 sender: (id) theSender 2958{ 2959 MailWindowController *aMailWindowController; 2960 CWIMAPCacheManager *anIMAPCacheManager; 2961 CWIMAPFolder *aFolder; 2962 NSString *aKey; 2963 Task *aTask; 2964 2965 BOOL reusingLastMailWindowOnTop, aMask; 2966 2967#ifdef MACOSX 2968 aMask = ([[NSApp currentEvent] modifierFlags] & NSAlternateKeyMask) == NSAlternateKeyMask; 2969#else 2970 aMask = ([[NSApp currentEvent] modifierFlags] & NSControlKeyMask) == NSControlKeyMask; 2971#endif 2972 2973 // Using IMAP, we currently only allow the user to have one folder open 2974 // at the time on the same CWIMAPStore. 2975 if ([[[theStore openFoldersEnumerator] allObjects] count] > 0) 2976 { 2977 id aWindow; 2978 2979 // We search for one open window (so folder) on the IMAP store 2980 aWindow = [Utilities windowForFolderName: nil store: (id<CWStore>)theStore]; 2981 2982 // If the folder that the window is 'using' is the same as the one we are trying to open, 2983 // we simply make this window the key one and order it front. There's no need to try 2984 // to reopen that folder! 2985 if ( [[[[aWindow windowController] folder] name] isEqualToString: theFolderName] ) 2986 { 2987 [aWindow makeKeyAndOrderFront: self]; 2988 return; 2989 } 2990 2991 // If we are trying to open a new MailWindow using the menu item or if we are reusing 2992 // a MailWindow but the current on top isn't a window 'using' our IMAP store... 2993 if ([theSender isKindOfClass: [NSMenuItem class]] || 2994 aMask || 2995 ([[GNUMail allMailWindows] count] > 1 && [GNUMail lastMailWindowOnTop] != aWindow)) 2996 { 2997 NSRunInformationalAlertPanel(_(@"Mailbox error!"), 2998 _(@"A mailbox (%@) is already open. Please close it first."), 2999 _(@"OK"), 3000 NULL, 3001 NULL, 3002 [(CWIMAPFolder *)[[theStore openFoldersEnumerator] nextObject] name]); 3003 return; 3004 } 3005 } 3006 3007 3008 // 3009 // We verify if we must reuse or not our window controller. The first if () is to 3010 // verify if we must NOT reuse it. 3011 // 3012 if ([theSender isKindOfClass: [NSMenuItem class]] || 3013 [GNUMail lastMailWindowOnTop] == nil || 3014 theSender == [NSApp delegate] || 3015 aMask) 3016 { 3017 aMailWindowController = [[MailWindowController alloc] initWithWindowNibName: @"MailWindow"]; 3018 reusingLastMailWindowOnTop = NO; 3019 } 3020 else 3021 { 3022 aMailWindowController = [[GNUMail lastMailWindowOnTop] windowController]; 3023 reusingLastMailWindowOnTop = YES; 3024 3025 // We must NOT assume that we got a MailWindowController 3026 if ([aMailWindowController isKindOfClass: [MessageViewWindowController class]]) 3027 { 3028 aMailWindowController = [(MessageViewWindowController *)aMailWindowController mailWindowController]; 3029 } 3030 3031 // We close the previous folder. No need to handle the IMAP timeout 3032 // as it's handled in IMAPFolder: -close. 3033 [[aMailWindowController folder] close]; 3034 } 3035 3036 // We send our message to the console saying we are about to open the IMAP folder 3037 ADD_CONSOLE_MESSAGE(_(@"Opening IMAP folder %@ on %@..."), theFolderName, [theStore name]); 3038 3039 3040 // 3041 // We obtain our folder from the IMAP store. 3042 // 3043 aFolder = (CWIMAPFolder *)[theStore folderForName: theFolderName 3044 mode: PantomimeReadWriteMode 3045 prefetch: NO]; 3046 3047 // We verify if the folder can be open. It could have been a \NoSelect folder. 3048 if (!aFolder) 3049 { 3050 NSRunInformationalAlertPanel(_(@"Mailbox error!"), 3051 _(@"You must select a valid mailbox to open!"), 3052 _(@"OK"), 3053 NULL, 3054 NULL, 3055 NULL); 3056 return; 3057 } 3058 3059 // 3060 // We verify if we haven't got a non-selectable folder. 3061 // 3062#warning what happens it we are DnD to this folder? 3063#if 0 3064 if (![aFolder selected]) 3065 { 3066 [aFolder close]; 3067 3068 aFolder = (CWIMAPFolder *)[theStore folderForName: theFolderName 3069 mode: PantomimeReadWriteMode 3070 prefetch: NO]; 3071 } 3072#endif 3073 3074 // We get our cache manager for this server / folder 3075 aKey = [NSString stringWithFormat: @"%@ @ %@", [theStore username], [theStore name]]; 3076 anIMAPCacheManager = [[CWIMAPCacheManager alloc] initWithPath: [NSString stringWithFormat: @"%@/IMAPCache_%@_%@", 3077 GNUMailUserLibraryPath(), 3078 [Utilities flattenPathFromString: aKey 3079 separator: '/'], 3080 [Utilities flattenPathFromString: theFolderName 3081 separator: [theStore folderSeparator]]] 3082 folder: aFolder]; 3083 AUTORELEASE(anIMAPCacheManager); 3084 3085 // We set the cache manager and we prefetch our messages 3086 [aFolder setCacheManager: anIMAPCacheManager]; 3087 3088 [[aFolder cacheManager] readAllMessages]; 3089 3090 // We set the folder 3091 [aMailWindowController setFolder: aFolder]; 3092 3093 aTask = [[Task alloc] init]; 3094 aTask->op = OPEN_ASYNC; 3095 [aTask setKey: [Utilities accountNameForFolder: aFolder]]; 3096 aTask->immediate = YES; 3097 aTask->service = [aFolder store]; 3098 [[TaskManager singleInstance] addTask: aTask]; 3099 RELEASE(aTask); 3100 3101#if 1 3102#warning do not clear the view but handle when the user click on a message of a closed folder 3103 // We are reusing our window controller, we must always reload the table view 3104 if ( reusingLastMailWindowOnTop && [GNUMail lastMailWindowOnTop] ) 3105 { 3106 [aMailWindowController tableViewShouldReloadData]; 3107 } 3108#endif 3109 3110 [[aMailWindowController window] orderFrontRegardless]; 3111 [[aMailWindowController window] makeKeyAndOrderFront: nil]; 3112 3113 ADD_CONSOLE_MESSAGE(_(@"IMAP folder %@ on %@ opened."), theFolderName, [theStore name]); 3114 3115 // If the "IMAP" node was collapsed in our MailboxManager, we now expand it 3116 if (![outlineView isItemExpanded: [self storeFolderNodeForName: 3117 [Utilities accountNameForServerName: [theStore name] 3118 username: [theStore username]]]]) 3119 { 3120 [outlineView expandItem: [self storeFolderNodeForName: 3121 [Utilities accountNameForServerName: [theStore name] 3122 username: [theStore username]]] ]; 3123 } 3124} 3125 3126 3127// 3128// 3129// 3130- (void) _reloadFoldersAndExpandParentsFromNode: (FolderNode *) theNode 3131 selectNodeWithPath: (NSString *) thePath 3132{ 3133 NSString *aServerName, *aUsername; 3134 NSMutableArray *nodesToExpand; 3135 3136 id aParent, aRootNode; 3137 NSInteger i, aRow; 3138 3139 [Utilities storeKeyForFolderNode: theNode 3140 serverName: &aServerName 3141 username: &aUsername]; 3142 3143 // We must refresh our outline view by reload its content 3144 [self reloadAllFolders]; 3145 3146 // We first get our root node 3147 if ([thePath hasPrefix: [NSString stringWithFormat: @"/%@", _(@"Local")]]) 3148 { 3149 aRootNode = localNodes; 3150 } 3151 else 3152 { 3153 aRootNode = [self storeFolderNodeForName: [Utilities accountNameForServerName: aServerName username: aUsername]]; 3154 } 3155 3156 3157 // We get our new node in our tree and also our new row index 3158 aParent = [Utilities folderNodeForPath: [thePath stringByDeletingLastPathComponent] 3159 using: aRootNode 3160 separator: '/']; 3161 3162 nodesToExpand = [[NSMutableArray alloc] init]; 3163 3164 // We expand all our parent, to make the row visible. 3165 while (aParent) 3166 { 3167 [nodesToExpand addObject: aParent]; 3168 aParent = [aParent parent]; 3169 } 3170 3171 // We must expand our nodes starting from the root to the children and not 3172 // the other way around. Otherwise, the NSOutlineView just borks. 3173 for (i = ([nodesToExpand count] - 1); i >= 0; i--) 3174 { 3175 [outlineView expandItem: [nodesToExpand objectAtIndex: i]]; 3176 } 3177 3178 RELEASE(nodesToExpand); 3179 3180 // We now get our new node node (renamed or created). Since it's now shown on the screen, 3181 // we can now obtain the row for it and select it. 3182 aParent = [Utilities folderNodeForPath: thePath 3183 using: aRootNode 3184 separator: '/']; 3185 3186 aRow = [outlineView rowForItem: aParent]; 3187 3188 3189 if (aRow >= 0 && aRow < [outlineView numberOfRows]) 3190 { 3191 [outlineView selectRow: aRow byExtendingSelection: NO]; 3192 [outlineView scrollRowToVisible: aRow]; 3193 } 3194} 3195 3196 3197// 3198// 3199// 3200- (NSString *) _stringValueOfURLNameFromItem: (id) theItem 3201 store: (CWStore **) theStore 3202{ 3203 NSMutableString *aMutableString; 3204 NSString *aString; 3205 3206 aMutableString = [[NSMutableString alloc] init]; 3207 3208 // We verify if it's a local folder 3209 if ([[Utilities completePathForFolderNode: theItem separator: '/'] 3210 hasPrefix: [NSString stringWithFormat: @"/%@", _(@"Local")]]) 3211 { 3212 [aMutableString appendFormat: @"local://%@", [[NSUserDefaults standardUserDefaults] 3213 objectForKey: @"LOCALMAILDIR"]]; 3214 *theStore = [self storeForName: @"GNUMAIL_LOCAL_STORE" 3215 username: NSUserName()]; 3216 } 3217 else 3218 { 3219 NSString *aServerName, *aUsername; 3220 3221 [Utilities storeKeyForFolderNode: theItem 3222 serverName: &aServerName 3223 username: &aUsername]; 3224 *theStore = [self storeForName: aServerName 3225 username: aUsername]; 3226 3227 [aMutableString appendFormat: @"imap://%@@%@", aUsername, aServerName]; 3228 } 3229 3230 // We get our folder name, respecting the folder separator 3231 aString = [Utilities pathOfFolderFromFolderNode: (FolderNode *)theItem 3232 separator: [(id<CWStore>)*theStore folderSeparator]]; 3233 3234 [aMutableString appendFormat: @"/%@", aString]; 3235 3236 return AUTORELEASE(aMutableString); 3237} 3238 3239 3240// 3241// 3242// 3243- (void) _updateMailboxesFromOldPath: (NSString *) theOldPath 3244 toPath: (NSString *) thePath 3245 3246{ 3247 NSMutableDictionary *allAccounts, *theAccount, *allValues; 3248 NSEnumerator *theEnumerator; 3249 NSString *aKey; 3250 3251 allAccounts = [[NSMutableDictionary alloc] initWithDictionary: [[NSUserDefaults standardUserDefaults] 3252 dictionaryForKey: @"ACCOUNTS"]]; 3253 3254 theEnumerator = [allAccounts keyEnumerator]; 3255 3256 while ((aKey = [theEnumerator nextObject])) 3257 { 3258 theAccount = [[NSMutableDictionary alloc] initWithDictionary: [allAccounts objectForKey: aKey]]; 3259 allValues = [[NSMutableDictionary alloc] initWithDictionary: [theAccount objectForKey: @"MAILBOXES"]]; 3260 3261 UPDATE_PATH(@"DRAFTSFOLDERNAME", theOldPath, thePath); 3262 UPDATE_PATH(@"INBOXFOLDERNAME", theOldPath, thePath); 3263 UPDATE_PATH(@"SENTFOLDERNAME", theOldPath, thePath); 3264 UPDATE_PATH(@"TRASHFOLDERNAME", theOldPath, thePath); 3265 3266 [theAccount setObject: allValues forKey: @"MAILBOXES"]; 3267 RELEASE(allValues); 3268 3269 [allAccounts setObject: theAccount forKey: aKey]; 3270 RELEASE(theAccount); 3271 } 3272 3273 [[NSUserDefaults standardUserDefaults] setObject: allAccounts forKey: @"ACCOUNTS"]; 3274 RELEASE(allAccounts); 3275} 3276 3277 3278// 3279// 3280// 3281- (void) _updateContextMenu 3282{ 3283 NSMenu *aMenu, *aSubmenu; 3284 NSMenuItem *aMenuItem; 3285 NSArray *allKeys; 3286 NSInteger i; 3287 3288 allKeys = [[Utilities allEnabledAccounts] allKeys]; 3289 aMenu = [[[menu itemArray] lastObject] submenu]; 3290 3291 for (i = 0; i < 3; i++) 3292 { 3293 NSUInteger j; 3294 aMenuItem = (NSMenuItem *)[aMenu itemAtIndex: i]; 3295 3296 aSubmenu = [[NSMenu alloc] init]; 3297 [aSubmenu setAutoenablesItems: NO]; 3298 3299 for (j = 0; j < [allKeys count]; j++) 3300 { 3301 [aSubmenu addItemWithTitle: [allKeys objectAtIndex: j] action: @selector(setMailboxAs:) keyEquivalent: @""]; 3302 [[[aSubmenu itemArray] lastObject] setTarget: self]; 3303 [[[aSubmenu itemArray] lastObject] setTag: i]; 3304 } 3305 3306 [aMenuItem setSubmenu: aSubmenu]; 3307 RELEASE(aSubmenu); 3308 } 3309} 3310 3311@end 3312 3313 3314 3315 3316 3317 3318 3319 3320