1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3  * License, v. 2.0. If a copy of the MPL was not distributed with this
4  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 
6 /* -------------------------------------------------------------------
7 To Build This:
8 
9   You need to add this to the the makefile.win in mozilla/dom/base:
10 
11 	.\$(OBJDIR)\nsFlyOwnPrintDialog.obj	\
12 
13 
14   And this to the makefile.win in mozilla/content/build:
15 
16 WIN_LIBS=                                       \
17         winspool.lib                           \
18         comctl32.lib                           \
19         comdlg32.lib
20 
21 ---------------------------------------------------------------------- */
22 
23 #include "plstr.h"
24 #include <windows.h>
25 #include <tchar.h>
26 
27 #include <unknwn.h>
28 #include <commdlg.h>
29 
30 #include "nsIWebBrowserPrint.h"
31 #include "nsString.h"
32 #include "nsIServiceManager.h"
33 #include "nsReadableUtils.h"
34 #include "nsIPrintSettings.h"
35 #include "nsIPrintSettingsWin.h"
36 #include "nsIPrinterEnumerator.h"
37 
38 #include "nsRect.h"
39 
40 #include "nsIPrefService.h"
41 #include "nsIPrefBranch.h"
42 
43 #include "nsCRT.h"
44 #include "prenv.h" /* for PR_GetEnv */
45 
46 #include <windows.h>
47 #include <winspool.h>
48 
49 // For Localization
50 #include "nsIStringBundle.h"
51 
52 // For NS_CopyUnicodeToNative
53 #include "nsNativeCharsetUtils.h"
54 
55 // This is for extending the dialog
56 #include <dlgs.h>
57 
58 #include "nsWindowsHelpers.h"
59 #include "WinUtils.h"
60 
61 // Default labels for the radio buttons
62 static const char* kAsLaidOutOnScreenStr = "As &laid out on the screen";
63 static const char* kTheSelectedFrameStr  = "The selected &frame";
64 static const char* kEachFrameSeparately  = "&Each frame separately";
65 
66 
67 //-----------------------------------------------
68 // Global Data
69 //-----------------------------------------------
70 // Identifies which new radio btn was cliked on
71 static UINT gFrameSelectedRadioBtn = 0;
72 
73 // Indicates whether the native print dialog was successfully extended
74 static bool gDialogWasExtended     = false;
75 
76 #define PRINTDLG_PROPERTIES "chrome://global/locale/printdialog.properties"
77 
78 static HWND gParentWnd = nullptr;
79 
80 //----------------------------------------------------------------------------------
81 // Return localized bundle for resource strings
82 static nsresult
GetLocalizedBundle(const char * aPropFileName,nsIStringBundle ** aStrBundle)83 GetLocalizedBundle(const char * aPropFileName, nsIStringBundle** aStrBundle)
84 {
85   NS_ENSURE_ARG_POINTER(aPropFileName);
86   NS_ENSURE_ARG_POINTER(aStrBundle);
87 
88   nsresult rv;
89   nsCOMPtr<nsIStringBundle> bundle;
90 
91 
92   // Create bundle
93   nsCOMPtr<nsIStringBundleService> stringService =
94     do_GetService(NS_STRINGBUNDLE_CONTRACTID, &rv);
95   if (NS_SUCCEEDED(rv) && stringService) {
96     rv = stringService->CreateBundle(aPropFileName, aStrBundle);
97   }
98 
99   return rv;
100 }
101 
102 //--------------------------------------------------------
103 // Return localized string
104 static nsresult
GetLocalizedString(nsIStringBundle * aStrBundle,const char * aKey,nsString & oVal)105 GetLocalizedString(nsIStringBundle* aStrBundle, const char* aKey, nsString& oVal)
106 {
107   NS_ENSURE_ARG_POINTER(aStrBundle);
108   NS_ENSURE_ARG_POINTER(aKey);
109 
110   // Determine default label from string bundle
111   nsXPIDLString valUni;
112   nsAutoString key;
113   key.AssignWithConversion(aKey);
114   nsresult rv = aStrBundle->GetStringFromName(key.get(), getter_Copies(valUni));
115   if (NS_SUCCEEDED(rv) && valUni) {
116     oVal.Assign(valUni);
117   } else {
118     oVal.Truncate();
119   }
120   return rv;
121 }
122 
123 //--------------------------------------------------------
124 // Set a multi-byte string in the control
SetTextOnWnd(HWND aControl,const nsString & aStr)125 static void SetTextOnWnd(HWND aControl, const nsString& aStr)
126 {
127   nsAutoCString text;
128   if (NS_SUCCEEDED(NS_CopyUnicodeToNative(aStr, text))) {
129     ::SetWindowText(aControl, text.get());
130   }
131 }
132 
133 //--------------------------------------------------------
134 // Will get the control and localized string by "key"
SetText(HWND aParent,UINT aId,nsIStringBundle * aStrBundle,const char * aKey)135 static void SetText(HWND             aParent,
136                     UINT             aId,
137                     nsIStringBundle* aStrBundle,
138                     const char*      aKey)
139 {
140   HWND wnd = GetDlgItem (aParent, aId);
141   if (!wnd) {
142     return;
143   }
144   nsAutoString str;
145   nsresult rv = GetLocalizedString(aStrBundle, aKey, str);
146   if (NS_SUCCEEDED(rv)) {
147     SetTextOnWnd(wnd, str);
148   }
149 }
150 
151 //--------------------------------------------------------
SetRadio(HWND aParent,UINT aId,bool aIsSet,bool isEnabled=true)152 static void SetRadio(HWND         aParent,
153                      UINT         aId,
154                      bool         aIsSet,
155                      bool         isEnabled = true)
156 {
157   HWND wnd = ::GetDlgItem (aParent, aId);
158   if (!wnd) {
159     return;
160   }
161   if (!isEnabled) {
162     ::EnableWindow(wnd, FALSE);
163     return;
164   }
165   ::EnableWindow(wnd, TRUE);
166   ::SendMessage(wnd, BM_SETCHECK, (WPARAM)aIsSet, (LPARAM)0);
167 }
168 
169 //--------------------------------------------------------
SetRadioOfGroup(HWND aDlg,int aRadId)170 static void SetRadioOfGroup(HWND aDlg, int aRadId)
171 {
172   int radioIds[] = {rad4, rad5, rad6};
173   int numRads = 3;
174 
175   for (int i=0;i<numRads;i++) {
176     HWND radWnd = ::GetDlgItem(aDlg, radioIds[i]);
177     if (radWnd != nullptr) {
178       ::SendMessage(radWnd, BM_SETCHECK, (WPARAM)(radioIds[i] == aRadId), (LPARAM)0);
179     }
180   }
181 }
182 
183 //--------------------------------------------------------
184 typedef struct {
185   const char * mKeyStr;
186   long   mKeyId;
187 } PropKeyInfo;
188 
189 // These are the control ids used in the dialog and
190 // defined by MS-Windows in commdlg.h
191 static PropKeyInfo gAllPropKeys[] = {
192     {"printFramesTitleWindows", grp3},
193     {"asLaidOutWindows", rad4},
194     {"selectedFrameWindows", rad5},
195     {"separateFramesWindows", rad6},
196     {nullptr, 0}};
197 
198 //--------------------------------------------------------
199 //--------------------------------------------------------
200 //--------------------------------------------------------
201 //--------------------------------------------------------
202 // Get the absolute coords of the child windows relative
203 // to its parent window
GetLocalRect(HWND aWnd,RECT & aRect,HWND aParent)204 static void GetLocalRect(HWND aWnd, RECT& aRect, HWND aParent)
205 {
206   ::GetWindowRect(aWnd, &aRect);
207 
208   // MapWindowPoints converts screen coordinates to client coordinates.
209   // It works correctly in both left-to-right and right-to-left windows.
210   ::MapWindowPoints(nullptr, aParent, (LPPOINT)&aRect, 2);
211 }
212 
213 //--------------------------------------------------------
214 // Show or Hide the control
Show(HWND aWnd,bool bState)215 static void Show(HWND aWnd, bool bState)
216 {
217   if (aWnd) {
218     ::ShowWindow(aWnd, bState?SW_SHOW:SW_HIDE);
219   }
220 }
221 
222 //--------------------------------------------------------
223 // Create a child window "control"
CreateControl(LPCTSTR aType,DWORD aStyle,HINSTANCE aHInst,HWND aHdlg,int aId,const nsAString & aStr,const nsIntRect & aRect)224 static HWND CreateControl(LPCTSTR          aType,
225                           DWORD            aStyle,
226                           HINSTANCE        aHInst,
227                           HWND             aHdlg,
228                           int              aId,
229                           const nsAString& aStr,
230                           const nsIntRect& aRect)
231 {
232   nsAutoCString str;
233   if (NS_FAILED(NS_CopyUnicodeToNative(aStr, str)))
234     return nullptr;
235 
236   HWND hWnd = ::CreateWindow (aType, str.get(),
237                               WS_CHILD | WS_CLIPSIBLINGS | WS_VISIBLE | aStyle,
238                               aRect.x, aRect.y, aRect.width, aRect.height,
239                               (HWND)aHdlg, (HMENU)(intptr_t)aId,
240                               aHInst, nullptr);
241   if (hWnd == nullptr) return nullptr;
242 
243   // get the native font for the dialog and
244   // set it into the new control
245   HFONT hFont = (HFONT)::SendMessage(aHdlg, WM_GETFONT, (WPARAM)0, (LPARAM)0);
246   if (hFont != nullptr) {
247     ::SendMessage(hWnd, WM_SETFONT, (WPARAM) hFont, (LPARAM)0);
248   }
249   return hWnd;
250 }
251 
252 //--------------------------------------------------------
253 // Create a Radio Button
CreateRadioBtn(HINSTANCE aHInst,HWND aHdlg,int aId,const char * aStr,const nsIntRect & aRect)254 static HWND CreateRadioBtn(HINSTANCE        aHInst,
255                            HWND             aHdlg,
256                            int              aId,
257                            const char*      aStr,
258                            const nsIntRect& aRect)
259 {
260   nsString cStr;
261   cStr.AssignWithConversion(aStr);
262   return CreateControl("BUTTON", BS_RADIOBUTTON, aHInst, aHdlg, aId, cStr, aRect);
263 }
264 
265 //--------------------------------------------------------
266 // Create a Group Box
CreateGroupBox(HINSTANCE aHInst,HWND aHdlg,int aId,const nsAString & aStr,const nsIntRect & aRect)267 static HWND CreateGroupBox(HINSTANCE        aHInst,
268                            HWND             aHdlg,
269                            int              aId,
270                            const nsAString& aStr,
271                            const nsIntRect& aRect)
272 {
273   return CreateControl("BUTTON", BS_GROUPBOX, aHInst, aHdlg, aId, aStr, aRect);
274 }
275 
276 //--------------------------------------------------------
277 // Localizes and initializes the radio buttons and group
InitializeExtendedDialog(HWND hdlg,int16_t aHowToEnableFrameUI)278 static void InitializeExtendedDialog(HWND hdlg, int16_t aHowToEnableFrameUI)
279 {
280   MOZ_ASSERT(aHowToEnableFrameUI != nsIPrintSettings::kFrameEnableNone,
281              "should not be called");
282 
283   // Localize the new controls in the print dialog
284   nsCOMPtr<nsIStringBundle> strBundle;
285   if (NS_SUCCEEDED(GetLocalizedBundle(PRINTDLG_PROPERTIES, getter_AddRefs(strBundle)))) {
286     int32_t i = 0;
287     while (gAllPropKeys[i].mKeyStr != nullptr) {
288       SetText(hdlg, gAllPropKeys[i].mKeyId, strBundle, gAllPropKeys[i].mKeyStr);
289       i++;
290     }
291   }
292 
293   // Set up radio buttons
294   if (aHowToEnableFrameUI == nsIPrintSettings::kFrameEnableAll) {
295     SetRadio(hdlg, rad4, false);
296     SetRadio(hdlg, rad5, true);
297     SetRadio(hdlg, rad6, false);
298     // set default so user doesn't have to actually press on it
299     gFrameSelectedRadioBtn = rad5;
300 
301   } else { // nsIPrintSettings::kFrameEnableAsIsAndEach
302     SetRadio(hdlg, rad4, false);
303     SetRadio(hdlg, rad5, false, false);
304     SetRadio(hdlg, rad6, true);
305     // set default so user doesn't have to actually press on it
306     gFrameSelectedRadioBtn = rad6;
307   }
308 }
309 
310 
311 //--------------------------------------------------------
312 // Special Hook Procedure for handling the print dialog messages
PrintHookProc(HWND hdlg,UINT uiMsg,WPARAM wParam,LPARAM lParam)313 static UINT CALLBACK PrintHookProc(HWND hdlg, UINT uiMsg, WPARAM wParam, LPARAM lParam)
314 {
315 
316   if (uiMsg == WM_COMMAND) {
317     UINT id = LOWORD(wParam);
318     if (id == rad4 || id == rad5 || id == rad6) {
319       gFrameSelectedRadioBtn = id;
320       SetRadioOfGroup(hdlg, id);
321     }
322 
323   } else if (uiMsg == WM_INITDIALOG) {
324     PRINTDLG * printDlg = (PRINTDLG *)lParam;
325     if (printDlg == nullptr) return 0L;
326 
327     int16_t howToEnableFrameUI = (int16_t)printDlg->lCustData;
328     // don't add frame options if they would be disabled anyway
329     // because there are no frames
330     if (howToEnableFrameUI == nsIPrintSettings::kFrameEnableNone)
331       return TRUE;
332 
333     HINSTANCE hInst = (HINSTANCE)::GetWindowLongPtr(hdlg, GWLP_HINSTANCE);
334     if (hInst == nullptr) return 0L;
335 
336     // Start by getting the local rects of several of the controls
337     // so we can calculate where the new controls are
338     HWND wnd = ::GetDlgItem(hdlg, grp1);
339     if (wnd == nullptr) return 0L;
340     RECT dlgRect;
341     GetLocalRect(wnd, dlgRect, hdlg);
342 
343     wnd = ::GetDlgItem(hdlg, rad1); // this is the top control "All"
344     if (wnd == nullptr) return 0L;
345     RECT rad1Rect;
346     GetLocalRect(wnd, rad1Rect, hdlg);
347 
348     wnd = ::GetDlgItem(hdlg, rad2); // this is the bottom control "Selection"
349     if (wnd == nullptr) return 0L;
350     RECT rad2Rect;
351     GetLocalRect(wnd, rad2Rect, hdlg);
352 
353     wnd = ::GetDlgItem(hdlg, rad3); // this is the middle control "Pages"
354     if (wnd == nullptr) return 0L;
355     RECT rad3Rect;
356     GetLocalRect(wnd, rad3Rect, hdlg);
357 
358     HWND okWnd = ::GetDlgItem(hdlg, IDOK);
359     if (okWnd == nullptr) return 0L;
360     RECT okRect;
361     GetLocalRect(okWnd, okRect, hdlg);
362 
363     wnd = ::GetDlgItem(hdlg, grp4); // this is the "Print range" groupbox
364     if (wnd == nullptr) return 0L;
365     RECT prtRect;
366     GetLocalRect(wnd, prtRect, hdlg);
367 
368 
369     // calculate various different "gaps" for layout purposes
370 
371     int rbGap     = rad3Rect.top - rad1Rect.bottom;     // gap between radiobtns
372     int grpBotGap = dlgRect.bottom - rad2Rect.bottom;   // gap from bottom rb to bottom of grpbox
373     int grpGap    = dlgRect.top - prtRect.bottom ;      // gap between group boxes
374     int top       = dlgRect.bottom + grpGap;
375     int radHgt    = rad1Rect.bottom - rad1Rect.top + 1; // top of new group box
376     int y         = top+(rad1Rect.top-dlgRect.top);     // starting pos of first radio
377     int rbWidth   = dlgRect.right - rad1Rect.left - 5;  // measure from rb left to the edge of the groupbox
378                                                         // (5 is arbitrary)
379     nsIntRect rect;
380 
381     // Create and position the radio buttons
382     //
383     // If any one control cannot be created then
384     // hide the others and bail out
385     //
386     rect.SetRect(rad1Rect.left, y, rbWidth,radHgt);
387     HWND rad4Wnd = CreateRadioBtn(hInst, hdlg, rad4, kAsLaidOutOnScreenStr, rect);
388     if (rad4Wnd == nullptr) return 0L;
389     y += radHgt + rbGap;
390 
391     rect.SetRect(rad1Rect.left, y, rbWidth, radHgt);
392     HWND rad5Wnd = CreateRadioBtn(hInst, hdlg, rad5, kTheSelectedFrameStr, rect);
393     if (rad5Wnd == nullptr) {
394       Show(rad4Wnd, FALSE); // hide
395       return 0L;
396     }
397     y += radHgt + rbGap;
398 
399     rect.SetRect(rad1Rect.left, y, rbWidth, radHgt);
400     HWND rad6Wnd = CreateRadioBtn(hInst, hdlg, rad6, kEachFrameSeparately, rect);
401     if (rad6Wnd == nullptr) {
402       Show(rad4Wnd, FALSE); // hide
403       Show(rad5Wnd, FALSE); // hide
404       return 0L;
405     }
406     y += radHgt + grpBotGap;
407 
408     // Create and position the group box
409     rect.SetRect (dlgRect.left, top, dlgRect.right-dlgRect.left+1, y-top+1);
410     HWND grpBoxWnd = CreateGroupBox(hInst, hdlg, grp3, NS_LITERAL_STRING("Print Frame"), rect);
411     if (grpBoxWnd == nullptr) {
412       Show(rad4Wnd, FALSE); // hide
413       Show(rad5Wnd, FALSE); // hide
414       Show(rad6Wnd, FALSE); // hide
415       return 0L;
416     }
417 
418     // Here we figure out the old height of the dlg
419     // then figure its gap from the old grpbx to the bottom
420     // then size the dlg
421     RECT pr, cr;
422     ::GetWindowRect(hdlg, &pr);
423     ::GetClientRect(hdlg, &cr);
424 
425     int dlgHgt = (cr.bottom - cr.top) + 1;
426     int bottomGap = dlgHgt - okRect.bottom;
427     pr.bottom += (dlgRect.bottom-dlgRect.top) + grpGap + 1 - (dlgHgt-dlgRect.bottom) + bottomGap;
428 
429     ::SetWindowPos(hdlg, nullptr, pr.left, pr.top, pr.right-pr.left+1, pr.bottom-pr.top+1,
430                    SWP_NOMOVE|SWP_NOREDRAW|SWP_NOZORDER);
431 
432     // figure out the new height of the dialog
433     ::GetClientRect(hdlg, &cr);
434     dlgHgt = (cr.bottom - cr.top) + 1;
435 
436     // Reposition the OK and Cancel btns
437     int okHgt = okRect.bottom - okRect.top + 1;
438     ::SetWindowPos(okWnd, nullptr, okRect.left, dlgHgt-bottomGap-okHgt, 0, 0,
439                    SWP_NOSIZE|SWP_NOREDRAW|SWP_NOZORDER);
440 
441     HWND cancelWnd = ::GetDlgItem(hdlg, IDCANCEL);
442     if (cancelWnd == nullptr) return 0L;
443 
444     RECT cancelRect;
445     GetLocalRect(cancelWnd, cancelRect, hdlg);
446     int cancelHgt = cancelRect.bottom - cancelRect.top + 1;
447     ::SetWindowPos(cancelWnd, nullptr, cancelRect.left, dlgHgt-bottomGap-cancelHgt, 0, 0,
448                    SWP_NOSIZE|SWP_NOREDRAW|SWP_NOZORDER);
449 
450     // localize and initialize the groupbox and radiobuttons
451     InitializeExtendedDialog(hdlg, howToEnableFrameUI);
452 
453     // Looks like we were able to extend the dialog
454     gDialogWasExtended = true;
455     return TRUE;
456   }
457   return 0L;
458 }
459 
460 //----------------------------------------------------------------------------------
461 // Returns a Global Moveable Memory Handle to a DevMode
462 // from the Printer by the name of aPrintName
463 //
464 // NOTE:
465 //   This function assumes that aPrintName has already been converted from
466 //   unicode
467 //
468 static nsReturnRef<nsHGLOBAL>
CreateGlobalDevModeAndInit(const nsXPIDLString & aPrintName,nsIPrintSettings * aPS)469 CreateGlobalDevModeAndInit(const nsXPIDLString& aPrintName,
470                            nsIPrintSettings* aPS)
471 {
472   nsHPRINTER hPrinter = nullptr;
473   // const cast kludge for silly Win32 api's
474   LPWSTR printName = const_cast<wchar_t*>(static_cast<const wchar_t*>(aPrintName.get()));
475   BOOL status = ::OpenPrinterW(printName, &hPrinter, nullptr);
476   if (!status) {
477     return nsReturnRef<nsHGLOBAL>();
478   }
479 
480   // Make sure hPrinter is closed on all paths
481   nsAutoPrinter autoPrinter(hPrinter);
482 
483   // Get the buffer size
484   LONG needed = ::DocumentPropertiesW(gParentWnd, hPrinter, printName, nullptr,
485                                       nullptr, 0);
486   if (needed < 0) {
487     return nsReturnRef<nsHGLOBAL>();
488   }
489 
490   // Allocate a buffer of the correct size.
491   nsAutoDevMode newDevMode((LPDEVMODEW)::HeapAlloc(::GetProcessHeap(), HEAP_ZERO_MEMORY,
492                                                    needed));
493   if (!newDevMode) {
494     return nsReturnRef<nsHGLOBAL>();
495   }
496 
497   nsHGLOBAL hDevMode = ::GlobalAlloc(GHND, needed);
498   nsAutoGlobalMem globalDevMode(hDevMode);
499   if (!hDevMode) {
500     return nsReturnRef<nsHGLOBAL>();
501   }
502 
503   LONG ret = ::DocumentPropertiesW(gParentWnd, hPrinter, printName, newDevMode,
504                                    nullptr, DM_OUT_BUFFER);
505   if (ret != IDOK) {
506     return nsReturnRef<nsHGLOBAL>();
507   }
508 
509   // Lock memory and copy contents from DEVMODE (current printer)
510   // to Global Memory DEVMODE
511   LPDEVMODEW devMode = (DEVMODEW *)::GlobalLock(hDevMode);
512   if (!devMode) {
513     return nsReturnRef<nsHGLOBAL>();
514   }
515 
516   memcpy(devMode, newDevMode.get(), needed);
517   // Initialize values from the PrintSettings
518   nsCOMPtr<nsIPrintSettingsWin> psWin = do_QueryInterface(aPS);
519   MOZ_ASSERT(psWin);
520   psWin->CopyToNative(devMode);
521 
522   // Sets back the changes we made to the DevMode into the Printer Driver
523   ret = ::DocumentPropertiesW(gParentWnd, hPrinter, printName, devMode, devMode,
524                               DM_IN_BUFFER | DM_OUT_BUFFER);
525   if (ret != IDOK) {
526     ::GlobalUnlock(hDevMode);
527     return nsReturnRef<nsHGLOBAL>();
528   }
529 
530   ::GlobalUnlock(hDevMode);
531 
532   return globalDevMode.out();
533 }
534 
535 //------------------------------------------------------------------
536 // helper
GetDefaultPrinterNameFromGlobalPrinters(nsXPIDLString & printerName)537 static void GetDefaultPrinterNameFromGlobalPrinters(nsXPIDLString &printerName)
538 {
539   nsCOMPtr<nsIPrinterEnumerator> prtEnum = do_GetService("@mozilla.org/gfx/printerenumerator;1");
540   if (prtEnum) {
541     prtEnum->GetDefaultPrinterName(getter_Copies(printerName));
542   }
543 }
544 
545 // Determine whether we have a completely native dialog
546 // or whether we cshould extend it
ShouldExtendPrintDialog()547 static bool ShouldExtendPrintDialog()
548 {
549   nsresult rv;
550   nsCOMPtr<nsIPrefService> prefs =
551     do_GetService(NS_PREFSERVICE_CONTRACTID, &rv);
552   NS_ENSURE_SUCCESS(rv, true);
553   nsCOMPtr<nsIPrefBranch> prefBranch;
554   rv = prefs->GetBranch(nullptr, getter_AddRefs(prefBranch));
555   NS_ENSURE_SUCCESS(rv, true);
556 
557   bool result;
558   rv = prefBranch->GetBoolPref("print.extend_native_print_dialog", &result);
559   NS_ENSURE_SUCCESS(rv, true);
560   return result;
561 }
562 
563 //------------------------------------------------------------------
564 // Displays the native Print Dialog
565 static nsresult
ShowNativePrintDialog(HWND aHWnd,nsIPrintSettings * aPrintSettings)566 ShowNativePrintDialog(HWND              aHWnd,
567                       nsIPrintSettings* aPrintSettings)
568 {
569   //NS_ENSURE_ARG_POINTER(aHWnd);
570   NS_ENSURE_ARG_POINTER(aPrintSettings);
571 
572   gDialogWasExtended  = false;
573 
574   // Get the Print Name to be used
575   nsXPIDLString printerName;
576   aPrintSettings->GetPrinterName(getter_Copies(printerName));
577 
578   // If there is no name then use the default printer
579   if (printerName.IsEmpty()) {
580     GetDefaultPrinterNameFromGlobalPrinters(printerName);
581   } else {
582     HANDLE hPrinter = nullptr;
583     if(!::OpenPrinterW(const_cast<wchar_t*>(static_cast<const wchar_t*>(printerName.get())),
584                        &hPrinter, nullptr)) {
585       // If the last used printer is not found, we should use default printer.
586       GetDefaultPrinterNameFromGlobalPrinters(printerName);
587     } else {
588       ::ClosePrinter(hPrinter);
589     }
590   }
591 
592   // Now create a DEVNAMES struct so the the dialog is initialized correctly.
593 
594   uint32_t len = printerName.Length();
595   nsHGLOBAL hDevNames = ::GlobalAlloc(GHND, sizeof(wchar_t) * (len + 1)
596                                       + sizeof(DEVNAMES));
597   nsAutoGlobalMem autoDevNames(hDevNames);
598   if (!hDevNames) {
599     return NS_ERROR_OUT_OF_MEMORY;
600   }
601 
602   DEVNAMES* pDevNames = (DEVNAMES*)::GlobalLock(hDevNames);
603   if (!pDevNames) {
604     return NS_ERROR_FAILURE;
605   }
606   pDevNames->wDriverOffset = sizeof(DEVNAMES)/sizeof(wchar_t);
607   pDevNames->wDeviceOffset = sizeof(DEVNAMES)/sizeof(wchar_t);
608   pDevNames->wOutputOffset = sizeof(DEVNAMES)/sizeof(wchar_t)+len;
609   pDevNames->wDefault      = 0;
610 
611   memcpy(pDevNames+1, printerName, (len + 1) * sizeof(wchar_t));
612   ::GlobalUnlock(hDevNames);
613 
614   // Create a Moveable Memory Object that holds a new DevMode
615   // from the Printer Name
616   // The PRINTDLG.hDevMode requires that it be a moveable memory object
617   // NOTE: autoDevMode is automatically freed when any error occurred
618   nsAutoGlobalMem autoDevMode(CreateGlobalDevModeAndInit(printerName, aPrintSettings));
619 
620   // Prepare to Display the Print Dialog
621   PRINTDLGW  prntdlg;
622   memset(&prntdlg, 0, sizeof(PRINTDLGW));
623 
624   prntdlg.lStructSize = sizeof(prntdlg);
625   prntdlg.hwndOwner   = aHWnd;
626   prntdlg.hDevMode    = autoDevMode.get();
627   prntdlg.hDevNames   = hDevNames;
628   prntdlg.hDC         = nullptr;
629   prntdlg.Flags       = PD_ALLPAGES | PD_RETURNIC |
630                         PD_USEDEVMODECOPIESANDCOLLATE | PD_COLLATE;
631 
632   // if there is a current selection then enable the "Selection" radio button
633   int16_t howToEnableFrameUI = nsIPrintSettings::kFrameEnableNone;
634   bool isOn;
635   aPrintSettings->GetPrintOptions(nsIPrintSettings::kEnableSelectionRB, &isOn);
636   if (!isOn) {
637     prntdlg.Flags |= PD_NOSELECTION;
638   }
639   aPrintSettings->GetHowToEnableFrameUI(&howToEnableFrameUI);
640 
641   int32_t pg = 1;
642   aPrintSettings->GetStartPageRange(&pg);
643   prntdlg.nFromPage           = pg;
644 
645   aPrintSettings->GetEndPageRange(&pg);
646   prntdlg.nToPage             = pg;
647 
648   prntdlg.nMinPage            = 1;
649   prntdlg.nMaxPage            = 0xFFFF;
650   prntdlg.nCopies             = 1;
651   prntdlg.lpfnSetupHook       = nullptr;
652   prntdlg.lpSetupTemplateName = nullptr;
653   prntdlg.hPrintTemplate      = nullptr;
654   prntdlg.hSetupTemplate      = nullptr;
655 
656   prntdlg.hInstance           = nullptr;
657   prntdlg.lpPrintTemplateName = nullptr;
658 
659   if (!ShouldExtendPrintDialog()) {
660     prntdlg.lCustData         = 0;
661     prntdlg.lpfnPrintHook     = nullptr;
662   } else {
663     // Set up print dialog "hook" procedure for extending the dialog
664     prntdlg.lCustData         = (DWORD)howToEnableFrameUI;
665     prntdlg.lpfnPrintHook     = (LPPRINTHOOKPROC)PrintHookProc;
666     prntdlg.Flags            |= PD_ENABLEPRINTHOOK;
667   }
668 
669   BOOL result;
670   {
671     mozilla::widget::WinUtils::AutoSystemDpiAware dpiAwareness;
672     result = ::PrintDlgW(&prntdlg);
673   }
674 
675   if (TRUE == result) {
676     // check to make sure we don't have any nullptr pointers
677     NS_ENSURE_TRUE(aPrintSettings && prntdlg.hDevMode, NS_ERROR_FAILURE);
678 
679     if (prntdlg.hDevNames == nullptr) {
680       return NS_ERROR_FAILURE;
681     }
682     // Lock the deviceNames and check for nullptr
683     DEVNAMES *devnames = (DEVNAMES *)::GlobalLock(prntdlg.hDevNames);
684     if (devnames == nullptr) {
685       return NS_ERROR_FAILURE;
686     }
687 
688     char16_t* device = &(((char16_t *)devnames)[devnames->wDeviceOffset]);
689     char16_t* driver = &(((char16_t *)devnames)[devnames->wDriverOffset]);
690 
691     // Check to see if the "Print To File" control is checked
692     // then take the name from devNames and set it in the PrintSettings
693     //
694     // NOTE:
695     // As per Microsoft SDK documentation the returned value offset from
696     // devnames->wOutputOffset is either "FILE:" or nullptr
697     // if the "Print To File" checkbox is checked it MUST be "FILE:"
698     // We assert as an extra safety check.
699     if (prntdlg.Flags & PD_PRINTTOFILE) {
700       char16ptr_t fileName = &(((wchar_t *)devnames)[devnames->wOutputOffset]);
701       NS_ASSERTION(wcscmp(fileName, L"FILE:") == 0, "FileName must be `FILE:`");
702       aPrintSettings->SetToFileName(fileName);
703       aPrintSettings->SetPrintToFile(true);
704     } else {
705       // clear "print to file" info
706       aPrintSettings->SetPrintToFile(false);
707       aPrintSettings->SetToFileName(nullptr);
708     }
709 
710     nsCOMPtr<nsIPrintSettingsWin> psWin(do_QueryInterface(aPrintSettings));
711     if (!psWin) {
712       return NS_ERROR_FAILURE;
713     }
714 
715     // Setup local Data members
716     psWin->SetDeviceName(device);
717     psWin->SetDriverName(driver);
718 
719 #if defined(DEBUG_rods) || defined(DEBUG_dcone)
720     wprintf(L"printer: driver %s, device %s  flags: %d\n", driver, device, prntdlg.Flags);
721 #endif
722     // fill the print options with the info from the dialog
723 
724     aPrintSettings->SetPrinterName(device);
725 
726     if (prntdlg.Flags & PD_SELECTION) {
727       aPrintSettings->SetPrintRange(nsIPrintSettings::kRangeSelection);
728 
729     } else if (prntdlg.Flags & PD_PAGENUMS) {
730       aPrintSettings->SetPrintRange(nsIPrintSettings::kRangeSpecifiedPageRange);
731       aPrintSettings->SetStartPageRange(prntdlg.nFromPage);
732       aPrintSettings->SetEndPageRange(prntdlg.nToPage);
733 
734     } else { // (prntdlg.Flags & PD_ALLPAGES)
735       aPrintSettings->SetPrintRange(nsIPrintSettings::kRangeAllPages);
736     }
737 
738     if (howToEnableFrameUI != nsIPrintSettings::kFrameEnableNone) {
739       // make sure the dialog got extended
740       if (gDialogWasExtended) {
741         // check to see about the frame radio buttons
742         switch (gFrameSelectedRadioBtn) {
743           case rad4:
744             aPrintSettings->SetPrintFrameType(nsIPrintSettings::kFramesAsIs);
745             break;
746           case rad5:
747             aPrintSettings->SetPrintFrameType(nsIPrintSettings::kSelectedFrame);
748             break;
749           case rad6:
750             aPrintSettings->SetPrintFrameType(nsIPrintSettings::kEachFrameSep);
751             break;
752         } // switch
753       } else {
754         // if it didn't get extended then have it default to printing
755         // each frame separately
756         aPrintSettings->SetPrintFrameType(nsIPrintSettings::kEachFrameSep);
757       }
758     } else {
759       aPrintSettings->SetPrintFrameType(nsIPrintSettings::kNoFrames);
760     }
761     // Unlock DeviceNames
762     ::GlobalUnlock(prntdlg.hDevNames);
763 
764     // Transfer the settings from the native data to the PrintSettings
765     LPDEVMODEW devMode = (LPDEVMODEW)::GlobalLock(prntdlg.hDevMode);
766     if (!devMode || !prntdlg.hDC) {
767       return NS_ERROR_FAILURE;
768     }
769     psWin->SetDevMode(devMode); // copies DevMode
770     psWin->CopyFromNative(prntdlg.hDC, devMode);
771     ::GlobalUnlock(prntdlg.hDevMode);
772     ::DeleteDC(prntdlg.hDC);
773 
774 #if defined(DEBUG_rods) || defined(DEBUG_dcone)
775     bool    printSelection = prntdlg.Flags & PD_SELECTION;
776     bool    printAllPages  = prntdlg.Flags & PD_ALLPAGES;
777     bool    printNumPages  = prntdlg.Flags & PD_PAGENUMS;
778     int32_t fromPageNum    = 0;
779     int32_t toPageNum      = 0;
780 
781     if (printNumPages) {
782       fromPageNum = prntdlg.nFromPage;
783       toPageNum   = prntdlg.nToPage;
784     }
785     if (printSelection) {
786       printf("Printing the selection\n");
787 
788     } else if (printAllPages) {
789       printf("Printing all the pages\n");
790 
791     } else {
792       printf("Printing from page no. %d to %d\n", fromPageNum, toPageNum);
793     }
794 #endif
795 
796   } else {
797     ::SetFocus(aHWnd);
798     aPrintSettings->SetIsCancelled(true);
799     return NS_ERROR_ABORT;
800   }
801 
802   return NS_OK;
803 }
804 
805 //------------------------------------------------------------------
806 static void
PrepareForPrintDialog(nsIWebBrowserPrint * aWebBrowserPrint,nsIPrintSettings * aPS)807 PrepareForPrintDialog(nsIWebBrowserPrint* aWebBrowserPrint, nsIPrintSettings* aPS)
808 {
809   NS_ASSERTION(aWebBrowserPrint, "Can't be null");
810   NS_ASSERTION(aPS, "Can't be null");
811 
812   bool isFramesetDocument;
813   bool isFramesetFrameSelected;
814   bool isIFrameSelected;
815   bool isRangeSelection;
816 
817   aWebBrowserPrint->GetIsFramesetDocument(&isFramesetDocument);
818   aWebBrowserPrint->GetIsFramesetFrameSelected(&isFramesetFrameSelected);
819   aWebBrowserPrint->GetIsIFrameSelected(&isIFrameSelected);
820   aWebBrowserPrint->GetIsRangeSelection(&isRangeSelection);
821 
822   // Setup print options for UI
823   if (isFramesetDocument) {
824     if (isFramesetFrameSelected) {
825       aPS->SetHowToEnableFrameUI(nsIPrintSettings::kFrameEnableAll);
826     } else {
827       aPS->SetHowToEnableFrameUI(nsIPrintSettings::kFrameEnableAsIsAndEach);
828     }
829   } else {
830     aPS->SetHowToEnableFrameUI(nsIPrintSettings::kFrameEnableNone);
831   }
832 
833   // Now determine how to set up the Frame print UI
834   aPS->SetPrintOptions(nsIPrintSettings::kEnableSelectionRB, isRangeSelection || isIFrameSelected);
835 
836 }
837 
838 //----------------------------------------------------------------------------------
839 //-- Show Print Dialog
840 //----------------------------------------------------------------------------------
NativeShowPrintDialog(HWND aHWnd,nsIWebBrowserPrint * aWebBrowserPrint,nsIPrintSettings * aPrintSettings)841 nsresult NativeShowPrintDialog(HWND                aHWnd,
842                                nsIWebBrowserPrint* aWebBrowserPrint,
843                                nsIPrintSettings*   aPrintSettings)
844 {
845   PrepareForPrintDialog(aWebBrowserPrint, aPrintSettings);
846 
847   nsresult rv = ShowNativePrintDialog(aHWnd, aPrintSettings);
848   if (aHWnd) {
849     ::DestroyWindow(aHWnd);
850   }
851 
852   return rv;
853 }
854 
855