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