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