]> git.saurik.com Git - apple/mdnsresponder.git/blobdiff - Clients/PrinterSetupWizard/ThirdPage.cpp
mDNSResponder-87.tar.gz
[apple/mdnsresponder.git] / Clients / PrinterSetupWizard / ThirdPage.cpp
diff --git a/Clients/PrinterSetupWizard/ThirdPage.cpp b/Clients/PrinterSetupWizard/ThirdPage.cpp
new file mode 100644 (file)
index 0000000..6828f3c
--- /dev/null
@@ -0,0 +1,1224 @@
+/*
+ * Copyright (c) 1997-2004 Apple Computer, Inc. All rights reserved.
+ *
+ * @APPLE_LICENSE_HEADER_START@
+ * 
+ * This file contains Original Code and/or Modifications of Original Code
+ * as defined in and that are subject to the Apple Public Source License
+ * Version 2.0 (the 'License'). You may not use this file except in
+ * compliance with the License. Please obtain a copy of the License at
+ * http://www.opensource.apple.com/apsl/ and read it before using this
+ * file.
+ * 
+ * The Original Code and all software distributed under the License are
+ * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
+ * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
+ * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
+ * Please see the License for the specific language governing rights and
+ * limitations under the License.
+ * 
+ * @APPLE_LICENSE_HEADER_END@
+
+    Change History (most recent first):
+    
+$Log: ThirdPage.cpp,v $
+Revision 1.10  2004/10/11 22:55:34  shersche
+<rdar://problem/3827624> Use the IP port number when deriving the printer port name.
+Bug #: 3827624
+
+Revision 1.9  2004/06/27 23:08:00  shersche
+code cleanup, make sure EnumPrintDrivers returns non-zero value, ignore comments in inf files
+
+Revision 1.8  2004/06/27 08:06:45  shersche
+Parse [Strings] section of inf file
+
+Revision 1.7  2004/06/26 04:00:05  shersche
+fix warnings compiling in debug mode
+Submitted by: herscher
+
+Revision 1.6  2004/06/26 03:19:57  shersche
+clean up warning messages
+
+Submitted by: herscher
+
+Revision 1.5  2004/06/25 05:06:02  shersche
+Trim whitespace from key/value pairs when parsing inf files
+Submitted by: herscher
+
+Revision 1.4  2004/06/25 02:44:13  shersche
+Tweaked code to handle Xerox Phaser printer identification
+Submitted by: herscher
+
+Revision 1.3  2004/06/25 02:27:58  shersche
+Do a CListCtrl::FindItem() before calling CListCtrl::SetItemState().
+Submitted by: herscher
+
+Revision 1.2  2004/06/23 18:09:23  shersche
+Normalize tag names when parsing inf files.
+Submitted by: herscher
+
+Revision 1.1  2004/06/18 04:36:58  rpantos
+First checked in
+
+
+*/
+
+#include "stdafx.h"
+#include "PrinterSetupWizardApp.h"
+#include "PrinterSetupWizardSheet.h"
+#include "ThirdPage.h"
+#include "StdioFileEx.h"
+#include <dns_sd.h>
+#include <tcpxcv.h>
+#include <winspool.h>
+
+// local variable is initialize but not referenced
+#pragma warning(disable:4189)
+
+
+//
+// This is the printer description file that is shipped
+// with Windows
+//
+#define kNTPrintFile           L"inf\\ntprint.inf"
+
+//
+// These are pre-defined names for Generic manufacturer and model
+//
+#define kGenericManufacturer   L"Generic"
+#define kGenericModel                  L"Generic / Text Only"
+
+//
+// states for parsing ntprint.inf
+//
+enum PrinterParsingState
+{
+       Looking,
+       ParsingManufacturers,
+       ParsingModels,
+       ParsingStrings
+};
+
+
+// CThirdPage dialog
+
+IMPLEMENT_DYNAMIC(CThirdPage, CPropertyPage)
+CThirdPage::CThirdPage()
+       : CPropertyPage(CThirdPage::IDD),
+               m_initialized(false)
+{
+       m_psp.dwFlags &= ~(PSP_HASHELP);
+       m_psp.dwFlags |= PSP_DEFAULT|PSP_USEHEADERTITLE|PSP_USEHEADERSUBTITLE;
+       
+       m_psp.pszHeaderTitle = MAKEINTRESOURCE(IDS_INSTALL_TITLE);
+       m_psp.pszHeaderSubTitle = MAKEINTRESOURCE(IDS_INSTALL_SUBTITLE);
+}
+
+
+CThirdPage::~CThirdPage()
+{
+       //
+       // clean up all the printer manufacturers
+       //
+       while (m_manufacturers.size())
+       {
+               Manufacturers::iterator iter = m_manufacturers.begin();
+
+               while (iter->second->models.size())
+               {
+                       Models::iterator it = iter->second->models.begin();
+
+                       Model * model = *it;
+
+                       delete model;
+
+                       iter->second->models.erase(it);
+               }
+
+               delete iter->second;
+
+               m_manufacturers.erase(iter);
+       }
+}
+
+
+// ----------------------------------------------------
+// SelectMatch
+//
+// SelectMatch will do all the UI work associated with
+// selected a manufacturer and model of printer.  It also
+// makes sure the printer object is update with the 
+// latest settings
+//
+// ----------------------------------------------------
+void
+CThirdPage::SelectMatch(Printer * printer, Manufacturer * manufacturer, Model * model)
+{
+       LVFINDINFO      info;
+       int                     nIndex;
+
+       check( printer != NULL );
+       check( manufacturer != NULL );
+       check( model != NULL );
+
+       Manufacturers manufacturers;
+       manufacturers[manufacturer->name] = manufacturer;
+
+       PopulateUI( manufacturers );
+
+       //
+       // select the manufacturer
+       //
+       info.flags      = LVFI_STRING;
+       info.psz        = manufacturer->name;
+
+       nIndex = m_manufacturerListCtrl.FindItem(&info);
+       
+       if (nIndex != -1)
+       {
+               m_manufacturerListCtrl.SetItemState(nIndex, LVIS_SELECTED, LVIS_SELECTED);
+               m_manufacturerListCtrl.EnsureVisible(nIndex, FALSE);
+       }
+
+       //
+       // select the model
+       //
+       info.flags      = LVFI_STRING;
+       info.psz        = model->name;
+
+       nIndex = m_modelListCtrl.FindItem(&info);
+
+       if (nIndex != -1)
+       {
+               m_modelListCtrl.SetItemState(nIndex, LVIS_SELECTED, LVIS_SELECTED);
+               m_modelListCtrl.EnsureVisible(nIndex, FALSE);
+
+               m_modelListCtrl.SetFocus();
+       }
+
+       CopyPrinterSettings( printer, manufacturer, model );
+}
+
+
+// --------------------------------------------------------
+// CopyPrinterSettings
+//
+// This function makes sure that the printer object has the
+// latest settings from the manufacturer and model objects
+// --------------------------------------------------------
+
+void
+CThirdPage::CopyPrinterSettings( Printer * printer, Manufacturer * manufacturer, Model * model )
+{
+       printer->manufacturer           =       manufacturer->name;
+       printer->model                          =       model->name;
+       printer->driverInstalled        =       model->driverInstalled;
+       printer->infFileName            =       model->infFileName;
+       printer->portName.Format(L"IP_%s.%d", static_cast<LPCTSTR>(printer->hostname), printer->portNumber);
+}
+
+
+// ------------------------------------------------------
+// LoadPrintDriverDefsFromFile
+//
+// This function does all the heavy lifting in parsing inf
+// files.  It is called to parse both ntprint.inf, and driver
+// files that might be shipped on a printer's installation
+// disk
+//
+// The inf file is not totally parsed.  I only want to determine
+// the manufacturer and models that are involved. I leave it
+// to printui.dll to actually copy the driver files to the
+// right places.
+//
+// I was aiming to parse as little as I could so as not to
+// duplicate the parsing code that is contained in Windows.  There
+// are no public APIs for parsing inf files.
+//
+// That part of the inf file that we're interested in has a fairly
+// easy format.  Tags are strings that are enclosed in brackets.
+// We are only interested in [MANUFACTURERS] and models.
+//
+// The only potentially opaque thing about this function is the 
+// checkForDuplicateModels flag.  The problem here is that ntprint.inf
+// doesn't contain duplicate models, and it has hundreds of models
+// listed.  You wouldn't check for duplicates there.  But oftentimes,
+// loading different windows print driver files contain multiple
+// entries for the same printer.  You don't want the UI to display
+// the same printer multiple times, so in that case, you would ask
+// this function to check for multiple models.
+
+OSStatus
+CThirdPage::LoadPrintDriverDefsFromFile(Manufacturers & manufacturers, const CString & filename, bool checkForDuplicateModels )
+{
+       PrinterParsingState             state           = Looking;
+       Manufacturers::iterator iter            = manufacturers.end();
+       CStdioFileEx                    file;
+       CFileException                  feError;
+       CString                                 s;
+       OSStatus                                err;
+       BOOL                                    ok;
+
+       typedef std::map<CString, CString> StringMap;
+
+       StringMap                               strings;
+       ok = file.Open( filename,  CFile::modeRead|CFile::typeText, &feError);
+       err = translate_errno( ok, errno_compat(), kUnknownErr );
+       require_noerr( err, exit );
+
+       check ( state == Looking );
+       check ( iter == manufacturers.end() );
+
+       //
+       // first, parse the file looking for string sections
+       //
+       while (file.ReadString(s))
+       {
+               //
+               // check for comment
+               //
+               if (s.Find(';') == 0)
+               {
+                       continue;
+               }
+
+               //
+               // check for tag
+               //
+               else if (s.Find('[') == 0)
+               {
+                       //
+                       // handle any capitalization issues here
+                       //
+                       CString tag = s;
+
+                       tag.MakeLower();
+
+                       if (tag == L"[strings]")
+                       {
+                               state = ParsingStrings;
+                       }
+                       else
+                       {
+                               state = Looking;
+                       }
+               }
+               else
+               {
+                       switch (state)
+                       {
+                               case ParsingStrings:
+                               {
+                                       int     curPos = 0;
+
+                                       if (s.GetLength() > 0)
+                                       {
+                                               CString key = s.Tokenize(L"=",curPos);
+                                               CString val = s.Tokenize(L"=",curPos);
+
+                                               //
+                                               // get rid of all delimiters
+                                               //
+                                               val.Remove('"');
+       
+                                               //
+                                               // and store it
+                                               //
+                                               strings[key] = val;
+                                       }
+                               }
+                               break;
+                       }
+               }
+       }
+
+       file.Close();
+
+       ok = file.Open( filename,  CFile::modeRead|CFile::typeText, &feError);
+       err = translate_errno( ok, errno_compat(), kUnknownErr );
+       require_noerr( err, exit );
+
+       state = Looking;
+
+       check ( iter == manufacturers.end() );
+
+       while (file.ReadString(s))
+       {
+               //
+               // check for comment
+               //
+               if (s.Find(';') == 0)
+               {
+                       continue;
+               }
+
+               //
+               // check for tag
+               //
+               else if (s.Find('[') == 0)
+               {
+                       //
+                       // handle any capitalization issues here
+                       //
+                       CString tag = s;
+
+                       tag.MakeLower();
+
+                       if (tag == L"[manufacturer]")
+                       {
+                               state = ParsingManufacturers;
+                       }
+                       else
+                       {
+                               // remove the leading and trailing delimiters
+                               //
+                               s.Remove('[');
+                               s.Remove(']');
+
+                               // check to see if this is a printer entry
+                               //
+                               iter = manufacturers.find(s);
+
+                               if (iter != manufacturers.end())
+                               {
+                                       state = ParsingModels;
+                               }
+                               else
+                               {
+                                       state = Looking;
+                               }
+                       }
+               }
+               //
+               // only look at this if the line isn't empty, or
+               // if it isn't a comment
+               //
+               else if ((s.GetLength() > 0) && (s.Find(';') != 0))
+               {
+                       switch (state)
+                       {
+                               //
+                               // if we're parsing manufacturers, then we will parse
+                               // an entry of the form key=val, where key is a delimited
+                               // string specifying a manufacturer name, and val is
+                               // a tag that is used later in the file.  the key is 
+                               // delimited by either '"' (quotes) or '%' (percent sign).
+                               //
+                               // the tag is used further down the file when models are
+                               // declared.  this allows multiple manufacturers to exist
+                               // in a single inf file.
+                               //
+                               case ParsingManufacturers:
+                               {
+                                       Manufacturer    *       manufacturer;
+                                       int                                     curPos = 0;
+
+                                       CString key = s.Tokenize(L"=",curPos);
+                                       CString val = s.Tokenize(L"=",curPos);
+
+                                       try
+                                       {
+                                               manufacturer = new Manufacturer;
+                                       }
+                                       catch (...)
+                                       {
+                                               manufacturer = NULL;
+                                       }
+
+                                       require_action( manufacturer, exit, err = kNoMemoryErr );
+
+                                       //
+                                       // if it's a variable, look it up
+                                       //
+                                       if (key.Find('%') == 0)
+                                       {
+                                               StringMap::iterator it;
+
+                                               key.Remove('%');
+
+                                               it = strings.find(key);
+
+                                               if (it != strings.end())
+                                               {
+                                                       key = it->second;
+                                               }
+                                       }
+                                       else
+                                       {
+                                               key.Remove('"');
+                                       }
+
+                                       val.TrimLeft();
+                                       val.TrimRight();
+
+                                       //
+                                       // why is there no consistency in inf files?
+                                       //
+                                       if (val.GetLength() == 0)
+                                       {
+                                               val = key;
+                                       }
+
+                                       //
+                                       // fix the manufacturer name if necessary
+                                       //
+                                       curPos  =       0;
+                                       val             =       val.Tokenize(L",", curPos);
+
+                                       manufacturer->name = NormalizeManufacturerName( key );
+                                       manufacturer->tag  = val;
+
+                                       manufacturers[val] = manufacturer;
+                               }
+                               break;
+
+                               case ParsingModels:
+                               {
+                                       check( iter != manufacturers.end() );
+
+                                       Model   *       model;
+                                       int                     curPos = 0;
+
+                                       CString name            = s.Tokenize(L"=",curPos);
+                                       CString description = s.Tokenize(L"=",curPos);
+                                       
+                                       name.Remove('"');
+                                       name.Trim();
+                                       description.Trim();
+                                       
+                                       //
+                                       // If true, see if we've seen this guy before
+                                       //
+                                       if (checkForDuplicateModels == true)
+                                       {
+                                               if ( MatchModel( iter->second, name ) != NULL )
+                                               {
+                                                       continue;
+                                               }
+                                       }
+
+                                       try
+                                       {
+                                               model = new Model;
+                                       }
+                                       catch (...)
+                                       {
+                                               model = NULL;
+                                       }
+
+                                       require_action( model, exit, err = kNoMemoryErr );
+
+                                       model->infFileName              =       filename;
+                                       model->name                             =       name;
+                                       model->driverInstalled  =       false;
+
+                                       iter->second->models.push_back(model);
+                               }
+                               break;
+
+                               default:
+                               {
+                                       // pay no attention if we are in any other state
+                               }
+                               break;
+                       }
+               }
+       }
+
+exit:
+
+       file.Close();
+
+       return (err);
+}
+
+
+// -------------------------------------------------------
+// LoadPrintDriverDefs
+//
+// This function is responsible for loading the print driver
+// definitions of all print drivers that have been installed
+// on this machine.
+// -------------------------------------------------------
+OSStatus
+CThirdPage::LoadPrintDriverDefs( Manufacturers & manufacturers )
+{
+       BYTE    *       buffer                  =       NULL;
+       DWORD           bytesReceived   =       0;
+       DWORD           numPrinters             =       0;
+       OSStatus        err                             =       0;
+       BOOL            ok;
+
+       //
+       // like a lot of win32 calls, we call this first to get the
+       // size of the buffer we need.
+       //
+       EnumPrinterDrivers(NULL, L"all", 6, NULL, 0, &bytesReceived, &numPrinters);
+
+       if (bytesReceived > 0)
+       {
+               try
+               {
+                       buffer = new BYTE[bytesReceived];
+               }
+               catch (...)
+               {
+                       buffer = NULL;
+               }
+       
+               require_action( buffer, exit, err = kNoMemoryErr );
+               
+               //
+               // this call gets the real info
+               //
+               ok = EnumPrinterDrivers(NULL, L"all", 6, buffer, bytesReceived, &bytesReceived, &numPrinters);
+               err = translate_errno( ok, errno_compat(), kUnknownErr );
+               require_noerr( err, exit );
+       
+               DRIVER_INFO_6 * info = (DRIVER_INFO_6*) buffer;
+       
+               for (DWORD i = 0; i < numPrinters; i++)
+               {
+                       Manufacturer    *       manufacturer;
+                       Model                   *       model;
+                       CString                         name;
+       
+                       //
+                       // skip over anything that doesn't have a manufacturer field.  This
+                       // fixes a bug that I noticed that occurred after I installed 
+                       // ProComm.  This program add a print driver with no manufacturer
+                       // that screwed up this wizard.
+                       //
+                       if (info[i].pszMfgName == NULL)
+                       {
+                               continue;
+                       }
+       
+                       //
+                       // look for manufacturer
+                       //
+                       Manufacturers::iterator iter;
+       
+                       //
+                       // save the name
+                       //
+                       name = NormalizeManufacturerName( info[i].pszMfgName );
+       
+                       iter = manufacturers.find(name);
+       
+                       if (iter != manufacturers.end())
+                       {
+                               manufacturer = iter->second;
+                       }
+                       else
+                       {
+                               try
+                               {
+                                       manufacturer = new Manufacturer;
+                               }
+                               catch (...)
+                               {
+                                       manufacturer = NULL;
+                               }
+       
+                               require_action( manufacturer, exit, err = kNoMemoryErr );
+       
+                               manufacturer->name      =       name;
+       
+                               manufacturers[name]     =       manufacturer;
+                       }
+       
+                       //
+                       // now look to see if we have already seen this guy.  this could
+                       // happen if we have already installed printers that are described
+                       // in ntprint.inf.  the extant drivers will show up in EnumPrinterDrivers
+                       // but we have already loaded their info
+                       //
+                       //
+                       if ( MatchModel( manufacturer, ConvertToModelName( info[i].pName ) ) == NULL )
+                       {
+                               try
+                               {
+                                       model = new Model;
+                               }
+                               catch (...)
+                               {
+                                       model = NULL;
+                               }
+       
+                               require_action( model, exit, err = kNoMemoryErr );
+       
+                               model->name                             =       info[i].pName;
+                               model->driverInstalled  =       true;
+       
+                               manufacturer->models.push_back(model);
+                       }
+               }
+       }
+
+exit:
+
+       if (buffer != NULL)
+       {
+               delete [] buffer;
+       }
+
+       return err;
+}
+
+
+// ------------------------------------------------------
+// ConvertToManufacturerName
+//
+// This function is responsible for tweaking the 
+// name so that subsequent string operations won't fail because
+// of capitalizations/different names for the same manufacturer
+// (i.e.  Hewlett-Packard/HP/Hewlett Packard)
+//
+CString
+CThirdPage::ConvertToManufacturerName( const CString & name )
+{
+       //
+       // first we're going to convert all the characters to lower
+       // case
+       //
+       CString lower = name;
+       lower.MakeLower();
+
+       //
+       // now we're going to check to see if the string says "hewlett-packard",
+       // because sometimes they refer to themselves as "hewlett-packard", and
+       // sometimes they refer to themselves as "hp".
+       //
+       if ( lower == L"hewlett-packard")
+       {
+               lower = "hp";
+       }
+
+       //
+       // tweak for Xerox Phaser, which doesn't announce itself
+       // as a xerox
+       //
+       else if ( lower.Find( L"phaser", 0 ) != -1 )
+       {
+               lower = "xerox";
+       }
+
+       return lower;
+}
+
+
+// ------------------------------------------------------
+// ConvertToModelName
+//
+// This function is responsible for ensuring that subsequent
+// string operations don't fail because of differing capitalization
+// schemes and the like
+// ------------------------------------------------------
+
+CString
+CThirdPage::ConvertToModelName( const CString & name )
+{
+       //
+       // convert it to lowercase
+       //
+       CString lower = name;
+       lower.MakeLower();
+
+       return lower;
+}
+
+
+// ------------------------------------------------------
+// NormalizeManufacturerName
+//
+// This function is responsible for tweaking the manufacturer
+// name so that there are no aliases for vendors
+//
+CString
+CThirdPage::NormalizeManufacturerName( const CString & name )
+{
+       CString normalized = name;
+
+       //
+       // now we're going to check to see if the string says "hewlett-packard",
+       // because sometimes they refer to themselves as "hewlett-packard", and
+       // sometimes they refer to themselves as "hp".
+       //
+       if ( normalized == L"Hewlett-Packard")
+       {
+               normalized = "HP";
+       }
+
+       return normalized;
+}
+
+
+// -------------------------------------------------------
+// MatchPrinter
+//
+// This function is responsible for matching a printer
+// to a list of manufacturers and models.  It calls
+// MatchManufacturer and MatchModel in turn.
+//
+
+OSStatus CThirdPage::MatchPrinter(Manufacturers & manufacturers, Printer * printer)
+{
+       CString                                 normalizedProductName;
+       Manufacturer            *       manufacturer    =       NULL;
+       Model                           *       model                   =       NULL;
+       bool                                    found                   =       false;
+       CString                                 text;
+       OSStatus                                err                             =       kNoErr;
+
+       //
+       // first look to see if we have a usb_MFG descriptor
+       //
+       if (printer->usb_MFG.GetLength() > 0)
+       {
+               manufacturer = MatchManufacturer( manufacturers, ConvertToManufacturerName ( printer->usb_MFG ) );
+       }
+
+       if ( manufacturer == NULL )
+       {
+               printer->product.Remove('(');
+               printer->product.Remove(')');
+
+               manufacturer = MatchManufacturer( manufacturers, ConvertToManufacturerName ( printer->product ) );
+       }
+       
+       //
+       // if we found the manufacturer, then start looking for the model
+       //
+       if ( manufacturer != NULL )
+       {
+               if (printer->usb_MDL.GetLength() > 0)
+               {
+                       model = MatchModel ( manufacturer, ConvertToModelName ( printer->usb_MDL ) );
+               }
+
+               if ( model == NULL )
+               {
+                       printer->product.Remove('(');
+                       printer->product.Remove(')');
+
+                       model = MatchModel ( manufacturer, ConvertToModelName ( printer->product ) );
+               }
+
+               if ( model != NULL )
+               {
+                       SelectMatch(printer, manufacturer, model);
+                       found = true;
+               }
+       }
+
+       //
+       // display a message to the user based on whether we could match
+       // this printer
+       //
+       if (found)
+       {
+               text.LoadString(IDS_PRINTER_MATCH_GOOD);
+       }
+       else
+       {
+               text.LoadString(IDS_PRINTER_MATCH_BAD);
+
+               //
+               // if there was any crud in this list from before, get rid of it now
+               //
+               m_modelListCtrl.DeleteAllItems();
+               
+               //
+               // select the manufacturer if we found one
+               //
+               if (manufacturer != NULL)
+               {
+                       LVFINDINFO      info;
+                       int                     nIndex;
+
+                       //
+                       // select the manufacturer
+                       //
+                       info.flags      = LVFI_STRING;
+                       info.psz        = manufacturer->name;
+
+                       nIndex = m_manufacturerListCtrl.FindItem(&info);
+       
+                       if (nIndex != -1)
+                       {
+                               m_manufacturerListCtrl.SetItemState(nIndex, LVIS_SELECTED, LVIS_SELECTED);
+                               m_manufacturerListCtrl.EnsureVisible(nIndex, FALSE);
+                       }
+               }
+       }
+
+       m_printerSelectionText.SetWindowText(text);
+
+       return err;
+}
+
+
+// ------------------------------------------------------
+// MatchManufacturer
+//
+// This function is responsible for finding a manufacturer
+// object from a string name.  It does a CString::Find, which
+// is like strstr, so it doesn't have to do an exact match
+//
+// If it can't find a match, NULL is returned
+// ------------------------------------------------------
+
+Manufacturer*
+CThirdPage::MatchManufacturer( Manufacturers & manufacturers, const CString & name)
+{
+       Manufacturers::iterator iter;
+
+       for (iter = manufacturers.begin(); iter != manufacturers.end(); iter++)
+       {
+               //
+               // we're going to convert all the manufacturer names to lower case,
+               // so we match the name passed in.
+               //
+               CString lower = iter->second->name;
+               lower.MakeLower();
+
+               //
+               // now try and find the lowered string in the name passed in.
+               // 
+               if (name.Find(lower) == 0)
+               {
+                       return iter->second;
+               }
+       }
+
+       return NULL;
+}
+
+
+// -------------------------------------------------------
+// MatchModel
+//
+// This function is responsible for matching a model from
+// a name.  It does a CString::Find(), which works like strstr,
+// so it doesn't rely on doing an exact string match.
+//
+
+Model*
+CThirdPage::MatchModel(Manufacturer * manufacturer, const CString & name)
+{
+       Models::iterator iter;
+
+       iter = manufacturer->models.begin();
+
+       for (iter = manufacturer->models.begin(); iter != manufacturer->models.end(); iter++)
+       {
+               Model * model = *iter;
+
+               //
+               // convert the model name to lower case
+               //
+               CString lowered = model->name;
+               lowered.MakeLower();
+
+               if (lowered.Find( name ) != -1)
+               {
+                       return model;
+               }
+       }
+
+       return NULL;
+}
+
+
+
+// -----------------------------------------------------------
+// OnInitPage
+//
+// This function is responsible for doing initialization that
+// only occurs once during a run of the wizard
+//
+
+OSStatus CThirdPage::OnInitPage()
+{
+       static const int                bufferSize      = 32768;
+       TCHAR                                   windowsDirectory[bufferSize];
+       CString                                 header;
+       CString                                 ntPrint;
+       OSStatus                                err;
+       BOOL                                    ok;
+       
+
+       //
+       // The CTreeCtrl widget automatically sends a selection changed
+       // message which initially we want to ignore, because the user
+       // hasn't selected anything
+       //
+       // this flag gets reset in the message handler.  Every subsequent
+       // message gets handled.
+       //
+
+       //
+       // we have to make sure that we only do this once.  Typically, 
+       // we would do this in something like OnInitDialog, but we don't
+       // have this in Wizards, because the window is a PropertySheet.
+       // We're considered fully initialized when we receive the first
+       // selection notice
+       //
+       header.LoadString(IDS_MANUFACTURER_HEADING);
+       m_manufacturerListCtrl.InsertColumn(0, header, LVCFMT_LEFT, 138);
+       m_manufacturerSelected = NULL;
+
+       header.LoadString(IDS_MODEL_HEADING);
+       m_modelListCtrl.InsertColumn(0, header, LVCFMT_LEFT, 247);
+       m_modelSelected = NULL;
+
+       //
+       // load printers from ntprint.inf
+       //
+       ok = GetWindowsDirectory( windowsDirectory, bufferSize );
+       err = translate_errno( ok, errno_compat(), kUnknownErr );
+       require_noerr( err, exit );
+       ntPrint.Format(L"%s\\%s", windowsDirectory, kNTPrintFile);
+       err = LoadPrintDriverDefsFromFile( m_manufacturers, ntPrint, false );
+       require_noerr(err, exit);
+
+       //
+       // load printer drivers that have been installed on this machine
+       //
+       err = LoadPrintDriverDefs( m_manufacturers );
+       require_noerr(err, exit);
+
+exit:
+
+       return (err);
+}
+
+
+void CThirdPage::DoDataExchange(CDataExchange* pDX)
+{
+       CPropertyPage::DoDataExchange(pDX);
+       DDX_Control(pDX, IDC_PRINTER_MANUFACTURER, m_manufacturerListCtrl);
+       DDX_Control(pDX, IDC_PRINTER_MODEL, m_modelListCtrl);
+       DDX_Control(pDX, IDC_PRINTER_NAME, m_printerName);
+       DDX_Control(pDX, IDC_DEFAULT_PRINTER, m_defaultPrinterCtrl);
+       DDX_Control(pDX, IDC_PRINTER_SELECTION_TEXT, m_printerSelectionText);
+}
+
+
+// ----------------------------------------------------------
+// OnSetActive
+//
+// This function is called by MFC after the window has been
+// activated.
+//
+
+BOOL
+CThirdPage::OnSetActive()
+{
+       CPrinterSetupWizardSheet        *       psheet;
+       Printer                                         *       printer;
+
+       psheet = reinterpret_cast<CPrinterSetupWizardSheet*>(GetParent());
+       require_quiet( psheet, exit );
+   
+       if ((m_manufacturerListCtrl.GetFirstSelectedItemPosition() != NULL) &&
+           (m_modelListCtrl.GetFirstSelectedItemPosition() != NULL))
+       {
+               psheet->SetWizardButtons( PSWIZB_BACK|PSWIZB_NEXT );
+       }
+       else
+       {
+               psheet->SetWizardButtons( PSWIZB_BACK );
+       }
+
+       printer = psheet->GetSelectedPrinter();
+       require_quiet( printer, exit );
+
+       //
+       // call OnInitPage once
+       //
+       if (!m_initialized)
+       {
+               OnInitPage();
+               m_initialized = true;
+       }
+
+       //
+       // update the UI with the printer name
+       //
+       m_printerName.SetWindowText(printer->displayName);
+
+       //
+       // populate the list controls with the manufacturers and models
+       // from ntprint.inf
+       //
+       PopulateUI( m_manufacturers );
+
+       //
+       // and try and match the printer
+       //
+       MatchPrinter( m_manufacturers, printer );
+
+exit:
+
+       return CPropertyPage::OnSetActive();
+}
+
+
+// -------------------------------------------------------
+// PopulateUI
+//
+// This function is called to populate the list of manufacturers
+//
+OSStatus
+CThirdPage::PopulateUI(Manufacturers & manufacturers)
+{
+       Manufacturers::iterator iter;
+       
+       m_manufacturerListCtrl.DeleteAllItems();
+
+       for (iter = manufacturers.begin(); iter != manufacturers.end(); iter++)
+       {
+               int nIndex;
+
+               Manufacturer * manufacturer = iter->second;
+
+               nIndex = m_manufacturerListCtrl.InsertItem(0, manufacturer->name);
+
+               m_manufacturerListCtrl.SetItemData(nIndex, (DWORD_PTR) manufacturer);
+       }
+
+       return 0;
+}
+
+
+BEGIN_MESSAGE_MAP(CThirdPage, CPropertyPage)
+       ON_NOTIFY(LVN_ITEMCHANGED, IDC_PRINTER_MANUFACTURER, OnLvnItemchangedManufacturer)
+       ON_NOTIFY(LVN_ITEMCHANGED, IDC_PRINTER_MODEL, OnLvnItemchangedPrinterModel)
+       ON_BN_CLICKED(IDC_DEFAULT_PRINTER, OnBnClickedDefaultPrinter)
+       ON_BN_CLICKED(IDC_HAVE_DISK, OnBnClickedHaveDisk)
+END_MESSAGE_MAP()
+
+
+// CThirdPage message handlers
+void CThirdPage::OnLvnItemchangedManufacturer(NMHDR *pNMHDR, LRESULT *pResult)
+{
+       LPNMLISTVIEW pNMLV = reinterpret_cast<LPNMLISTVIEW>(pNMHDR);
+
+       POSITION p = m_manufacturerListCtrl.GetFirstSelectedItemPosition();
+       int nSelected = m_manufacturerListCtrl.GetNextSelectedItem(p);
+
+       if (nSelected != -1)
+       {
+               m_manufacturerSelected = (Manufacturer*) m_manufacturerListCtrl.GetItemData(nSelected);
+
+               m_modelListCtrl.SetRedraw(FALSE);
+               
+               m_modelListCtrl.DeleteAllItems();
+               m_modelSelected = NULL;
+
+               Models::iterator iter;
+
+               for (iter = m_manufacturerSelected->models.begin(); iter != m_manufacturerSelected->models.end(); iter++)
+               {
+                       Model * model = *iter;
+
+                       int nItem = m_modelListCtrl.InsertItem(0, model->name);
+
+                       m_modelListCtrl.SetItemData(nItem, (DWORD_PTR) model);
+               }
+
+               m_modelListCtrl.SetRedraw(TRUE);
+       }
+
+       *pResult = 0;
+}
+
+void CThirdPage::OnLvnItemchangedPrinterModel(NMHDR *pNMHDR, LRESULT *pResult)
+{
+       LPNMLISTVIEW                                    pNMLV = reinterpret_cast<LPNMLISTVIEW>(pNMHDR);
+       
+       CPrinterSetupWizardSheet        *       psheet;
+       Printer                                         *       printer;
+
+       psheet = reinterpret_cast<CPrinterSetupWizardSheet*>(GetParent());
+       require_quiet( psheet, exit );
+
+       printer = psheet->GetSelectedPrinter();
+       require_quiet( printer, exit );
+
+       check ( m_manufacturerSelected );
+
+       POSITION p = m_modelListCtrl.GetFirstSelectedItemPosition();
+       int nSelected = m_modelListCtrl.GetNextSelectedItem(p);
+
+       if (nSelected != -1)
+       {
+               m_modelSelected = (Model*) m_modelListCtrl.GetItemData(nSelected);
+
+               CopyPrinterSettings( printer, m_manufacturerSelected, m_modelSelected );
+
+               psheet->SetWizardButtons(PSWIZB_BACK|PSWIZB_NEXT);
+       }
+       else
+       {
+               psheet->SetWizardButtons(PSWIZB_BACK);
+       }
+
+exit:
+
+       *pResult = 0;
+}
+
+
+void CThirdPage::OnBnClickedDefaultPrinter()
+{
+       CPrinterSetupWizardSheet        *       psheet;
+       Printer                                         *       printer;
+
+       psheet = reinterpret_cast<CPrinterSetupWizardSheet*>(GetParent());
+       require_quiet( psheet, exit );
+
+       printer = psheet->GetSelectedPrinter();
+       require_quiet( printer, exit );
+
+       printer->deflt = m_defaultPrinterCtrl.GetState() ? true : false;
+
+exit:
+
+       return;
+}
+
+void CThirdPage::OnBnClickedHaveDisk()
+{
+       CPrinterSetupWizardSheet        *       psheet;
+       Printer                                         *       printer;
+
+       CFileDialog dlg(TRUE, NULL, NULL, OFN_HIDEREADONLY|OFN_FILEMUSTEXIST, L"Setup Information (*.inf)|*.inf||", this);
+
+       psheet = reinterpret_cast<CPrinterSetupWizardSheet*>(GetParent());
+       require_quiet( psheet, exit );
+
+       printer = psheet->GetSelectedPrinter();
+       require_quiet( printer, exit );
+       
+       if ( dlg.DoModal() == IDOK )
+       {
+               Manufacturers   manufacturers;
+               CString                 filename = dlg.GetPathName();
+
+               LoadPrintDriverDefsFromFile( manufacturers, filename, true );
+   
+               PopulateUI( manufacturers );
+
+               MatchPrinter( manufacturers, printer );
+       }
+
+exit:
+
+       return;
+}