/* * Wordpad implementation - Printing and print preview functions * * Copyright 2007-2008 by Alexander N. Sørnes <alex@thehandofagony.com> * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA */ #include <windows.h> #include <richedit.h> #include <commctrl.h> #include <commdlg.h> #include "wordpad.h" typedef struct _previewinfo { int page; int pages_shown; int saved_pages_shown; int *pageEnds, pageCapacity; int textlength; HDC hdc; HDC hdc2; RECT window; RECT rcPage; SIZE bmSize; SIZE bmScaledSize; SIZE spacing; float zoomratio; int zoomlevel; LPWSTR wszFileName; } previewinfo, *ppreviewinfo; static HGLOBAL devMode; static HGLOBAL devNames; static RECT margins; static previewinfo preview; extern const WCHAR wszPreviewWndClass[]; static const WCHAR var_pagemargin[] = {'P','a','g','e','M','a','r','g','i','n',0}; static const WCHAR var_previewpages[] = {'P','r','e','v','i','e','w','P','a','g','e','s',0}; static LPWSTR get_print_file_filter(HWND hMainWnd) { static WCHAR wszPrintFilter[MAX_STRING_LEN*2+6+4+1]; const WCHAR files_prn[] = {'*','.','P','R','N',0}; const WCHAR files_all[] = {'*','.','*','\0'}; LPWSTR p; HINSTANCE hInstance = GetModuleHandleW(0); p = wszPrintFilter; LoadStringW(hInstance, STRING_PRINTER_FILES_PRN, p, MAX_STRING_LEN); p += lstrlenW(p) + 1; lstrcpyW(p, files_prn); p += lstrlenW(p) + 1; LoadStringW(hInstance, STRING_ALL_FILES, p, MAX_STRING_LEN); p += lstrlenW(p) + 1; lstrcpyW(p, files_all); p += lstrlenW(p) + 1; *p = 0; return wszPrintFilter; } void registry_set_pagemargins(HKEY hKey) { RegSetValueExW(hKey, var_pagemargin, 0, REG_BINARY, (LPBYTE)&margins, sizeof(RECT)); } void registry_read_pagemargins(HKEY hKey) { DWORD size = sizeof(RECT); if(!hKey || RegQueryValueExW(hKey, var_pagemargin, 0, NULL, (LPBYTE)&margins, &size) != ERROR_SUCCESS || size != sizeof(RECT)) { margins.top = 1417; margins.bottom = 1417; margins.left = 1757; margins.right = 1757; } } void registry_set_previewpages(HKEY hKey) { RegSetValueExW(hKey, var_previewpages, 0, REG_DWORD, (LPBYTE)&preview.pages_shown, sizeof(DWORD)); } void registry_read_previewpages(HKEY hKey) { DWORD size = sizeof(DWORD); if(!hKey || RegQueryValueExW(hKey, var_previewpages, 0, NULL, (LPBYTE)&preview.pages_shown, &size) != ERROR_SUCCESS || size != sizeof(DWORD)) { preview.pages_shown = 1; } else { if (preview.pages_shown < 1) preview.pages_shown = 1; else if (preview.pages_shown > 2) preview.pages_shown = 2; } } static void AddTextButton(HWND hRebarWnd, UINT string, UINT command, UINT id) { REBARBANDINFOW rb; HINSTANCE hInstance = GetModuleHandleW(0); WCHAR text[MAX_STRING_LEN]; HWND hButton; LoadStringW(hInstance, string, text, MAX_STRING_LEN); hButton = CreateWindowW(WC_BUTTONW, text, WS_VISIBLE | WS_CHILD, 5, 5, 100, 15, hRebarWnd, ULongToHandle(command), hInstance, NULL); rb.cbSize = REBARBANDINFOW_V6_SIZE; rb.fMask = RBBIM_SIZE | RBBIM_CHILDSIZE | RBBIM_STYLE | RBBIM_CHILD | RBBIM_IDEALSIZE | RBBIM_ID; rb.fStyle = RBBS_NOGRIPPER | RBBS_VARIABLEHEIGHT; rb.hwndChild = hButton; rb.cyChild = rb.cyMinChild = 22; rb.cx = rb.cxMinChild = 90; rb.cxIdeal = 100; rb.wID = id; SendMessageW(hRebarWnd, RB_INSERTBANDW, -1, (LPARAM)&rb); } static HDC make_dc(void) { if(devNames && devMode) { LPDEVNAMES dn = GlobalLock(devNames); LPDEVMODEW dm = GlobalLock(devMode); HDC ret; ret = CreateDCW((LPWSTR)dn + dn->wDriverOffset, (LPWSTR)dn + dn->wDeviceOffset, 0, dm); GlobalUnlock(dn); GlobalUnlock(dm); return ret; } else { return 0; } } static LONG twips_to_centmm(int twips) { return MulDiv(twips, CENTMM_PER_INCH, TWIPS_PER_INCH); } static LONG centmm_to_twips(int mm) { return MulDiv(mm, TWIPS_PER_INCH, CENTMM_PER_INCH); } static LONG twips_to_pixels(int twips, int dpi) { return MulDiv(twips, dpi, TWIPS_PER_INCH); } static LONG devunits_to_twips(int units, int dpi) { return MulDiv(units, TWIPS_PER_INCH, dpi); } static RECT get_print_rect(HDC hdc) { RECT rc; int width, height; if(hdc) { int dpiY = GetDeviceCaps(hdc, LOGPIXELSY); int dpiX = GetDeviceCaps(hdc, LOGPIXELSX); width = devunits_to_twips(GetDeviceCaps(hdc, PHYSICALWIDTH), dpiX); height = devunits_to_twips(GetDeviceCaps(hdc, PHYSICALHEIGHT), dpiY); } else { width = centmm_to_twips(18500); height = centmm_to_twips(27000); } rc.left = margins.left; rc.right = width - margins.right; rc.top = margins.top; rc.bottom = height - margins.bottom; return rc; } void target_device(HWND hMainWnd, DWORD wordWrap) { HWND hEditorWnd = GetDlgItem(hMainWnd, IDC_EDITOR); if(wordWrap == ID_WORDWRAP_MARGIN) { int width = 0; LRESULT result; HDC hdc = make_dc(); RECT rc = get_print_rect(hdc); width = rc.right - rc.left; if(!hdc) { HDC hMaindc = GetDC(hMainWnd); hdc = CreateCompatibleDC(hMaindc); ReleaseDC(hMainWnd, hMaindc); } result = SendMessageW(hEditorWnd, EM_SETTARGETDEVICE, (WPARAM)hdc, width); DeleteDC(hdc); if (result) return; /* otherwise EM_SETTARGETDEVICE failed, so fall back on wrapping * to window using the NULL DC. */ } if (wordWrap != ID_WORDWRAP_NONE) { SendMessageW(hEditorWnd, EM_SETTARGETDEVICE, 0, 0); } else { SendMessageW(hEditorWnd, EM_SETTARGETDEVICE, 0, 1); } } static LPWSTR dialog_print_to_file(HWND hMainWnd) { OPENFILENAMEW ofn; static WCHAR file[MAX_PATH] = {'O','U','T','P','U','T','.','P','R','N',0}; static const WCHAR defExt[] = {'P','R','N',0}; static LPWSTR file_filter; if(!file_filter) file_filter = get_print_file_filter(hMainWnd); ZeroMemory(&ofn, sizeof(ofn)); ofn.lStructSize = sizeof(ofn); ofn.Flags = OFN_PATHMUSTEXIST | OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT; ofn.hwndOwner = hMainWnd; ofn.lpstrFilter = file_filter; ofn.lpstrFile = file; ofn.nMaxFile = MAX_PATH; ofn.lpstrDefExt = defExt; if(GetSaveFileNameW(&ofn)) return file; else return FALSE; } static void char_from_pagenum(HWND hEditorWnd, FORMATRANGE *fr, int page) { int i; fr->chrg.cpMin = 0; for(i = 1; i < page; i++) { int bottom = fr->rc.bottom; fr->chrg.cpMin = SendMessageW(hEditorWnd, EM_FORMATRANGE, FALSE, (LPARAM)fr); fr->rc.bottom = bottom; } } static HWND get_ruler_wnd(HWND hMainWnd) { return GetDlgItem(GetDlgItem(hMainWnd, IDC_REBAR), IDC_RULER); } void redraw_ruler(HWND hRulerWnd) { RECT rc; GetClientRect(hRulerWnd, &rc); InvalidateRect(hRulerWnd, &rc, TRUE); } static void update_ruler(HWND hRulerWnd) { SendMessageW(hRulerWnd, WM_USER, 0, 0); redraw_ruler(hRulerWnd); } static void add_ruler_units(HDC hdcRuler, RECT* drawRect, BOOL NewMetrics, LONG EditLeftmost) { static HDC hdc; if(NewMetrics) { static HBITMAP hBitmap; int i, x, y, RulerTextEnd; int CmPixels; int QuarterCmPixels; HFONT hFont; WCHAR FontName[] = {'M','S',' ','S','a','n','s',' ','S','e','r','i','f',0}; if(hdc) { DeleteDC(hdc); DeleteObject(hBitmap); } hdc = CreateCompatibleDC(0); CmPixels = twips_to_pixels(centmm_to_twips(1000), GetDeviceCaps(hdc, LOGPIXELSX)); QuarterCmPixels = (int)((float)CmPixels / 4.0); hBitmap = CreateCompatibleBitmap(hdc, drawRect->right, drawRect->bottom); SelectObject(hdc, hBitmap); FillRect(hdc, drawRect, GetStockObject(WHITE_BRUSH)); hFont = CreateFontW(10, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, FontName); SelectObject(hdc, hFont); SetBkMode(hdc, TRANSPARENT); SetTextAlign(hdc, TA_CENTER); y = (int)(((float)drawRect->bottom - (float)drawRect->top) / 2.0) + 1; RulerTextEnd = drawRect->right - EditLeftmost + 1; for(i = 1, x = EditLeftmost; x < (drawRect->right - EditLeftmost + 1); i ++) { WCHAR str[3]; WCHAR format[] = {'%','d',0}; int x2 = x; x2 += QuarterCmPixels; if(x2 > RulerTextEnd) break; MoveToEx(hdc, x2, y, NULL); LineTo(hdc, x2, y+2); x2 += QuarterCmPixels; if(x2 > RulerTextEnd) break; MoveToEx(hdc, x2, y - 3, NULL); LineTo(hdc, x2, y + 3); x2 += QuarterCmPixels; if(x2 > RulerTextEnd) break; MoveToEx(hdc, x2, y, NULL); LineTo(hdc, x2, y+2); x += CmPixels; if(x > RulerTextEnd) break; wsprintfW(str, format, i); TextOutW(hdc, x, 5, str, lstrlenW(str)); } DeleteObject(hFont); } BitBlt(hdcRuler, 0, 0, drawRect->right, drawRect->bottom, hdc, 0, 0, SRCAND); } static void paint_ruler(HWND hWnd, LONG EditLeftmost, BOOL NewMetrics) { PAINTSTRUCT ps; HDC hdc = BeginPaint(hWnd, &ps); HDC hdcPrint = make_dc(); RECT printRect = get_print_rect(hdcPrint); RECT drawRect; HBRUSH hBrush = CreateSolidBrush(GetSysColor(COLOR_MENU)); GetClientRect(hWnd, &drawRect); FillRect(hdc, &drawRect, hBrush); drawRect.top += 3; drawRect.bottom -= 3; drawRect.left = EditLeftmost; drawRect.right = twips_to_pixels(printRect.right - margins.left, GetDeviceCaps(hdc, LOGPIXELSX)); FillRect(hdc, &drawRect, GetStockObject(WHITE_BRUSH)); drawRect.top--; drawRect.bottom++; DrawEdge(hdc, &drawRect, EDGE_SUNKEN, BF_RECT); drawRect.left = drawRect.right - 1; drawRect.right = twips_to_pixels(printRect.right + margins.right - margins.left, GetDeviceCaps(hdc, LOGPIXELSX)); DrawEdge(hdc, &drawRect, EDGE_ETCHED, BF_RECT); drawRect.left = 0; drawRect.top = 0; add_ruler_units(hdc, &drawRect, NewMetrics, EditLeftmost); SelectObject(hdc, GetStockObject(BLACK_BRUSH)); DeleteObject(hBrush); DeleteDC(hdcPrint); EndPaint(hWnd, &ps); } LRESULT CALLBACK ruler_proc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) { static WNDPROC pPrevRulerProc; static LONG EditLeftmost; static BOOL NewMetrics; switch(msg) { case WM_USER: if(wParam) { EditLeftmost = ((POINTL*)wParam)->x; pPrevRulerProc = (WNDPROC)lParam; } NewMetrics = TRUE; break; case WM_PAINT: paint_ruler(hWnd, EditLeftmost, NewMetrics); break; default: return CallWindowProcW(pPrevRulerProc, hWnd, msg, wParam, lParam); } return 0; } static void print(LPPRINTDLGW pd, LPWSTR wszFileName) { FORMATRANGE fr; DOCINFOW di; HWND hEditorWnd = GetDlgItem(pd->hwndOwner, IDC_EDITOR); int printedPages = 0; fr.hdc = pd->hDC; fr.hdcTarget = pd->hDC; fr.rc = get_print_rect(fr.hdc); fr.rcPage.left = 0; fr.rcPage.right = fr.rc.right + margins.right; fr.rcPage.top = 0; fr.rcPage.bottom = fr.rc.bottom + margins.bottom; ZeroMemory(&di, sizeof(di)); di.cbSize = sizeof(di); di.lpszDocName = wszFileName; if(pd->Flags & PD_PRINTTOFILE) { di.lpszOutput = dialog_print_to_file(pd->hwndOwner); if(!di.lpszOutput) return; } if(pd->Flags & PD_SELECTION) { SendMessageW(hEditorWnd, EM_EXGETSEL, 0, (LPARAM)&fr.chrg); } else { GETTEXTLENGTHEX gt; gt.flags = GTL_DEFAULT; gt.codepage = 1200; fr.chrg.cpMin = 0; fr.chrg.cpMax = SendMessageW(hEditorWnd, EM_GETTEXTLENGTHEX, (WPARAM)>, 0); if(pd->Flags & PD_PAGENUMS) char_from_pagenum(hEditorWnd, &fr, pd->nToPage); } StartDocW(fr.hdc, &di); do { if(StartPage(fr.hdc) <= 0) break; fr.chrg.cpMin = SendMessageW(hEditorWnd, EM_FORMATRANGE, TRUE, (LPARAM)&fr); if(EndPage(fr.hdc) <= 0) break; printedPages++; if((pd->Flags & PD_PAGENUMS) && (printedPages > (pd->nToPage - pd->nFromPage))) break; } while(fr.chrg.cpMin && fr.chrg.cpMin < fr.chrg.cpMax); EndDoc(fr.hdc); SendMessageW(hEditorWnd, EM_FORMATRANGE, FALSE, 0); } void dialog_printsetup(HWND hMainWnd) { PAGESETUPDLGW ps; ZeroMemory(&ps, sizeof(ps)); ps.lStructSize = sizeof(ps); ps.hwndOwner = hMainWnd; ps.Flags = PSD_INHUNDREDTHSOFMILLIMETERS | PSD_MARGINS; ps.rtMargin.left = twips_to_centmm(margins.left); ps.rtMargin.right = twips_to_centmm(margins.right); ps.rtMargin.top = twips_to_centmm(margins.top); ps.rtMargin.bottom = twips_to_centmm(margins.bottom); ps.hDevMode = devMode; ps.hDevNames = devNames; if(PageSetupDlgW(&ps)) { margins.left = centmm_to_twips(ps.rtMargin.left); margins.right = centmm_to_twips(ps.rtMargin.right); margins.top = centmm_to_twips(ps.rtMargin.top); margins.bottom = centmm_to_twips(ps.rtMargin.bottom); devMode = ps.hDevMode; devNames = ps.hDevNames; update_ruler(get_ruler_wnd(hMainWnd)); } } void get_default_printer_opts(void) { PRINTDLGW pd; ZeroMemory(&pd, sizeof(pd)); ZeroMemory(&pd, sizeof(pd)); pd.lStructSize = sizeof(pd); pd.Flags = PD_RETURNDC | PD_RETURNDEFAULT; pd.hDevMode = devMode; PrintDlgW(&pd); devMode = pd.hDevMode; devNames = pd.hDevNames; } void print_quick(HWND hMainWnd, LPWSTR wszFileName) { PRINTDLGW pd; ZeroMemory(&pd, sizeof(pd)); pd.hwndOwner = hMainWnd; pd.hDC = make_dc(); print(&pd, wszFileName); DeleteDC(pd.hDC); } void dialog_print(HWND hMainWnd, LPWSTR wszFileName) { PRINTDLGW pd; HWND hEditorWnd = GetDlgItem(hMainWnd, IDC_EDITOR); int from = 0; int to = 0; ZeroMemory(&pd, sizeof(pd)); pd.lStructSize = sizeof(pd); pd.hwndOwner = hMainWnd; pd.Flags = PD_RETURNDC | PD_USEDEVMODECOPIESANDCOLLATE; pd.nMinPage = 1; pd.nMaxPage = -1; pd.hDevMode = devMode; pd.hDevNames = devNames; SendMessageW(hEditorWnd, EM_GETSEL, (WPARAM)&from, (LPARAM)&to); if(from == to) pd.Flags |= PD_NOSELECTION; if(PrintDlgW(&pd)) { devMode = pd.hDevMode; devNames = pd.hDevNames; print(&pd, wszFileName); update_ruler(get_ruler_wnd(hMainWnd)); } } static void preview_bar_show(HWND hMainWnd, BOOL show) { HWND hReBar = GetDlgItem(hMainWnd, IDC_REBAR); int i; if(show) { REBARBANDINFOW rb; HWND hStatic; UINT num_pages_string = preview.pages_shown > 1 ? STRING_PREVIEW_ONEPAGE : STRING_PREVIEW_TWOPAGES; AddTextButton(hReBar, STRING_PREVIEW_PRINT, ID_PRINT, BANDID_PREVIEW_BTN1); AddTextButton(hReBar, STRING_PREVIEW_NEXTPAGE, ID_PREVIEW_NEXTPAGE, BANDID_PREVIEW_BTN2); AddTextButton(hReBar, STRING_PREVIEW_PREVPAGE, ID_PREVIEW_PREVPAGE, BANDID_PREVIEW_BTN3); AddTextButton(hReBar, num_pages_string, ID_PREVIEW_NUMPAGES, BANDID_PREVIEW_BTN4); AddTextButton(hReBar, STRING_PREVIEW_ZOOMIN, ID_PREVIEW_ZOOMIN, BANDID_PREVIEW_BTN5); AddTextButton(hReBar, STRING_PREVIEW_ZOOMOUT, ID_PREVIEW_ZOOMOUT, BANDID_PREVIEW_BTN6); AddTextButton(hReBar, STRING_PREVIEW_CLOSE, ID_FILE_EXIT, BANDID_PREVIEW_BTN7); hStatic = CreateWindowW(WC_STATICW, NULL, WS_VISIBLE | WS_CHILD, 0, 0, 0, 0, hReBar, NULL, NULL, NULL); rb.cbSize = REBARBANDINFOW_V6_SIZE; rb.fMask = RBBIM_SIZE | RBBIM_CHILDSIZE | RBBIM_STYLE | RBBIM_CHILD | RBBIM_IDEALSIZE | RBBIM_ID; rb.fStyle = RBBS_NOGRIPPER | RBBS_VARIABLEHEIGHT; rb.hwndChild = hStatic; rb.cyChild = rb.cyMinChild = 22; rb.cx = rb.cxMinChild = 90; rb.cxIdeal = 100; rb.wID = BANDID_PREVIEW_BUFFER; SendMessageW(hReBar, RB_INSERTBANDW, -1, (LPARAM)&rb); } else { for(i = 0; i <= PREVIEW_BUTTONS; i++) SendMessageW(hReBar, RB_DELETEBAND, SendMessageW(hReBar, RB_IDTOINDEX, BANDID_PREVIEW_BTN1+i, 0), 0); } } static const int min_spacing = 10; static void update_preview_scrollbars(HWND hwndPreview, RECT *window) { SCROLLINFO sbi; sbi.cbSize = sizeof(sbi); sbi.fMask = SIF_PAGE|SIF_RANGE; sbi.nMin = 0; if (preview.zoomlevel == 0) { /* Hide scrollbars when zoomed out. */ sbi.nMax = 0; sbi.nPage = window->right; SetScrollInfo(hwndPreview, SB_HORZ, &sbi, TRUE); sbi.nPage = window->bottom; SetScrollInfo(hwndPreview, SB_VERT, &sbi, TRUE); } else { sbi.nMax = preview.bmScaledSize.cx * preview.pages_shown + min_spacing * (preview.pages_shown + 1); sbi.nPage = window->right; SetScrollInfo(hwndPreview, SB_HORZ, &sbi, TRUE); /* Change in the horizontal scrollbar visibility affects the * client rect, so update the client rect. */ GetClientRect(hwndPreview, window); sbi.nMax = preview.bmScaledSize.cy + min_spacing * 2; sbi.nPage = window->bottom; SetScrollInfo(hwndPreview, SB_VERT, &sbi, TRUE); } } static void update_preview_sizes(HWND hwndPreview, BOOL zoomLevelUpdated) { RECT window; GetClientRect(hwndPreview, &window); /* The zoom ratio isn't updated for partial zoom because of resizing the window. */ if (zoomLevelUpdated || preview.zoomlevel != 1) { float ratio, ratioHeight, ratioWidth; if (preview.zoomlevel == 2) { ratio = 1.0; } else { ratioHeight = (window.bottom - min_spacing * 2) / (float)preview.bmSize.cy; ratioWidth = (float)(window.right - min_spacing * (preview.pages_shown + 1)) / (preview.pages_shown * preview.bmSize.cx); if(ratioWidth > ratioHeight) ratio = ratioHeight; else ratio = ratioWidth; if (preview.zoomlevel == 1) ratio += (1.0 - ratio) / 2; } preview.zoomratio = ratio; } preview.bmScaledSize.cx = preview.bmSize.cx * preview.zoomratio; preview.bmScaledSize.cy = preview.bmSize.cy * preview.zoomratio; preview.spacing.cy = max(min_spacing, (window.bottom - preview.bmScaledSize.cy) / 2); preview.spacing.cx = (window.right - preview.bmScaledSize.cx * preview.pages_shown) / (preview.pages_shown + 1); if (preview.spacing.cx < min_spacing) preview.spacing.cx = min_spacing; update_preview_scrollbars(hwndPreview, &window); } static void draw_margin_lines(HDC hdc, int x, int y, float ratio) { HPEN hPen, oldPen; SIZE dpi; RECT page_margin = preview.rcPage; dpi.cx = GetDeviceCaps(hdc, LOGPIXELSX); dpi.cy = GetDeviceCaps(hdc, LOGPIXELSY); page_margin.left = preview.rcPage.left + margins.left; page_margin.top = preview.rcPage.top + margins.top; page_margin.bottom = preview.rcPage.bottom - margins.bottom; page_margin.right = preview.rcPage.right - margins.right; page_margin.left = (int)((float)twips_to_pixels(page_margin.left, dpi.cx) * ratio); page_margin.top = (int)((float)twips_to_pixels(page_margin.top, dpi.cy) * ratio); page_margin.bottom = (int)((float)twips_to_pixels(page_margin.bottom, dpi.cy) * ratio); page_margin.right = (int)((float)twips_to_pixels(page_margin.right, dpi.cx) * ratio); page_margin.left += x; page_margin.top += y; page_margin.bottom += y; page_margin.right += x; hPen = CreatePen(PS_DOT, 1, RGB(0,0,0)); oldPen = SelectObject(hdc, hPen); MoveToEx(hdc, x, page_margin.top, NULL); LineTo(hdc, x + preview.bmScaledSize.cx, page_margin.top); MoveToEx(hdc, x, page_margin.bottom, NULL); LineTo(hdc, x + preview.bmScaledSize.cx, page_margin.bottom); MoveToEx(hdc, page_margin.left, y, NULL); LineTo(hdc, page_margin.left, y + preview.bmScaledSize.cy); MoveToEx(hdc, page_margin.right, y, NULL); LineTo(hdc, page_margin.right, y + preview.bmScaledSize.cy); SelectObject(hdc, oldPen); DeleteObject(hPen); } static BOOL is_last_preview_page(int page) { return preview.pageEnds[page - 1] >= preview.textlength; } void init_preview(HWND hMainWnd, LPWSTR wszFileName) { HINSTANCE hInstance = GetModuleHandleW(0); preview.page = 1; preview.hdc = 0; preview.hdc2 = 0; preview.wszFileName = wszFileName; preview.zoomratio = 0; preview.zoomlevel = 0; preview_bar_show(hMainWnd, TRUE); CreateWindowExW(0, wszPreviewWndClass, NULL, WS_VISIBLE | WS_CHILD | WS_VSCROLL | WS_HSCROLL, 0, 0, 200, 10, hMainWnd, (HMENU)IDC_PREVIEW, hInstance, NULL); } void close_preview(HWND hMainWnd) { HWND hwndPreview = GetDlgItem(hMainWnd, IDC_PREVIEW); preview.window.right = 0; preview.window.bottom = 0; preview.page = 0; HeapFree(GetProcessHeap(), 0, preview.pageEnds); preview.pageEnds = NULL; preview.pageCapacity = 0; if (preview.zoomlevel > 0) preview.pages_shown = preview.saved_pages_shown; if(preview.hdc) { HBITMAP oldbm = GetCurrentObject(preview.hdc, OBJ_BITMAP); DeleteDC(preview.hdc); DeleteObject(oldbm); preview.hdc = NULL; } if(preview.hdc2) { HBITMAP oldbm = GetCurrentObject(preview.hdc2, OBJ_BITMAP); DeleteDC(preview.hdc2); DeleteObject(oldbm); preview.hdc2 = NULL; } preview_bar_show(hMainWnd, FALSE); DestroyWindow(hwndPreview); } BOOL preview_isactive(void) { return preview.page != 0; } static void draw_preview(HWND hEditorWnd, FORMATRANGE* lpFr, RECT* paper, int page) { int bottom; if (!preview.pageEnds) { preview.pageCapacity = 32; preview.pageEnds = HeapAlloc(GetProcessHeap(), 0, sizeof(int) * preview.pageCapacity); if (!preview.pageEnds) return; } else if (page >= preview.pageCapacity) { int *new_buffer; new_buffer = HeapReAlloc(GetProcessHeap(), 0, preview.pageEnds, sizeof(int) * preview.pageCapacity * 2); if (!new_buffer) return; preview.pageCapacity *= 2; preview.pageEnds = new_buffer; } FillRect(lpFr->hdc, paper, GetStockObject(WHITE_BRUSH)); if (page > 1 && is_last_preview_page(page - 1)) return; lpFr->chrg.cpMin = page <= 1 ? 0 : preview.pageEnds[page-2]; bottom = lpFr->rc.bottom; preview.pageEnds[page-1] = SendMessageW(hEditorWnd, EM_FORMATRANGE, TRUE, (LPARAM)lpFr); /* EM_FORMATRANGE sets fr.rc.bottom to indicate the area printed in, * but we want to keep the original for drawing margins */ lpFr->rc.bottom = bottom; SendMessageW(hEditorWnd, EM_FORMATRANGE, FALSE, 0); } static void update_preview_buttons(HWND hMainWnd) { HWND hReBar = GetDlgItem(hMainWnd, IDC_REBAR); EnableWindow(GetDlgItem(hReBar, ID_PREVIEW_PREVPAGE), preview.page > 1); EnableWindow(GetDlgItem(hReBar, ID_PREVIEW_NEXTPAGE), !is_last_preview_page(preview.page) && !is_last_preview_page(preview.page + preview.pages_shown - 1)); EnableWindow(GetDlgItem(hReBar, ID_PREVIEW_NUMPAGES), preview.pages_shown > 1 || (!is_last_preview_page(1) && preview.zoomlevel == 0)); EnableWindow(GetDlgItem(hReBar, ID_PREVIEW_ZOOMIN), preview.zoomlevel < 2); EnableWindow(GetDlgItem(hReBar, ID_PREVIEW_ZOOMOUT), preview.zoomlevel > 0); } static LRESULT print_preview(HWND hwndPreview) { HPEN hPen, oldPen; HDC hdc; HRGN back_rgn, excl_rgn; RECT window, background; PAINTSTRUCT ps; int x, y; hdc = BeginPaint(hwndPreview, &ps); GetClientRect(hwndPreview, &window); back_rgn = CreateRectRgnIndirect(&window); x = preview.spacing.cx - GetScrollPos(hwndPreview, SB_HORZ); y = preview.spacing.cy - GetScrollPos(hwndPreview, SB_VERT); /* draw page outlines */ hPen = CreatePen(PS_SOLID|PS_INSIDEFRAME, 2, RGB(0,0,0)); oldPen = SelectObject(hdc, hPen); background.left = x - 2; background.right = x + preview.bmScaledSize.cx + 2; background.top = y - 2; background.bottom = y + preview.bmScaledSize.cy + 2; Rectangle(hdc, background.left, background.top, background.right, background.bottom); excl_rgn = CreateRectRgnIndirect(&background); CombineRgn(back_rgn, back_rgn, excl_rgn, RGN_DIFF); if(preview.pages_shown > 1) { background.left += preview.bmScaledSize.cx + preview.spacing.cx; background.right += preview.bmScaledSize.cx + preview.spacing.cx; Rectangle(hdc, background.left, background.top, background.right, background.bottom); SetRectRgn(excl_rgn, background.left, background.top, background.right, background.bottom); CombineRgn(back_rgn, back_rgn, excl_rgn, RGN_DIFF); } SelectObject(hdc, oldPen); DeleteObject(hPen); FillRgn(hdc, back_rgn, GetStockObject(GRAY_BRUSH)); DeleteObject(excl_rgn); DeleteObject(back_rgn); StretchBlt(hdc, x, y, preview.bmScaledSize.cx, preview.bmScaledSize.cy, preview.hdc, 0, 0, preview.bmSize.cx, preview.bmSize.cy, SRCCOPY); draw_margin_lines(hdc, x, y, preview.zoomratio); if(preview.pages_shown > 1) { if (!is_last_preview_page(preview.page)) { x += preview.spacing.cx + preview.bmScaledSize.cx; StretchBlt(hdc, x, y, preview.bmScaledSize.cx, preview.bmScaledSize.cy, preview.hdc2, 0, 0, preview.bmSize.cx, preview.bmSize.cy, SRCCOPY); draw_margin_lines(hdc, x, y, preview.zoomratio); } else { background.left += 2; background.right -= 2; background.top += 2; background.bottom -= 2; FillRect(hdc, &background, GetStockObject(WHITE_BRUSH)); } } preview.window = window; EndPaint(hwndPreview, &ps); return 0; } static void update_preview_statusbar(HWND hMainWnd) { HWND hStatusbar = GetDlgItem(hMainWnd, IDC_STATUSBAR); HINSTANCE hInst = GetModuleHandleW(0); WCHAR *p; WCHAR wstr[MAX_STRING_LEN]; p = wstr; if (preview.pages_shown < 2 || is_last_preview_page(preview.page)) { static const WCHAR fmt[] = {' ','%','d','\0'}; p += LoadStringW(hInst, STRING_PREVIEW_PAGE, wstr, MAX_STRING_LEN); wsprintfW(p, fmt, preview.page); } else { static const WCHAR fmt[] = {' ','%','d','-','%','d','\0'}; p += LoadStringW(hInst, STRING_PREVIEW_PAGES, wstr, MAX_STRING_LEN); wsprintfW(p, fmt, preview.page, preview.page + 1); } SetWindowTextW(hStatusbar, wstr); } /* Update for page changes. */ static void update_preview(HWND hMainWnd) { RECT paper; HWND hEditorWnd = GetDlgItem(hMainWnd, IDC_EDITOR); HWND hwndPreview = GetDlgItem(hMainWnd, IDC_PREVIEW); HBITMAP hBitmapCapture; FORMATRANGE fr; HDC hdc = GetDC(hwndPreview); fr.hdcTarget = make_dc(); fr.rc = fr.rcPage = preview.rcPage; fr.rc.left += margins.left; fr.rc.top += margins.top; fr.rc.bottom -= margins.bottom; fr.rc.right -= margins.right; fr.chrg.cpMin = 0; fr.chrg.cpMax = preview.textlength; paper.left = 0; paper.right = preview.bmSize.cx; paper.top = 0; paper.bottom = preview.bmSize.cy; if (!preview.hdc) { preview.hdc = CreateCompatibleDC(hdc); hBitmapCapture = CreateCompatibleBitmap(hdc, preview.bmSize.cx, preview.bmSize.cy); SelectObject(preview.hdc, hBitmapCapture); } fr.hdc = preview.hdc; draw_preview(hEditorWnd, &fr, &paper, preview.page); if(preview.pages_shown > 1) { if (!preview.hdc2) { preview.hdc2 = CreateCompatibleDC(hdc); hBitmapCapture = CreateCompatibleBitmap(hdc, preview.bmSize.cx, preview.bmSize.cy); SelectObject(preview.hdc2, hBitmapCapture); } fr.hdc = preview.hdc2; draw_preview(hEditorWnd, &fr, &fr.rcPage, preview.page + 1); } DeleteDC(fr.hdcTarget); ReleaseDC(hwndPreview, hdc); InvalidateRect(hwndPreview, NULL, FALSE); update_preview_buttons(hMainWnd); update_preview_statusbar(hMainWnd); } static void toggle_num_pages(HWND hMainWnd) { HWND hReBar = GetDlgItem(hMainWnd, IDC_REBAR); WCHAR name[MAX_STRING_LEN]; HINSTANCE hInst = GetModuleHandleW(0); int nPreviewPages; preview.pages_shown = preview.pages_shown > 1 ? 1 : 2; nPreviewPages = preview.zoomlevel > 0 ? preview.saved_pages_shown : preview.pages_shown; LoadStringW(hInst, nPreviewPages > 1 ? STRING_PREVIEW_ONEPAGE : STRING_PREVIEW_TWOPAGES, name, MAX_STRING_LEN); SetWindowTextW(GetDlgItem(hReBar, ID_PREVIEW_NUMPAGES), name); update_preview_sizes(GetDlgItem(hMainWnd, IDC_PREVIEW), TRUE); update_preview(hMainWnd); } /* Returns the page shown that the point is in (1 or 2) or 0 if the point * isn't inside either page */ static int preview_page_hittest(POINT pt) { RECT rc; rc.left = preview.spacing.cx; rc.right = rc.left + preview.bmScaledSize.cx; rc.top = preview.spacing.cy; rc.bottom = rc.top + preview.bmScaledSize.cy; if (PtInRect(&rc, pt)) return 1; if (preview.pages_shown <= 1) return 0; rc.left += preview.bmScaledSize.cx + preview.spacing.cx; rc.right += preview.bmScaledSize.cx + preview.spacing.cx; if (PtInRect(&rc, pt)) return is_last_preview_page(preview.page) ? 1 : 2; return 0; } LRESULT CALLBACK preview_proc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) { switch(msg) { case WM_CREATE: { HWND hMainWnd = GetParent(hWnd); HWND hEditorWnd = GetDlgItem(hMainWnd, IDC_EDITOR); FORMATRANGE fr; GETTEXTLENGTHEX gt = {GTL_DEFAULT, 1200}; HDC hdc = GetDC(hWnd); HDC hdcTarget = make_dc(); fr.rc = preview.rcPage = get_print_rect(hdcTarget); preview.rcPage.bottom += margins.bottom; preview.rcPage.right += margins.right; preview.rcPage.top = preview.rcPage.left = 0; fr.rcPage = preview.rcPage; preview.bmSize.cx = twips_to_pixels(preview.rcPage.right, GetDeviceCaps(hdc, LOGPIXELSX)); preview.bmSize.cy = twips_to_pixels(preview.rcPage.bottom, GetDeviceCaps(hdc, LOGPIXELSY)); preview.textlength = SendMessageW(hEditorWnd, EM_GETTEXTLENGTHEX, (WPARAM)>, 0); fr.hdc = CreateCompatibleDC(hdc); fr.hdcTarget = hdcTarget; fr.chrg.cpMin = 0; fr.chrg.cpMax = preview.textlength; DeleteDC(fr.hdc); DeleteDC(hdcTarget); ReleaseDC(hWnd, hdc); update_preview_sizes(hWnd, TRUE); update_preview(hMainWnd); break; } case WM_PAINT: return print_preview(hWnd); case WM_SIZE: { update_preview_sizes(hWnd, FALSE); InvalidateRect(hWnd, NULL, FALSE); break; } case WM_VSCROLL: case WM_HSCROLL: { SCROLLINFO si; RECT rc; int nBar = (msg == WM_VSCROLL) ? SB_VERT : SB_HORZ; int origPos; GetClientRect(hWnd, &rc); si.cbSize = sizeof(si); si.fMask = SIF_ALL; GetScrollInfo(hWnd, nBar, &si); origPos = si.nPos; switch(LOWORD(wParam)) { case SB_TOP: /* == SB_LEFT */ si.nPos = si.nMin; break; case SB_BOTTOM: /* == SB_RIGHT */ si.nPos = si.nMax; break; case SB_LINEUP: /* == SB_LINELEFT */ si.nPos -= si.nPage / 10; break; case SB_LINEDOWN: /* == SB_LINERIGHT */ si.nPos += si.nPage / 10; break; case SB_PAGEUP: /* == SB_PAGELEFT */ si.nPos -= si.nPage; break; case SB_PAGEDOWN: /* SB_PAGERIGHT */ si.nPos += si.nPage; break; case SB_THUMBTRACK: si.nPos = si.nTrackPos; break; } si.fMask = SIF_POS; SetScrollInfo(hWnd, nBar, &si, TRUE); GetScrollInfo(hWnd, nBar, &si); if (si.nPos != origPos) { int amount = origPos - si.nPos; if (msg == WM_VSCROLL) ScrollWindow(hWnd, 0, amount, NULL, NULL); else ScrollWindow(hWnd, amount, 0, NULL, NULL); } return 0; } case WM_SETCURSOR: { POINT pt; RECT rc; int bHittest = FALSE; DWORD messagePos = GetMessagePos(); pt.x = (short)LOWORD(messagePos); pt.y = (short)HIWORD(messagePos); ScreenToClient(hWnd, &pt); GetClientRect(hWnd, &rc); if (PtInRect(&rc, pt)) { pt.x += GetScrollPos(hWnd, SB_HORZ); pt.y += GetScrollPos(hWnd, SB_VERT); bHittest = preview_page_hittest(pt); } if (bHittest) SetCursor(LoadCursorW(GetModuleHandleW(0), MAKEINTRESOURCEW(IDC_ZOOM))); else SetCursor(LoadCursorW(NULL, (WCHAR*)IDC_ARROW)); return TRUE; } case WM_LBUTTONDOWN: { int page; POINT pt; pt.x = (short)LOWORD(lParam) + GetScrollPos(hWnd, SB_HORZ); pt.y = (short)HIWORD(lParam) + GetScrollPos(hWnd, SB_VERT); if ((page = preview_page_hittest(pt)) > 0) { HWND hMainWnd = GetParent(hWnd); /* Convert point from client coordinate to unzoomed page * coordinate. */ pt.x -= preview.spacing.cx; if (page > 1) pt.x -= preview.bmScaledSize.cx + preview.spacing.cx; pt.y -= preview.spacing.cy; pt.x /= preview.zoomratio; pt.y /= preview.zoomratio; if (preview.zoomlevel == 0) preview.saved_pages_shown = preview.pages_shown; preview.zoomlevel = (preview.zoomlevel + 1) % 3; preview.zoomratio = 0; if (preview.zoomlevel == 0 && preview.saved_pages_shown > 1) { toggle_num_pages(hMainWnd); } else if (preview.pages_shown > 1) { if (page >= 2) preview.page++; toggle_num_pages(hMainWnd); } else { update_preview_sizes(hWnd, TRUE); InvalidateRect(hWnd, NULL, FALSE); update_preview_buttons(hMainWnd); } if (preview.zoomlevel > 0) { SCROLLINFO si; /* Convert the coordinate back to client coordinate. */ pt.x *= preview.zoomratio; pt.y *= preview.zoomratio; pt.x += preview.spacing.cx; pt.y += preview.spacing.cy; /* Scroll to center view at that point on the page */ si.cbSize = sizeof(si); si.fMask = SIF_PAGE; GetScrollInfo(hWnd, SB_HORZ, &si); pt.x -= si.nPage / 2; SetScrollPos(hWnd, SB_HORZ, pt.x, TRUE); GetScrollInfo(hWnd, SB_VERT, &si); pt.y -= si.nPage / 2; SetScrollPos(hWnd, SB_VERT, pt.y, TRUE); } } } default: return DefWindowProcW(hWnd, msg, wParam, lParam); } return 0; } LRESULT preview_command(HWND hWnd, WPARAM wParam) { switch(LOWORD(wParam)) { case ID_FILE_EXIT: PostMessageW(hWnd, WM_CLOSE, 0, 0); break; case ID_PREVIEW_NEXTPAGE: case ID_PREVIEW_PREVPAGE: { if(LOWORD(wParam) == ID_PREVIEW_NEXTPAGE) preview.page++; else preview.page--; update_preview(hWnd); } break; case ID_PREVIEW_NUMPAGES: toggle_num_pages(hWnd); break; case ID_PREVIEW_ZOOMIN: if (preview.zoomlevel < 2) { if (preview.zoomlevel == 0) preview.saved_pages_shown = preview.pages_shown; preview.zoomlevel++; preview.zoomratio = 0; if (preview.pages_shown > 1) { /* Forced switch to one page when zooming in. */ toggle_num_pages(hWnd); } else { HWND hwndPreview = GetDlgItem(hWnd, IDC_PREVIEW); update_preview_sizes(hwndPreview, TRUE); InvalidateRect(hwndPreview, NULL, FALSE); update_preview_buttons(hWnd); } } break; case ID_PREVIEW_ZOOMOUT: if (preview.zoomlevel > 0) { HWND hwndPreview = GetDlgItem(hWnd, IDC_PREVIEW); preview.zoomlevel--; preview.zoomratio = 0; if (preview.zoomlevel == 0 && preview.saved_pages_shown > 1) { toggle_num_pages(hWnd); } else { update_preview_sizes(hwndPreview, TRUE); InvalidateRect(hwndPreview, NULL, FALSE); update_preview_buttons(hWnd); } } break; case ID_PRINT: dialog_print(hWnd, preview.wszFileName); SendMessageW(hWnd, WM_CLOSE, 0, 0); break; } return 0; }