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