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