5 // Created by Evan Schoenberg on 12/4/05.
8 #import "AdiumSetupWizard.h"
9 #import <Adium/AIAccountControllerProtocol.h>
10 #import <Adium/AIContentControllerProtocol.h>
11 #import "SetupWizardBackgroundView.h"
12 #import "GBFireImporter.h"
13 #import <AIUtilities/AIImageAdditions.h>
14 #import "AIServiceMenu.h"
15 #import <Adium/AIService.h>
16 #import <Adium/AIAccount.h>
17 #import <AIUtilities/AITextFieldAdditions.h>
18 #import <AIUtilities/AIStringFormatter.h>
19 #import <AIUtilities/AIFileManagerAdditions.h>
20 #import "AIHTMLDecoder.h"
22 #define ACCOUNT_SETUP_IDENTIFIER @"account_setup"
23 #define IMPORT_IDENTIFIER @"import_client"
24 #define WELCOME_IDENTIFIER @"welcome"
25 #define DONE_IDENTIFIER @"done"
28 WIZARD_TAB_WELCOME = 0,
29 WIZARD_TAB_FIREIMPORT = 1,
30 WIZARD_TAB_ADD_ACCOUNTS = 2,
35 * @classs AdiumSetupWizard
36 * @brief Class responsible for the first-run setup wizard
38 @implementation AdiumSetupWizard
41 * @brief Run the wizard
45 AdiumSetupWizard *setupWizardWindowController;
47 setupWizardWindowController = [[self alloc] initWithWindowNibName:@"SetupWizard"];
49 //Configure and show window
50 [setupWizardWindowController showWindow:nil];
51 [[setupWizardWindowController window] orderFront:nil];
55 * @brief Localized some common items' titles
59 [button_goBack setLocalizedString:AILocalizedString(@"Go Back","'go back' button title")];
60 [textField_passwordLabel setLocalizedString:AILocalizedString(@"Password:", "Label for the password field in the account preferences")];
61 [textField_serviceLabel setLocalizedString:AILocalizedString(@"Service:",nil)];
63 [button_alternate setLocalizedString:AILocalizedString(@"Skip Import","button title for skipping the import of another client in the setup wizard")];
67 * @brief The window loaded
71 [[self window] setTitle:AILocalizedString(@"Adium Setup Assistant",nil)];
73 //Ensure the first tab view item is selected
74 [tabView selectTabViewItemAtIndex:WIZARD_TAB_WELCOME];
75 [self tabView:tabView willSelectTabViewItem:[tabView selectedTabViewItem]];
77 //Configure our background view; it should display the image transparently where our tabView overlaps it
78 [backgroundView setBackgroundImage:[NSImage imageNamed:@"AdiumyButler"
79 forClass:[self class]]];
80 NSRect tabViewFrame = [tabView frame];
81 NSRect backgroundViewFrame = [backgroundView frame];
82 tabViewFrame.origin.x -= backgroundViewFrame.origin.x;
83 tabViewFrame.origin.y -= backgroundViewFrame.origin.y;
84 [backgroundView setTransparentRect:tabViewFrame];
86 //Check to see if we can import Fire's settings
87 NSFileManager *fileManager = [NSFileManager defaultManager];
88 NSString *fireDir = [[fileManager userApplicationSupportFolder] stringByAppendingPathComponent:@"Fire"];
90 if([fileManager fileExistsAtPath:fireDir isDirectory:&isDir] && isDir)
92 if([progress_processing respondsToSelector:@selector(setHidden:)])
93 [progress_processing setHidden:YES];
97 [[self window] center];
99 [super windowDidLoad];
102 - (IBAction)promptForMultiples:(id)sender
104 // Since we have multiple dedicated importers in 1.1+ it's better to direct the user as needed
105 NSAlert *multipleImportPrompt = [NSAlert alertWithMessageText:AILocalizedString(@"Have you used other chat clients?", "Title which introduces import assistants during setup")
106 defaultButton:AILocalizedStringFromTable(@"Continue", @"Buttons", nil)
107 alternateButton:AILocalizedString(@"Import from Fire", "Fire is another OS X instant messaging client; the name probably should not be localized")
108 otherButton:AILocalizedString(@"Import from iChat", "iChat is the OS X instant messaging client which ships with OS X; the name probably should not be localized")
109 informativeTextWithFormat:AILocalizedString(@"Adium includes assistants to import your accounts, settings, and transcripts from other clients. Choose a client below to open its assistant, or press Continue to skip importing.", nil)];
110 [multipleImportPrompt beginSheetModalForWindow:[self window]
112 didEndSelector:@selector(multipleImportAlertDidEnd:returnCode:contextInfo:)
116 - (void)multipleImportAlertDidEnd:(NSAlert *)alert returnCode:(int)returnCode contextInfo:(void *)contextInfo
118 if(returnCode == NSAlertOtherReturn)
120 [adium performSelector:@selector(importFromiChat:)
123 [[self window] close];
125 if(returnCode == NSAlertAlternateReturn)
127 // not yet supported, but should open the manual Fire importer
132 * @brief Perform behaviors before the window closes
134 * As our window is closing, we auto-release this window controller instance.
136 - (void)windowWillClose:(id)sender
138 [super windowWillClose:sender];
144 * @brief Start the progress indicator to let the user we are processing
146 - (void)activateProgressIndicator
148 [progress_processing setHidden:NO];
150 //start the progress spinner (using multi-threading)
151 [progress_processing setUsesThreadedAnimation:YES];
152 [progress_processing startAnimation:nil];
156 * @brief A tab view item was completed; post-process any entered data
158 - (BOOL)didCompleteTabViewItemWithIdentifier:(NSString *)identifier
162 if ([identifier isEqualToString:ACCOUNT_SETUP_IDENTIFIER]) {
163 NSString *UID = [textField_username stringValue];
165 if (UID && [UID length]) {
166 AIService *service = [[popUp_services selectedItem] representedObject];
167 AIAccount *account = [[adium accountController] createAccountWithService:service
171 NSString *password = [textField_password secureStringValue];
173 if (password && [password length] != 0) {
174 [[adium accountController] setPassword:password forAccount:account];
176 AILog(@"AdiumSetupWizard: Creating account %@ on service %@",account,service);
177 //New accounts need to be added to our account list once they're configured
178 [[adium accountController] addAccount:account];
180 //Put new accounts online by default
181 [account setShouldBeOnline:YES];
183 addedAnAccount = YES;
186 //Successful without having a UID entered if they already added at least one account; unsuccessful otherwise.
187 success = addedAnAccount;
189 } else if ([identifier isEqualToString:IMPORT_IDENTIFIER]) {
190 [self activateProgressIndicator];
191 success = [GBFireImporter importFireConfiguration];
192 addedAnAccount = [[[adium accountController] accounts] count] != 0;
193 [progress_processing stopAnimation:nil];
194 [progress_processing setHidden:YES];
201 * @brief The Continue button, which is also the Done button, was pressed
203 - (IBAction)nextTab:(id)sender
205 NSTabViewItem *currentTabViewItem = [tabView selectedTabViewItem];
206 if ([self didCompleteTabViewItemWithIdentifier:[currentTabViewItem identifier]]) {
207 if ([tabView indexOfTabViewItem:currentTabViewItem] == WIZARD_TAB_DONE) {
211 } else if ([[currentTabViewItem identifier] isEqualToString:WELCOME_IDENTIFIER]) {
213 //We can import; go to next tab
214 [tabView selectNextTabViewItem:self];
217 [tabView selectTabViewItemAtIndex:WIZARD_TAB_FIREIMPORT + 1];
220 //Go to the next tab view item
221 [tabView selectNextTabViewItem:self];
229 * @brief The Back button was pressed
231 - (IBAction)previousTab:(id)sender
233 NSTabViewItem *currentTabViewItem = [tabView selectedTabViewItem];
234 if([[currentTabViewItem identifier] isEqualToString:ACCOUNT_SETUP_IDENTIFIER] && !canImport)
235 [tabView selectTabViewItemAtIndex:WIZARD_TAB_FIREIMPORT - 1];
237 [tabView selectPreviousTabViewItem:self];
241 * @brief The alternate (third) button was pressed; its behavior will vary by tab view item
243 - (IBAction)pressedAlternateButton:(id)sender
245 NSTabViewItem *currentTabViewItem = [tabView selectedTabViewItem];
246 NSString *identifier = [currentTabViewItem identifier];
248 if ([identifier isEqualToString:ACCOUNT_SETUP_IDENTIFIER]) {
249 //Configure the account
250 if ([self didCompleteTabViewItemWithIdentifier:identifier]) {
252 [self tabView:tabView willSelectTabViewItem:currentTabViewItem];
256 } else if ([identifier isEqualToString:IMPORT_IDENTIFIER]) {
258 [tabView selectNextTabViewItem:self];
263 * @brief Set up the Account Setup tab for a given service
265 - (void)configureAccountSetupForService:(AIService *)service
268 [textField_usernameLabel setStringValue:[[service userNameLabel] stringByAppendingString:AILocalizedString(@":", "Colon which will be appended after a label such as 'User Name', before an input field")]];
270 //UID formatter and placeholder
271 [textField_username setFormatter:
272 [AIStringFormatter stringFormatterAllowingCharacters:[service allowedCharactersForAccountName]
273 length:[service allowedLengthForAccountName]
274 caseSensitive:[service caseSensitive]
275 errorMessage:AILocalizedString(@"The characters you're entering are not valid for an account name on this service.", nil)]];
276 [[textField_username cell] setPlaceholderString:[service UIDPlaceholder]];
278 BOOL showPasswordField = ![service supportsPassword];
279 [textField_passwordLabel setHidden:showPasswordField];
280 [textField_password setHidden:showPasswordField];
283 - (BOOL)showAlternateButtonForIdentifier:(NSString *)identifier
285 return [identifier isEqualToString:ACCOUNT_SETUP_IDENTIFIER] || [identifier isEqualToString:IMPORT_IDENTIFIER];
289 * @brief The tab view is about to select a tab view item
291 - (void)tabView:(NSTabView *)inTabView willSelectTabViewItem:(NSTabViewItem *)tabViewItem
293 NSString *identifier = [tabViewItem identifier];
295 //The continue button is only initially enabled if the user has added at least one account
296 [button_continue setEnabled:YES];
298 if ([identifier isEqualToString:ACCOUNT_SETUP_IDENTIFIER]) {
299 //Set the services menu if it hasn't already been set
300 if (!setupAccountTabViewItem) {
301 [popUp_services setMenu:[AIServiceMenu menuOfServicesWithTarget:self
302 activeServicesOnly:NO
306 [textField_addAccount setStringValue:AILocalizedString(@"Add an Instant Messaging Account",nil)];
307 [textView_addAccountMessage setDrawsBackground:NO];
308 [[textView_addAccountMessage enclosingScrollView] setDrawsBackground:NO];
310 NSAttributedString *accountMessage = [AIHTMLDecoder decodeHTML:
311 AILocalizedString(@"<HTML>To chat with your friends, family, and coworkers, you must have an instant messaging account on the same service they do. Choose a service, name, and password below; if you don't have an account yet, click <A HREF=\"http://trac.adiumx.com/wiki/CreatingAnAccount#Sigingupforanaccount\">here</A> for more information.\n\nAdium supports as many accounts as you want to add; you can always add more in the Accounts pane of the Adium Preferences.</HTML>", nil)
312 withDefaultAttributes:[[textView_addAccountMessage textStorage] attributesAtIndex:0
313 effectiveRange:NULL]];
314 [[textView_addAccountMessage textStorage] setAttributedString:accountMessage];
315 setupAccountTabViewItem = YES;
318 AIService *service = [[popUp_services selectedItem] representedObject];
319 [textField_username setStringValue:@""];
320 [[self window] makeFirstResponder:textField_username];
322 [textField_password setStringValue:@""];
324 //The continue button is only initially enabled if the user has added at least one account
325 [button_continue setEnabled:addedAnAccount];
326 [button_alternate setLocalizedString:AILocalizedString(@"Add Another","button title for adding another account in the setup wizard")];
327 [button_alternate setEnabled:NO];
329 [self configureAccountSetupForService:service];
331 } else if ([identifier isEqualToString:IMPORT_IDENTIFIER]) {
332 [textField_import setStringValue:AILocalizedString(@"Import Fire's Settings", nil)];
333 [textView_importMessage setDrawsBackground:NO];
334 [[textView_importMessage enclosingScrollView] setDrawsBackground:NO];
336 NSAttributedString *importMessage = [AIHTMLDecoder decodeHTML:
337 AILocalizedString(@"<HTML>Adium has detected that you have previously used Fire. You may choose to import settings from Fire by pressing continue below. If you choose to not use these settings, simply skip this step.<HTML>",nil)
338 withDefaultAttributes:[[textView_importMessage textStorage] attributesAtIndex:0
339 effectiveRange:NULL]];
340 [[textView_importMessage textStorage] setAttributedString:importMessage];
341 [button_alternate setLocalizedString:AILocalizedString(@"Skip Import","button title for skipping the import of another client in the setup wizard")];
342 [button_alternate setEnabled:YES];
344 } else if ([identifier isEqualToString:WELCOME_IDENTIFIER]) {
345 [textView_welcomeMessage setDrawsBackground:NO];
346 [[textView_welcomeMessage enclosingScrollView] setDrawsBackground:NO];
347 NSAttributedString *welcomeMessage = [AIHTMLDecoder decodeHTML:
348 AILocalizedString(@"<HTML>Adium is <i>your</i> instant messaging solution.<br><br>Chat with whomever you want, whenever you want, however you want. Multiple messaging services or accounts? Just one account? Work? Play? Both? No problem; Adium has you covered.<br><br>Adium is fast, free, and fun, with an interface you'll love to use day in and day out. :)<br><br>This assistant will help you set up your instant messaging accounts and get started chatting.<br><br>Click <b>Continue</b> and the duck will take it from here.</HTML>",nil)
349 withDefaultAttributes:[[textView_addAccountMessage textStorage] attributesAtIndex:0
350 effectiveRange:NULL]];
351 //Turn that smiley into an emoticon :)
352 welcomeMessage = [[adium contentController] filterAttributedString:welcomeMessage
353 usingFilterType:AIFilterDisplay
354 direction:AIFilterIncoming
356 [[textView_welcomeMessage textStorage] setAttributedString:welcomeMessage];
358 [textField_welcome setStringValue:AILocalizedString(@"Welcome to Adium!",nil)];
360 } else if ([identifier isEqualToString:DONE_IDENTIFIER]) {
361 [textView_doneMessage setDrawsBackground:NO];
362 [[textView_doneMessage enclosingScrollView] setDrawsBackground:NO];
363 [textView_doneMessage setString:AILocalizedString(@"Adium is now ready for you. \n\nThe Status indicator at the top of your Contact List and in the Status menu lets you determine whether others see you as Available or Away or, alternately, if you are Offline. Select Custom to type your own status message.\n\nDouble-click a name in your Contact List to begin a conversation. You can add contacts to your Contact List via the Contact menu.\n\nWant to customize your Adium experience? Check out the Adium Preferences and Xtras Manager via the Adium menu.\n\nEnjoy! Click Done to begin using Adium.", nil)],
365 [textField_done setStringValue:AILocalizedString(@"Congratulations!","Header line in the last pane of the Adium setup wizard")];
368 //Hide go back on the first tab
369 [button_goBack setEnabled:([tabView indexOfTabViewItem:tabViewItem] != WIZARD_TAB_WELCOME)];
371 [button_alternate setHidden:![self showAlternateButtonForIdentifier:identifier]];
373 //Set the done / continue button properly
374 if ([tabView indexOfTabViewItem:tabViewItem] == WIZARD_TAB_DONE) {
375 [button_continue setLocalizedString:AILocalizedString(@"Done","'done' button title")];
378 [button_continue setLocalizedString:AILocalizedString(@"Continue","'done' button title")];
383 * @brief The selected service in the account configuration tab view item was changed
385 - (void)selectServiceType:(id)sender
387 [self configureAccountSetupForService:[[popUp_services selectedItem] representedObject]];
390 - (void)controlTextDidChange:(NSNotification *)aNotification
392 if ([aNotification object] == textField_username) {
393 BOOL shouldEnable = ([[textField_username stringValue] length] > 0);
394 //Allow continuing if they have typed something or they already added an account
395 [button_continue setEnabled:(shouldEnable || addedAnAccount)];
397 //Allow adding another only if they have typed something
398 [button_alternate setEnabled:shouldEnable];