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