069e5599dc00d07dca9c0adaeec9f425e6d9a58d
[people/sha0/mDNSResponder.git] / Clients / PrinterSetupWizard / ThirdPage.cpp
1 /* -*- Mode: C; tab-width: 4 -*-
2  *
3  * Copyright (c) 1997-2004 Apple Computer, Inc. All rights reserved.
4  *
5  * Licensed under the Apache License, Version 2.0 (the "License");
6  * you may not use this file except in compliance with the License.
7  * You may obtain a copy of the License at
8  * 
9  *     http://www.apache.org/licenses/LICENSE-2.0
10  * 
11  * Unless required by applicable law or agreed to in writing, software
12  * distributed under the License is distributed on an "AS IS" BASIS,
13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  * See the License for the specific language governing permissions and
15  * limitations under the License.
16  */
17
18 #include "stdafx.h"
19 #include "PrinterSetupWizardApp.h"
20 #include "PrinterSetupWizardSheet.h"
21 #include "ThirdPage.h"
22 #include "StdioFileEx.h"
23 #include <dns_sd.h>
24 #include <tcpxcv.h>
25 #include <winspool.h>
26 #include <setupapi.h>
27
28 // local variable is initialize but not referenced
29 #pragma warning(disable:4189)
30
31 //
32 // This is the printer description file that is shipped
33 // with Windows XP and below
34 //
35 #define kNTPrintFile            L"inf\\ntprint.inf"
36
37 //
38 // Windows Vista ships with a set of prn*.inf files
39 //
40 #define kVistaPrintFiles        L"inf\\prn*.inf"
41
42 //
43 // These are pre-defined names for Generic manufacturer and model
44 //
45 #define kGenericManufacturer            L"Generic"
46 #define kGenericText                            L"Generic / Text Only"
47 #define kGenericPostscript                      L"Generic / Postscript"
48 #define kGenericPCL                                     L"Generic / PCL"
49 #define kPDLPostscriptKey                       L"application/postscript"
50 #define kPDLPCLKey                                      L"application/vnd.hp-pcl"
51 #define kGenericPSColorDriver           L"HP Color LaserJet 4550 PS"
52 #define kGenericPSDriver                        L"HP LaserJet 4050 Series PS"
53 #define kGenericPCLColorDriver          L"HP Color LaserJet 4550 PCL"
54 #define kGenericPCLDriver                       L"HP LaserJet 4050 Series PCL"
55
56
57 // CThirdPage dialog
58
59 IMPLEMENT_DYNAMIC(CThirdPage, CPropertyPage)
60 CThirdPage::CThirdPage()
61         : CPropertyPage(CThirdPage::IDD),
62                 m_manufacturerSelected( NULL ),
63                 m_modelSelected( NULL ),
64                 m_genericPostscript( NULL ),
65                 m_genericPCL( NULL ),
66                 m_initialized(false),
67                 m_printerImage( NULL )
68 {
69         static const int        bufferSize      = 32768;
70         TCHAR                           windowsDirectory[bufferSize];
71         CString                         header;
72         WIN32_FIND_DATA         findFileData;
73         HANDLE                          findHandle;
74         CString                         prnFiles;
75         CString                         ntPrint;
76         OSStatus                        err;
77         BOOL                            ok;
78
79         m_psp.dwFlags &= ~(PSP_HASHELP);
80         m_psp.dwFlags |= PSP_DEFAULT|PSP_USEHEADERTITLE|PSP_USEHEADERSUBTITLE;
81         
82         m_psp.pszHeaderTitle = MAKEINTRESOURCE(IDS_INSTALL_TITLE);
83         m_psp.pszHeaderSubTitle = MAKEINTRESOURCE(IDS_INSTALL_SUBTITLE);
84
85         //
86         // load printers from ntprint.inf
87         //
88         ok = GetWindowsDirectory( windowsDirectory, bufferSize );
89         err = translate_errno( ok, errno_compat(), kUnknownErr );
90         require_noerr( err, exit );
91  
92         //
93         // <rdar://problem/4826126>
94         //
95         // If there are no *prn.inf files, we'll assume that the information
96         // is in ntprint.inf
97         //
98         prnFiles.Format( L"%s\\%s", windowsDirectory, kVistaPrintFiles );
99         findHandle = FindFirstFile( prnFiles, &findFileData );
100  
101         if ( findHandle != INVALID_HANDLE_VALUE )
102         {
103                 CString absolute;
104
105                 absolute.Format( L"%s\\inf\\%s", windowsDirectory, findFileData.cFileName );
106                 err = LoadPrintDriverDefsFromFile( m_manufacturers, absolute, false );
107                 require_noerr( err, exit );
108
109                 while ( FindNextFile( findHandle, &findFileData ) )
110                 {
111                         absolute.Format( L"%s\\inf\\%s", windowsDirectory, findFileData.cFileName );
112                         err = LoadPrintDriverDefsFromFile( m_manufacturers, absolute, false );
113                         require_noerr( err, exit );
114                 }
115
116                 FindClose( findHandle );
117         }
118         else
119         {
120                 ntPrint.Format(L"%s\\%s", windowsDirectory, kNTPrintFile);
121                 err = LoadPrintDriverDefsFromFile( m_manufacturers, ntPrint, false );
122                 require_noerr(err, exit);
123         }
124
125         //
126         // load printer drivers that have been installed on this machine
127         //
128         err = LoadPrintDriverDefs( m_manufacturers );
129         require_noerr(err, exit);
130
131         //
132         // load our own special generic printer defs
133         //
134         err = LoadGenericPrintDriverDefs( m_manufacturers );
135         require_noerr( err, exit );
136
137 exit:
138
139         return;
140 }
141
142 CThirdPage::~CThirdPage()
143 {
144         //
145         // clean up all the printer manufacturers
146         //
147         while (m_manufacturers.size())
148         {
149                 Manufacturers::iterator iter = m_manufacturers.begin();
150
151                 while (iter->second->models.size())
152                 {
153                         Models::iterator it = iter->second->models.begin();
154
155                         Model * model = *it;
156
157                         delete model;
158
159                         iter->second->models.erase(it);
160                 }
161
162                 delete iter->second;
163
164                 m_manufacturers.erase(iter);
165         }
166 }
167
168 // ----------------------------------------------------
169 // SelectMatch
170 //
171 // SelectMatch will do all the UI work associated with
172 // selected a manufacturer and model of printer.  It also
173 // makes sure the printer object is update with the
174 // latest settings
175 //
176 // ----------------------------------------------------
177 void
178 CThirdPage::SelectMatch(Printer * printer, Service * service, Manufacturer * manufacturer, Model * model)
179 {
180         LVFINDINFO      info;
181         int                     nIndex;
182
183         check( printer != NULL );
184         check( manufacturer != NULL );
185         check( model != NULL );
186
187         //
188         // select the manufacturer
189         //
190         info.flags      = LVFI_STRING;
191         info.psz        = manufacturer->name;
192
193         nIndex = m_manufacturerListCtrl.FindItem(&info);
194         
195         if (nIndex != -1)
196         {
197                 m_manufacturerListCtrl.SetItemState(nIndex, LVIS_SELECTED, LVIS_SELECTED);
198                 //
199                 //<rdar://problem/4528853> mDNS: When auto-highlighting items in lists, scroll list so highlighted item is in the middle
200                 //
201                 AutoScroll(m_manufacturerListCtrl, nIndex);
202         }
203
204         //
205         // select the model
206         //
207         info.flags      = LVFI_STRING;
208         info.psz        = model->displayName;
209
210         nIndex = m_modelListCtrl.FindItem(&info);
211
212         if (nIndex != -1)
213         {
214                 m_modelListCtrl.SetItemState(nIndex, LVIS_SELECTED, LVIS_SELECTED);
215                 AutoScroll( m_modelListCtrl, nIndex );
216
217                 m_modelListCtrl.SetFocus();
218         }
219
220         CopyPrinterSettings( printer, service, manufacturer, model );
221 }
222
223 void
224 CThirdPage::SelectMatch(Manufacturers & manufacturers, Printer * printer, Service * service, Manufacturer * manufacturer, Model * model)
225 {
226         PopulateUI( manufacturers );
227
228         SelectMatch( printer, service, manufacturer, model );
229 }
230
231 // --------------------------------------------------------
232 // CopyPrinterSettings
233 //
234 // This function makes sure that the printer object has the
235 // latest settings from the manufacturer and model objects
236 // --------------------------------------------------------
237
238 void
239 CThirdPage::CopyPrinterSettings( Printer * printer, Service * service, Manufacturer * manufacturer, Model * model )
240 {
241         DWORD portNameLen;
242
243         printer->manufacturer           =       manufacturer->name;
244         printer->displayModelName       =       model->displayName;
245         printer->modelName                      =       model->name;
246         printer->driverInstalled        =       model->driverInstalled;
247         printer->infFileName            =       model->infFileName;
248
249         if ( service->type == kPDLServiceType )
250         {
251                 printer->portName.Format(L"IP_%s.%d", static_cast<LPCTSTR>(service->hostname), service->portNumber);
252                 service->protocol = L"Raw";
253         }
254         else if ( service->type == kLPRServiceType )
255         {
256                 Queue * q = service->queues.front();
257                 check( q );
258
259                 if ( q->name.GetLength() > 0 )
260                 {
261                         printer->portName.Format(L"LPR_%s.%d.%s", static_cast<LPCTSTR>(service->hostname), service->portNumber, static_cast<LPCTSTR>(q->name) );
262                 }
263                 else
264                 {
265                         printer->portName.Format(L"LPR_%s.%d", static_cast<LPCTSTR>(service->hostname), service->portNumber);
266                 }
267
268                 service->protocol = L"LPR";
269         }
270         else if ( service->type == kIPPServiceType )
271         {
272                 Queue * q = service->queues.front();
273                 check( q );
274
275                 if ( q->name.GetLength() > 0 )
276                 {
277                         printer->portName.Format(L"http://%s:%d/%s", static_cast<LPCTSTR>(service->hostname), service->portNumber, static_cast<LPCTSTR>(q->name) );
278                 }
279                 else
280                 {
281                         printer->portName.Format(L"http://%s:%d/", static_cast<LPCTSTR>(service->hostname), service->portNumber );
282                 }
283
284                 service->protocol = L"IPP";
285         }
286
287         // If it's not an IPP printr, truncate the portName so that it's valid
288
289         if ( service->type != kIPPServiceType )
290         {
291                 portNameLen = printer->portName.GetLength() + 1;
292                 
293                 if ( portNameLen > MAX_PORTNAME_LEN )
294                 {
295                         printer->portName.Delete( MAX_PORTNAME_LEN - 1, ( portNameLen - MAX_PORTNAME_LEN ) );
296                 }
297         }
298 }
299
300 // --------------------------------------------------------
301 // DefaultPrinterExists
302 //
303 // Checks to see if a default printer has been configured
304 // on this machine
305 // --------------------------------------------------------
306 BOOL
307 CThirdPage::DefaultPrinterExists()
308 {
309         CPrintDialog dlg(FALSE);
310         
311         dlg.m_pd.Flags |= PD_RETURNDEFAULT;
312
313         return dlg.GetDefaults();
314 }
315
316 // --------------------------------------------------------
317 // AutoScroll
318 //
319 // Ensure selected item is in middle of list
320 // --------------------------------------------------------
321 void
322 CThirdPage::AutoScroll( CListCtrl & list, int nIndex )
323 {
324         //
325         //<rdar://problem/4528853> mDNS: When auto-highlighting items in lists, scroll list so highlighted item is in the middle
326         //
327
328         int             top;
329         int             count;
330
331         list.EnsureVisible( nIndex, FALSE );
332         
333         top             = list.GetTopIndex();
334         count   = list.GetCountPerPage();
335
336         if ( ( nIndex == top ) || ( ( nIndex + 1 ) == ( top + count ) ) )
337         {
338                 CRect   rect;
339                 int             rows;
340                 
341                 rows = ( count / 2 );
342
343                 if ( nIndex == top )
344                 {
345                         list.GetItemRect(0, rect, LVIR_BOUNDS);
346                         list.Scroll( CPoint( 0, rows * rect.Height() * -1 ) );
347                 }
348                 else
349                 {
350                         list.GetItemRect(0, rect, LVIR_BOUNDS);
351                         list.Scroll( CPoint( 0, rows * rect.Height() ) );
352                 }
353         }
354 }
355
356 // ------------------------------------------------------
357 // LoadPrintDriverDefsFromFile
358 //
359 // The only potentially opaque thing about this function is the
360 // checkForDuplicateModels flag.  The problem here is that ntprint.inf
361 // doesn't contain duplicate models, and it has hundreds of models
362 // listed.  You wouldn't check for duplicates there.  But oftentimes,
363 // loading different windows print driver files contain multiple
364 // entries for the same printer.  You don't want the UI to display
365 // the same printer multiple times, so in that case, you would ask
366 // this function to check for multiple models.
367
368 OSStatus
369 CThirdPage::LoadPrintDriverDefsFromFile(Manufacturers & manufacturers, const CString & filename, bool checkForDuplicateModels )
370 {
371         HINF                    handle  = INVALID_HANDLE_VALUE;
372         const TCHAR *   section = TEXT( "Manufacturer" );
373         LONG                    sectionCount;
374         TCHAR                   line[ 1000 ];
375         CString                 klass;
376         INFCONTEXT              manufacturerContext;
377         BOOL                    ok;
378         OSStatus                err             = 0;
379         
380         // Make sure we can open the file
381         handle = SetupOpenInfFile( filename, NULL, INF_STYLE_WIN4, NULL );
382         translate_errno( handle != INVALID_HANDLE_VALUE, GetLastError(), kUnknownErr );
383         require_noerr( err, exit );
384
385         // Make sure it's a printer file
386         ok = SetupGetLineText( NULL, handle, TEXT( "Version" ), TEXT( "Class" ), line, sizeof( line ), NULL );
387         translate_errno( ok, GetLastError(), kUnknownErr );
388         require_noerr( err, exit );
389         klass = line;
390         require_action( klass == TEXT( "Printer" ), exit, err = kUnknownErr );
391
392         sectionCount = SetupGetLineCount( handle, section );
393         translate_errno( sectionCount != -1, GetLastError(), kUnknownErr );
394         require_noerr( err, exit );
395
396         memset( &manufacturerContext, 0, sizeof( manufacturerContext ) );
397                         
398         for ( LONG i = 0; i < sectionCount; i++ )
399         {
400                 Manufacturers::iterator iter;
401                 Manufacturer    *       manufacturer;
402                 CString                         manufacturerName;
403                 CString                         temp;
404                 CStringList                     modelSectionNameDecl;
405                 CString                         modelSectionName;
406                 CString                         baseModelName;
407                 CString                         model;
408                 INFCONTEXT                      modelContext;
409                 LONG                            modelCount;
410                 POSITION                        p;
411
412                 if ( i == 0 )
413                 {
414                         ok = SetupFindFirstLine( handle, section, NULL, &manufacturerContext );
415                         err = translate_errno( ok, GetLastError(), kUnknownErr );
416                         require_noerr( err, exit );
417                 }
418                 else
419                 {
420                         ok = SetupFindNextLine( &manufacturerContext, &manufacturerContext );
421                         err = translate_errno( ok, GetLastError(), kUnknownErr );
422                         require_noerr( err, exit );
423                 }
424
425                 ok = SetupGetStringField( &manufacturerContext, 0, line, sizeof( line ), NULL );
426                 err = translate_errno( ok, GetLastError(), kUnknownErr );
427                 require_noerr( err, exit );
428                 manufacturerName = line;
429
430                 ok = SetupGetLineText( &manufacturerContext, handle, NULL, NULL, line, sizeof( line ), NULL );
431                 err = translate_errno( ok, GetLastError(), kUnknownErr );
432                 require_noerr( err, exit );
433
434                 // Try to find some model section name that has entries. Explanation of int file structure
435                 // can be found at:
436                 //
437                 // <http://msdn.microsoft.com/en-us/library/ms794359.aspx>
438                 Split( line, ',', modelSectionNameDecl );
439
440                 p                                       = modelSectionNameDecl.GetHeadPosition();
441                 modelSectionName        = modelSectionNameDecl.GetNext( p );
442                 modelCount                      = SetupGetLineCount( handle, modelSectionName );
443                 baseModelName           = modelSectionName;
444                 
445                 while ( modelCount <= 0 && p )
446                 {
447                         CString targetOSVersion;
448
449                         targetOSVersion         = modelSectionNameDecl.GetNext( p );
450                         modelSectionName        = baseModelName + TEXT( "." ) + targetOSVersion;
451                         modelCount                      = SetupGetLineCount( handle, modelSectionName );
452                 }
453
454                 if ( modelCount > 0 )
455                 {
456                         manufacturerName = NormalizeManufacturerName( manufacturerName );
457
458                         iter = manufacturers.find( manufacturerName );
459
460                         if ( iter != manufacturers.end() )
461                         {
462                                 manufacturer = iter->second;
463                                 require_action( manufacturer, exit, err = kUnknownErr );
464                         }
465                         else
466                         {
467                                 try
468                                 {
469                                         manufacturer = new Manufacturer;
470                                 }
471                                 catch (...)
472                                 {
473                                         manufacturer = NULL;
474                                 }
475
476                                 require_action( manufacturer, exit, err = kNoMemoryErr );
477
478                                 manufacturer->name                                      = manufacturerName;
479                                 manufacturers[ manufacturerName ]       = manufacturer;
480                         }
481
482                         memset( &modelContext, 0, sizeof( modelContext ) );
483
484                         for ( LONG j = 0; j < modelCount; j++ )
485                         {
486                                 CString modelName;
487                                 Model * model;
488
489                                 if ( j == 0 )
490                                 {
491                                         ok = SetupFindFirstLine( handle, modelSectionName, NULL, &modelContext );
492                                         err = translate_errno( ok, GetLastError(), kUnknownErr );
493                                         require_noerr( err, exit );
494                                 }
495                                 else
496                                 {
497                                         SetupFindNextLine( &modelContext, &modelContext );
498                                         err = translate_errno( ok, GetLastError(), kUnknownErr );
499                                         require_noerr( err, exit );
500                                 }
501
502                                 ok = SetupGetStringField( &modelContext, 0, line, sizeof( line ), NULL );
503                                 err = translate_errno( ok, GetLastError(), kUnknownErr );
504                                 require_noerr( err, exit );
505
506                                 modelName = line;
507
508                                 if (checkForDuplicateModels == true)
509                                 {
510                                         if ( MatchModel( manufacturer, ConvertToModelName( modelName ) ) != NULL )
511                                         {
512                                                 continue;
513                                         }
514                                 }
515
516                                 //
517                                 // Stock Vista printer inf files embed guids in the model
518                                 // declarations for Epson printers. Let's ignore those.
519                                 //
520                                 if ( modelName.Find( TEXT( "{" ), 0 ) != -1 )
521                                 {
522                                         continue;
523                                 }
524
525                                 try
526                                 {
527                                         model = new Model;
528                                 }
529                                 catch (...)
530                                 {
531                                         model = NULL;
532                                 }
533
534                                 require_action( model, exit, err = kNoMemoryErr );
535
536                                 model->infFileName              =       filename;
537                                 model->displayName              =       modelName;
538                                 model->name                             =       modelName;
539                                 model->driverInstalled  =       false;
540
541                                 manufacturer->models.push_back(model);
542                         }
543                 }
544         }
545
546 exit:
547
548         if ( handle != INVALID_HANDLE_VALUE )
549         {
550                 SetupCloseInfFile( handle );
551                 handle = NULL;
552         }
553
554         return err;
555 }
556
557
558 // -------------------------------------------------------
559 // LoadPrintDriverDefs
560 //
561 // This function is responsible for loading the print driver
562 // definitions of all print drivers that have been installed
563 // on this machine.
564 // -------------------------------------------------------
565 OSStatus
566 CThirdPage::LoadPrintDriverDefs( Manufacturers & manufacturers )
567 {
568         BYTE    *       buffer                  =       NULL;
569         DWORD           bytesReceived   =       0;
570         DWORD           numPrinters             =       0;
571         OSStatus        err                             =       0;
572         BOOL            ok;
573
574         //
575         // like a lot of win32 calls, we call this first to get the
576         // size of the buffer we need.
577         //
578         EnumPrinterDrivers(NULL, L"all", 6, NULL, 0, &bytesReceived, &numPrinters);
579
580         if (bytesReceived > 0)
581         {
582                 try
583                 {
584                         buffer = new BYTE[bytesReceived];
585                 }
586                 catch (...)
587                 {
588                         buffer = NULL;
589                 }
590         
591                 require_action( buffer, exit, err = kNoMemoryErr );
592                 
593                 //
594                 // this call gets the real info
595                 //
596                 ok = EnumPrinterDrivers(NULL, L"all", 6, buffer, bytesReceived, &bytesReceived, &numPrinters);
597                 err = translate_errno( ok, errno_compat(), kUnknownErr );
598                 require_noerr( err, exit );
599         
600                 DRIVER_INFO_6 * info = (DRIVER_INFO_6*) buffer;
601         
602                 for (DWORD i = 0; i < numPrinters; i++)
603                 {
604                         Manufacturer    *       manufacturer;
605                         Model                   *       model;
606                         CString                         name;
607         
608                         //
609                         // skip over anything that doesn't have a manufacturer field.  This
610                         // fixes a bug that I noticed that occurred after I installed
611                         // ProComm.  This program add a print driver with no manufacturer
612                         // that screwed up this wizard.
613                         //
614                         if (info[i].pszMfgName == NULL)
615                         {
616                                 continue;
617                         }
618         
619                         //
620                         // look for manufacturer
621                         //
622                         Manufacturers::iterator iter;
623         
624                         //
625                         // save the name
626                         //
627                         name = NormalizeManufacturerName( info[i].pszMfgName );
628         
629                         iter = manufacturers.find(name);
630         
631                         if (iter != manufacturers.end())
632                         {
633                                 manufacturer = iter->second;
634                         }
635                         else
636                         {
637                                 try
638                                 {
639                                         manufacturer = new Manufacturer;
640                                 }
641                                 catch (...)
642                                 {
643                                         manufacturer = NULL;
644                                 }
645         
646                                 require_action( manufacturer, exit, err = kNoMemoryErr );
647         
648                                 manufacturer->name      =       name;
649         
650                                 manufacturers[name]     =       manufacturer;
651                         }
652         
653                         //
654                         // now look to see if we have already seen this guy.  this could
655                         // happen if we have already installed printers that are described
656                         // in ntprint.inf.  the extant drivers will show up in EnumPrinterDrivers
657                         // but we have already loaded their info
658                         //
659                         //
660                         if ( MatchModel( manufacturer, ConvertToModelName( info[i].pName ) ) == NULL )
661                         {
662                                 try
663                                 {
664                                         model = new Model;
665                                 }
666                                 catch (...)
667                                 {
668                                         model = NULL;
669                                 }
670         
671                                 require_action( model, exit, err = kNoMemoryErr );
672         
673                                 model->displayName              =       info[i].pName;
674                                 model->name                             =       info[i].pName;
675                                 model->driverInstalled  =       true;
676         
677                                 manufacturer->models.push_back(model);
678                         }
679                 }
680         }
681
682 exit:
683
684         if (buffer != NULL)
685         {
686                 delete [] buffer;
687         }
688
689         return err;
690 }
691
692 // -------------------------------------------------------
693 // LoadGenericPrintDriverDefs
694 //
695 // This function is responsible for loading polymorphic
696 // generic print drivers defs.  The UI will read
697 // something like "Generic / Postscript" and we can map
698 // that to any print driver we want.
699 // -------------------------------------------------------
700 OSStatus
701 CThirdPage::LoadGenericPrintDriverDefs( Manufacturers & manufacturers )
702 {
703         Manufacturer            *       manufacturer;
704         Model                           *       model;
705         Manufacturers::iterator iter;
706         CString                                 psDriverName;
707         CString                                 pclDriverName;
708         OSStatus                                err     = 0;
709
710         // <rdar://problem/4030388> Generic drivers don't do color
711
712         // First try and find our generic driver names
713
714         iter = m_manufacturers.find(L"HP");
715         require_action( iter != m_manufacturers.end(), exit, err = kUnknownErr );
716         manufacturer = iter->second;
717
718         // Look for Postscript
719
720         model = manufacturer->find( kGenericPSColorDriver );
721
722         if ( !model )
723         {
724                 model = manufacturer->find( kGenericPSDriver );
725         }
726
727         if ( model )
728         {
729                 psDriverName = model->name;
730         }
731
732         // Look for PCL
733         
734         model = manufacturer->find( kGenericPCLColorDriver );
735
736         if ( !model )
737         {
738                 model = manufacturer->find( kGenericPCLDriver );
739         }
740
741         if ( model )
742         {
743                 pclDriverName = model->name;
744         }
745
746         // If we found either a generic PS driver, or a generic PCL driver,
747         // then add them to the list
748
749         if ( psDriverName.GetLength() || pclDriverName.GetLength() )
750         {
751                 // Try and find generic manufacturer if there is one
752
753                 iter = manufacturers.find(L"Generic");
754                 
755                 if (iter != manufacturers.end())
756                 {
757                         manufacturer = iter->second;
758                 }
759                 else
760                 {
761                         try
762                         {
763                                 manufacturer = new Manufacturer;
764                         }
765                         catch (...)
766                         {
767                                 manufacturer = NULL;
768                         }
769                 
770                         require_action( manufacturer, exit, err = kNoMemoryErr );
771                 
772                         manufacturer->name                                      =       "Generic";
773                         manufacturers[manufacturer->name]       =       manufacturer;
774                 }
775
776                 if ( psDriverName.GetLength() > 0 )
777                 {
778                         try
779                         {
780                                 m_genericPostscript = new Model;
781                         }
782                         catch (...)
783                         {
784                                 m_genericPostscript = NULL;
785                         }
786                         
787                         require_action( m_genericPostscript, exit, err = kNoMemoryErr );
788
789                         m_genericPostscript->displayName                =       kGenericPostscript;
790                         m_genericPostscript->name                               =       psDriverName;
791                         m_genericPostscript->driverInstalled    =       false;
792
793                         manufacturer->models.push_back( m_genericPostscript );
794                 }
795
796                 if ( pclDriverName.GetLength() > 0 )
797                 {
798                         try
799                         {
800                                 m_genericPCL = new Model;
801                         }
802                         catch (...)
803                         {
804                                 m_genericPCL = NULL;
805                         }
806                         
807                         require_action( m_genericPCL, exit, err = kNoMemoryErr );
808
809                         m_genericPCL->displayName               =       kGenericPCL;
810                         m_genericPCL->name                              =       pclDriverName;
811                         m_genericPCL->driverInstalled   =       false;
812
813                         manufacturer->models.push_back( m_genericPCL );
814                 }
815         }
816
817 exit:
818
819         return err;
820 }
821
822 // ------------------------------------------------------
823 // ConvertToManufacturerName
824 //
825 // This function is responsible for tweaking the
826 // name so that subsequent string operations won't fail because
827 // of capitalizations/different names for the same manufacturer
828 // (i.e.  Hewlett-Packard/HP/Hewlett Packard)
829 //
830 CString
831 CThirdPage::ConvertToManufacturerName( const CString & name )
832 {
833         //
834         // first we're going to convert all the characters to lower
835         // case
836         //
837         CString lower = name;
838         lower.MakeLower();
839
840         //
841         // now we're going to check to see if the string says "hewlett-packard",
842         // because sometimes they refer to themselves as "hewlett-packard", and
843         // sometimes they refer to themselves as "hp".
844         //
845         if ( lower == L"hewlett-packard")
846         {
847                 lower = "hp";
848         }
849
850         //
851         // tweak for Xerox Phaser, which doesn't announce itself
852         // as a xerox
853         //
854         else if ( lower.Find( L"phaser", 0 ) != -1 )
855         {
856                 lower = "xerox";
857         }
858
859         return lower;
860 }
861
862 // ------------------------------------------------------
863 // ConvertToModelName
864 //
865 // This function is responsible for ensuring that subsequent
866 // string operations don't fail because of differing capitalization
867 // schemes and the like
868 // ------------------------------------------------------
869
870 CString
871 CThirdPage::ConvertToModelName( const CString & name )
872 {
873         //
874         // convert it to lowercase
875         //
876         CString lower = name;
877         lower.MakeLower();
878
879         return lower;
880 }
881
882 // ------------------------------------------------------
883 // NormalizeManufacturerName
884 //
885 // This function is responsible for tweaking the manufacturer
886 // name so that there are no aliases for vendors
887 //
888 CString
889 CThirdPage::NormalizeManufacturerName( const CString & name )
890 {
891         CString normalized = name;
892
893         //
894         // now we're going to check to see if the string says "hewlett-packard",
895         // because sometimes they refer to themselves as "hewlett-packard", and
896         // sometimes they refer to themselves as "hp".
897         //
898         if ( normalized == L"Hewlett-Packard")
899         {
900                 normalized = "HP";
901         }
902
903         return normalized;
904 }
905
906 // -------------------------------------------------------
907 // MatchPrinter
908 //
909 // This function is responsible for matching a printer
910 // to a list of manufacturers and models.  It calls
911 // MatchManufacturer and MatchModel in turn.
912 //
913
914 OSStatus CThirdPage::MatchPrinter(Manufacturers & manufacturers, Printer * printer, Service * service, bool useCUPSWorkaround)
915 {
916         CString                                 normalizedProductName;
917         Manufacturer            *       manufacturer            =       NULL;
918         Manufacturer            *       genericManufacturer     =       NULL;
919         Model                           *       model                           =       NULL;
920         Model                           *       genericModel            =       NULL;
921         bool                                    found                           =       false;
922         CString                                 text;
923         OSStatus                                err                                     =       kNoErr;
924
925         check( printer );
926         check( service );
927
928         Queue * q = service->SelectedQueue();
929
930         check( q );
931
932         //
933         // first look to see if we have a usb_MFG descriptor
934         //
935         if ( q->usb_MFG.GetLength() > 0)
936         {
937                 manufacturer = MatchManufacturer( manufacturers, ConvertToManufacturerName ( q->usb_MFG ) );
938         }
939
940         if ( manufacturer == NULL )
941         {
942                 q->product.Remove('(');
943                 q->product.Remove(')');
944
945                 manufacturer = MatchManufacturer( manufacturers, ConvertToManufacturerName ( q->product ) );
946         }
947         
948         //
949         // if we found the manufacturer, then start looking for the model
950         //
951         if ( manufacturer != NULL )
952         {
953                 if ( q->usb_MDL.GetLength() > 0 )
954                 {
955                         model = MatchModel ( manufacturer, ConvertToModelName ( q->usb_MDL ) );
956                 }
957
958                 if ( ( model == NULL ) && ( q->product.GetLength() > 0 ) )
959                 {
960                         q->product.Remove('(');
961                         q->product.Remove(')');
962
963                         model = MatchModel ( manufacturer, ConvertToModelName ( q->product ) );
964                 }
965
966                 if ( model != NULL )
967                 {
968                         // <rdar://problem/4124524> Offer Generic printers if printer advertises Postscript or PCL.  Workaround
969                         // bug in OS X CUPS printer sharing by selecting Generic driver instead of matched printer.
970  
971                         bool hasGenericDriver = false;
972
973                         if ( MatchGeneric( manufacturers, printer, service, &genericManufacturer, &genericModel ) )
974                         {
975                                 hasGenericDriver = true;
976                         }
977
978                         // <rdar://problem/4190104> Use "application/octet-stream" to determine if CUPS
979                         // shared queue supports raw
980
981                         if ( q->pdl.Find( L"application/octet-stream" ) != -1 )
982                         {
983                                 useCUPSWorkaround = false;
984                         }
985
986                         if ( useCUPSWorkaround && printer->isCUPSPrinter && hasGenericDriver )
987                         {
988                                 //
989                                 // <rdar://problem/4496652> mDNS: Don't allow user to choose non-working driver
990                                 //
991                                 Manufacturers genericManufacturers;
992
993                                 LoadGenericPrintDriverDefs( genericManufacturers );
994
995                                 SelectMatch( genericManufacturers, printer, service, genericManufacturer, genericModel );
996                         }
997                         else
998                         {
999                                 SelectMatch(manufacturers, printer, service, manufacturer, model);
1000                         }
1001
1002                         found = true;
1003                 }
1004         }
1005
1006         //
1007         // display a message to the user based on whether we could match
1008         // this printer
1009         //
1010         if (found)
1011         {
1012                 text.LoadString(IDS_PRINTER_MATCH_GOOD);
1013                 err = kNoErr;
1014         }
1015         else if ( MatchGeneric( manufacturers, printer, service, &genericManufacturer, &genericModel ) )
1016         {
1017                 if ( printer->isCUPSPrinter )
1018                 {
1019                         //
1020                         // <rdar://problem/4496652> mDNS: Don't allow user to choose non-working driver
1021                         //
1022                         Manufacturers genericManufacturers;
1023
1024                         LoadGenericPrintDriverDefs( genericManufacturers );
1025
1026                         SelectMatch( genericManufacturers, printer, service, genericManufacturer, genericModel );
1027                         
1028                         text.LoadString(IDS_PRINTER_MATCH_GOOD);
1029                 }
1030                 else
1031                 {
1032                         SelectMatch( manufacturers, printer, service, genericManufacturer, genericModel );
1033                         text.LoadString(IDS_PRINTER_MATCH_MAYBE);
1034                 }
1035
1036                 err = kNoErr;
1037         }
1038         else
1039         {
1040                 text.LoadString(IDS_PRINTER_MATCH_BAD);
1041
1042                 //
1043                 // if there was any crud in this list from before, get rid of it now
1044                 //
1045                 m_modelListCtrl.DeleteAllItems();
1046                 
1047                 //
1048                 // select the manufacturer if we found one
1049                 //
1050                 if (manufacturer != NULL)
1051                 {
1052                         LVFINDINFO      info;
1053                         int                     nIndex;
1054
1055                         //
1056                         // select the manufacturer
1057                         //
1058                         info.flags      = LVFI_STRING;
1059                         info.psz        = manufacturer->name;
1060
1061                         nIndex = m_manufacturerListCtrl.FindItem(&info);
1062         
1063                         if (nIndex != -1)
1064                         {
1065                                 m_manufacturerListCtrl.SetItemState(nIndex, LVIS_SELECTED, LVIS_SELECTED);
1066
1067                                 //
1068                                 //<rdar://problem/4528853> mDNS: When auto-highlighting items in lists, scroll list so highlighted item is in the middle
1069                                 //
1070                                 AutoScroll(m_manufacturerListCtrl, nIndex);
1071                         }
1072                 }
1073
1074                 err = kUnknownErr;
1075         }
1076
1077         m_printerSelectionText.SetWindowText(text);
1078
1079         return err;
1080 }
1081
1082 // ------------------------------------------------------
1083 // MatchManufacturer
1084 //
1085 // This function is responsible for finding a manufacturer
1086 // object from a string name.  It does a CString::Find, which
1087 // is like strstr, so it doesn't have to do an exact match
1088 //
1089 // If it can't find a match, NULL is returned
1090 // ------------------------------------------------------
1091
1092 Manufacturer*
1093 CThirdPage::MatchManufacturer( Manufacturers & manufacturers, const CString & name)
1094 {
1095         Manufacturers::iterator iter;
1096
1097         for (iter = manufacturers.begin(); iter != manufacturers.end(); iter++)
1098         {
1099                 //
1100                 // we're going to convert all the manufacturer names to lower case,
1101                 // so we match the name passed in.
1102                 //
1103                 CString lower = iter->second->name;
1104                 lower.MakeLower();
1105
1106                 //
1107                 // now try and find the lowered string in the name passed in.
1108                 //
1109                 if (name.Find(lower) != -1)
1110                 {
1111                         return iter->second;
1112                 }
1113         }
1114
1115         return NULL;
1116 }
1117
1118 // -------------------------------------------------------
1119 // MatchModel
1120 //
1121 // This function is responsible for matching a model from
1122 // a name.  It does a CString::Find(), which works like strstr,
1123 // so it doesn't rely on doing an exact string match.
1124 //
1125
1126 Model*
1127 CThirdPage::MatchModel(Manufacturer * manufacturer, const CString & name)
1128 {
1129         Models::iterator iter;
1130
1131         iter = manufacturer->models.begin();
1132
1133         for (iter = manufacturer->models.begin(); iter != manufacturer->models.end(); iter++)
1134         {
1135                 Model * model = *iter;
1136
1137                 //
1138                 // convert the model name to lower case
1139                 //
1140                 CString lowered = model->name;
1141                 lowered.MakeLower();
1142
1143                 if (lowered.Find( name ) != -1)
1144                 {
1145                         return model;
1146                 }
1147
1148                 //
1149                 // <rdar://problem/3841218>
1150                 // try removing the first substring and search again
1151                 //
1152
1153                 if ( name.Find(' ') != -1 )
1154                 {
1155                         CString altered = name;
1156                         altered.Delete( 0, altered.Find(' ') + 1 );
1157
1158                         if ( lowered.Find( altered ) != -1 )
1159                         {
1160                                 return model;
1161                         }
1162                 }
1163         }
1164
1165         return NULL;
1166 }
1167
1168 // -------------------------------------------------------
1169 // MatchGeneric
1170 //
1171 // This function will attempt to find a generic printer
1172 // driver for a printer that we weren't able to match
1173 // specifically
1174 //
1175 BOOL
1176 CThirdPage::MatchGeneric( Manufacturers & manufacturers, Printer * printer, Service * service, Manufacturer ** manufacturer, Model ** model )
1177 {
1178         CString pdl;
1179         BOOL    ok = FALSE;
1180
1181         DEBUG_UNUSED( printer );
1182
1183         check( service );
1184
1185         Queue * q = service->SelectedQueue();
1186
1187         check( q );
1188
1189         Manufacturers::iterator iter = manufacturers.find( kGenericManufacturer );
1190         require_action_quiet( iter != manufacturers.end(), exit, ok = FALSE );
1191
1192         *manufacturer = iter->second;
1193
1194         pdl = q->pdl;
1195         pdl.MakeLower();
1196
1197         if ( m_genericPCL && ( pdl.Find( kPDLPCLKey ) != -1 ) )
1198         {
1199                 *model  = m_genericPCL;
1200                 ok              = TRUE;
1201         }
1202         else if ( m_genericPostscript && ( pdl.Find( kPDLPostscriptKey ) != -1 ) )
1203         {
1204                 *model  = m_genericPostscript;
1205                 ok              = TRUE;
1206         }
1207
1208 exit:
1209
1210         return ok;
1211 }
1212
1213 // -----------------------------------------------------------
1214 // OnInitPage
1215 //
1216 // This function is responsible for doing initialization that
1217 // only occurs once during a run of the wizard
1218 //
1219
1220 OSStatus CThirdPage::OnInitPage()
1221 {
1222         CString         header;
1223         CString         ntPrint;
1224         OSStatus        err = kNoErr;
1225
1226         // Load printer icon
1227         check( m_printerImage == NULL );
1228         
1229         m_printerImage = (CStatic*) GetDlgItem( 1 );    // 1 == IDR_MANIFEST
1230         check( m_printerImage );
1231
1232         if ( m_printerImage != NULL )
1233         {
1234                 m_printerImage->SetIcon( LoadIcon( GetNonLocalizedResources(), MAKEINTRESOURCE( IDI_PRINTER ) ) );
1235         }
1236
1237         //
1238         // The CTreeCtrl widget automatically sends a selection changed
1239         // message which initially we want to ignore, because the user
1240         // hasn't selected anything
1241         //
1242         // this flag gets reset in the message handler.  Every subsequent
1243         // message gets handled.
1244         //
1245
1246         //
1247         // we have to make sure that we only do this once.  Typically,
1248         // we would do this in something like OnInitDialog, but we don't
1249         // have this in Wizards, because the window is a PropertySheet.
1250         // We're considered fully initialized when we receive the first
1251         // selection notice
1252         //
1253         header.LoadString(IDS_MANUFACTURER_HEADING);
1254         m_manufacturerListCtrl.InsertColumn(0, header, LVCFMT_LEFT, -1 );
1255         m_manufacturerSelected = NULL;
1256
1257         header.LoadString(IDS_MODEL_HEADING);
1258         m_modelListCtrl.InsertColumn(0, header, LVCFMT_LEFT, -1 );
1259         m_modelSelected = NULL;
1260
1261         return (err);
1262 }
1263
1264 void CThirdPage::DoDataExchange(CDataExchange* pDX)
1265 {
1266         CPropertyPage::DoDataExchange(pDX);
1267         DDX_Control(pDX, IDC_PRINTER_MANUFACTURER, m_manufacturerListCtrl);
1268         DDX_Control(pDX, IDC_PRINTER_MODEL, m_modelListCtrl);
1269         DDX_Control(pDX, IDC_PRINTER_NAME, m_printerName);
1270         DDX_Control(pDX, IDC_DEFAULT_PRINTER, m_defaultPrinterCtrl);
1271         DDX_Control(pDX, IDC_PRINTER_SELECTION_TEXT, m_printerSelectionText);
1272
1273 }
1274
1275 // ----------------------------------------------------------
1276 // OnSetActive
1277 //
1278 // This function is called by MFC after the window has been
1279 // activated.
1280 //
1281
1282 BOOL
1283 CThirdPage::OnSetActive()
1284 {
1285         CPrinterSetupWizardSheet        *       psheet;
1286         Printer                                         *       printer;
1287         Service                                         *       service;
1288
1289         psheet = reinterpret_cast<CPrinterSetupWizardSheet*>(GetParent());
1290         require_quiet( psheet, exit );
1291    
1292         psheet->SetWizardButtons( PSWIZB_BACK );
1293
1294         printer = psheet->GetSelectedPrinter();
1295         require_quiet( printer, exit );
1296
1297         service = printer->services.front();
1298         require_quiet( service, exit );
1299
1300         //
1301         // call OnInitPage once
1302         //
1303         if (!m_initialized)
1304         {
1305                 OnInitPage();
1306                 m_initialized = true;
1307         }
1308
1309         //
1310         // <rdar://problem/4580061> mDNS: Printers added using Bonjour should be set as the default printer.
1311         //
1312         if ( DefaultPrinterExists() )
1313         {
1314                 m_defaultPrinterCtrl.SetCheck( BST_UNCHECKED );
1315                 printer->deflt = false;
1316         }
1317         else
1318         {
1319                 m_defaultPrinterCtrl.SetCheck( BST_CHECKED );
1320                 printer->deflt = true;
1321         }
1322
1323         //
1324         // update the UI with the printer name
1325         //
1326         m_printerName.SetWindowText(printer->displayName);
1327
1328         //
1329         // populate the list controls with the manufacturers and models
1330         // from ntprint.inf
1331         //
1332         PopulateUI( m_manufacturers );
1333
1334         //
1335         // and try and match the printer
1336         //
1337
1338         if ( psheet->GetLastPage() == psheet->GetPage(0) )
1339         {
1340                 MatchPrinter( m_manufacturers, printer, service, true );
1341
1342                 if ( ( m_manufacturerSelected != NULL ) && ( m_modelSelected != NULL  ) )
1343                 {
1344                         GetParent()->PostMessage(PSM_SETCURSEL, 2 );
1345                 }
1346         }
1347         else
1348         {
1349                 SelectMatch(printer, service, m_manufacturerSelected, m_modelSelected);
1350         }
1351
1352 exit:
1353
1354         return CPropertyPage::OnSetActive();
1355 }
1356
1357 BOOL
1358 CThirdPage::OnKillActive()
1359 {
1360         CPrinterSetupWizardSheet * psheet;
1361
1362         psheet = reinterpret_cast<CPrinterSetupWizardSheet*>(GetParent());
1363         require_quiet( psheet, exit );
1364    
1365         psheet->SetLastPage(this);
1366
1367 exit:
1368
1369         return CPropertyPage::OnKillActive();
1370 }
1371
1372 // -------------------------------------------------------
1373 // PopulateUI
1374 //
1375 // This function is called to populate the list of manufacturers
1376 //
1377 OSStatus
1378 CThirdPage::PopulateUI(Manufacturers & manufacturers)
1379 {
1380         Manufacturers::iterator iter;
1381         
1382         m_manufacturerListCtrl.DeleteAllItems();
1383
1384         for (iter = manufacturers.begin(); iter != manufacturers.end(); iter++)
1385         {
1386                 int nIndex;
1387
1388                 Manufacturer * manufacturer = iter->second;
1389
1390                 nIndex = m_manufacturerListCtrl.InsertItem(0, manufacturer->name);
1391
1392                 m_manufacturerListCtrl.SetItemData(nIndex, (DWORD_PTR) manufacturer);
1393
1394                 m_manufacturerListCtrl.SetColumnWidth( 0, LVSCW_AUTOSIZE_USEHEADER );
1395         }
1396
1397         return 0;
1398 }
1399
1400 BEGIN_MESSAGE_MAP(CThirdPage, CPropertyPage)
1401         ON_NOTIFY(LVN_ITEMCHANGED, IDC_PRINTER_MANUFACTURER, OnLvnItemchangedManufacturer)
1402         ON_NOTIFY(LVN_ITEMCHANGED, IDC_PRINTER_MODEL, OnLvnItemchangedPrinterModel)
1403         ON_BN_CLICKED(IDC_DEFAULT_PRINTER, OnBnClickedDefaultPrinter)
1404         ON_BN_CLICKED(IDC_HAVE_DISK, OnBnClickedHaveDisk)
1405 END_MESSAGE_MAP()
1406
1407 // CThirdPage message handlers
1408 void CThirdPage::OnLvnItemchangedManufacturer(NMHDR *pNMHDR, LRESULT *pResult)
1409 {
1410         LPNMLISTVIEW pNMLV = reinterpret_cast<LPNMLISTVIEW>(pNMHDR);
1411
1412         POSITION p = m_manufacturerListCtrl.GetFirstSelectedItemPosition();
1413         int nSelected = m_manufacturerListCtrl.GetNextSelectedItem(p);
1414
1415         if (nSelected != -1)
1416         {
1417                 m_manufacturerSelected = (Manufacturer*) m_manufacturerListCtrl.GetItemData(nSelected);
1418
1419                 m_modelListCtrl.SetRedraw(FALSE);
1420                 
1421                 m_modelListCtrl.DeleteAllItems();
1422                 m_modelSelected = NULL;
1423
1424                 Models::iterator iter;
1425
1426                 for (iter = m_manufacturerSelected->models.begin(); iter != m_manufacturerSelected->models.end(); iter++)
1427                 {
1428                         Model * model = *iter;
1429
1430                         int nItem = m_modelListCtrl.InsertItem( 0, model->displayName );
1431
1432                         m_modelListCtrl.SetItemData(nItem, (DWORD_PTR) model);
1433
1434                         m_modelListCtrl.SetColumnWidth( 0, LVSCW_AUTOSIZE_USEHEADER );
1435                 }
1436
1437                 m_modelListCtrl.SetRedraw(TRUE);
1438         }
1439
1440         *pResult = 0;
1441 }
1442
1443 void CThirdPage::OnLvnItemchangedPrinterModel(NMHDR *pNMHDR, LRESULT *pResult)
1444 {
1445         LPNMLISTVIEW                                    pNMLV = reinterpret_cast<LPNMLISTVIEW>(pNMHDR);
1446         
1447         CPrinterSetupWizardSheet        *       psheet;
1448         Printer                                         *       printer;
1449         Service                                         *       service;
1450
1451         psheet = reinterpret_cast<CPrinterSetupWizardSheet*>(GetParent());
1452         require_quiet( psheet, exit );
1453
1454         printer = psheet->GetSelectedPrinter();
1455         require_quiet( printer, exit );
1456
1457         service = printer->services.front();
1458         require_quiet( service, exit );
1459
1460         check ( m_manufacturerSelected );
1461
1462         POSITION p = m_modelListCtrl.GetFirstSelectedItemPosition();
1463         int nSelected = m_modelListCtrl.GetNextSelectedItem(p);
1464
1465         if (nSelected != -1)
1466         {
1467                 m_modelSelected = (Model*) m_modelListCtrl.GetItemData(nSelected);
1468
1469                 CopyPrinterSettings( printer, service, m_manufacturerSelected, m_modelSelected );
1470
1471                 psheet->SetWizardButtons(PSWIZB_BACK|PSWIZB_NEXT);
1472         }
1473         else
1474         {
1475                 psheet->SetWizardButtons(PSWIZB_BACK);
1476         }
1477
1478 exit:
1479
1480         *pResult = 0;
1481 }
1482
1483 void CThirdPage::OnBnClickedDefaultPrinter()
1484 {
1485         CPrinterSetupWizardSheet        *       psheet;
1486         Printer                                         *       printer;
1487
1488         psheet = reinterpret_cast<CPrinterSetupWizardSheet*>(GetParent());
1489         require_quiet( psheet, exit );
1490
1491         printer = psheet->GetSelectedPrinter();
1492         require_quiet( printer, exit );
1493
1494         printer->deflt = ( m_defaultPrinterCtrl.GetCheck() == BST_CHECKED ) ? true : false;
1495
1496 exit:
1497
1498         return;
1499 }
1500
1501 void CThirdPage::OnBnClickedHaveDisk()
1502 {
1503         CPrinterSetupWizardSheet        *       psheet;
1504         Printer                                         *       printer;
1505         Service                                         *       service;
1506         Manufacturers                                   manufacturers;
1507
1508         CFileDialog dlg(TRUE, NULL, NULL, OFN_HIDEREADONLY|OFN_FILEMUSTEXIST, L"Setup Information (*.inf)|*.inf||", this);
1509
1510         psheet = reinterpret_cast<CPrinterSetupWizardSheet*>(GetParent());
1511         require_quiet( psheet, exit );
1512
1513         printer = psheet->GetSelectedPrinter();
1514         require_quiet( printer, exit );
1515         
1516         service = printer->services.front();
1517         require_quiet( service, exit );
1518
1519         for ( ;; )
1520         {
1521                 if ( dlg.DoModal() == IDOK )
1522                 {
1523                         CString filename = dlg.GetPathName();
1524
1525                         LoadPrintDriverDefsFromFile( manufacturers, filename, true );
1526    
1527                         // Sanity check
1528
1529                         if ( manufacturers.size() > 0 )
1530                         {
1531                                 PopulateUI( manufacturers );
1532
1533                                 if ( MatchPrinter( manufacturers, printer, service, false ) != kNoErr )
1534                                 {
1535                                         CString errorMessage;
1536                                         CString errorCaption;
1537                                         
1538                                         errorMessage.LoadString( IDS_NO_MATCH_INF_FILE );
1539                                         errorCaption.LoadString( IDS_NO_MATCH_INF_FILE_CAPTION );
1540
1541                                         MessageBox( errorMessage, errorCaption, MB_OK );
1542                                 }
1543
1544                                 break;
1545                         }
1546                         else
1547                         {
1548                                 CString errorMessage;
1549                                 CString errorCaption;
1550
1551                                 errorMessage.LoadString( IDS_BAD_INF_FILE );
1552                                 errorCaption.LoadString( IDS_BAD_INF_FILE_CAPTION );
1553
1554                                 MessageBox( errorMessage, errorCaption, MB_OK );
1555                         }
1556                 }
1557                 else
1558                 {
1559                         break;
1560                 }
1561         }
1562
1563 exit:
1564
1565         return;
1566 }
1567
1568
1569 void
1570 CThirdPage::Split( const CString & string, TCHAR ch, CStringList & components )
1571 {
1572         CString temp;
1573         int             n;
1574
1575         temp = string;
1576         
1577         while ( ( n = temp.Find( ch ) ) != -1 )
1578         {
1579                 components.AddTail( temp.Left( n ) );
1580                 temp = temp.Right( temp.GetLength() - ( n + 1 ) );
1581         }
1582
1583         components.AddTail( temp );
1584 }