// SPDX-License-Identifier: GPL-2.0-or-later /** * @file * Implementation of native file dialogs for Win32. */ /* Authors: * Joel Holdsworth * The Inkscape Organization * Abhishek Sharma * * Copyright (C) 2004-2008 The Inkscape Organization * * Released under GNU GPL v2+, read the file 'COPYING' for more information. */ #ifdef _WIN32 #include "filedialogimpl-win32.h" // General includes #include #include #include #include #include #include #include #include //Inkscape includes #include "display/cairo-utils.h" #include "document.h" #include "extension/db.h" #include "extension/input.h" #include "extension/output.h" #include "filedialog.h" #include "helper/pixbuf-ops.h" #include "preferences.h" #include "util/units.h" using namespace Glib; using namespace Cairo; using namespace Gdk::Cairo; namespace Inkscape { namespace UI { namespace Dialog { const int PREVIEW_WIDENING = 150; const int WINDOW_WIDTH_MINIMUM = 32; const int WINDOW_WIDTH_FALLBACK = 450; const int WINDOW_HEIGHT_MINIMUM = 32; const int WINDOW_HEIGHT_FALLBACK = 360; const char PreviewWindowClassName[] = "PreviewWnd"; const unsigned long MaxPreviewFileSize = 10240; // kB #define IDC_SHOW_PREVIEW 1000 struct Filter { gunichar2* name; glong name_length; gunichar2* filter; glong filter_length; Inkscape::Extension::Extension* mod; }; ustring utf16_to_ustring(const wchar_t *utf16string, int utf16length = -1) { gchar *utf8string = g_utf16_to_utf8((const gunichar2*)utf16string, utf16length, NULL, NULL, NULL); ustring result(utf8string); g_free(utf8string); return result; } namespace { int sanitizeWindowSizeParam( int size, int delta, int minimum, int fallback ) { int result = size; if ( size < minimum ) { g_warning( "Window size %d is less than cutoff.", size ); result = fallback - delta; } result += delta; return result; } } // namespace /*######################################################################### ### F I L E D I A L O G B A S E C L A S S #########################################################################*/ FileDialogBaseWin32::FileDialogBaseWin32(Gtk::Window &parent, const Glib::ustring &dir, const gchar *title, FileDialogType type, gchar const* /*preferenceBase*/) : dialogType(type), parent(parent), _current_directory(dir) { _main_loop = NULL; _filter_index = 1; _filter_count = 0; _title = (wchar_t*)g_utf8_to_utf16(title, -1, NULL, NULL, NULL); g_assert(_title != NULL); Glib::RefPtr parentWindow = parent.get_window(); g_assert(parentWindow->gobj() != NULL); _ownerHwnd = (HWND)gdk_win32_window_get_handle((GdkWindow*)parentWindow->gobj()); } FileDialogBaseWin32::~FileDialogBaseWin32() { g_free(_title); } Inkscape::Extension::Extension *FileDialogBaseWin32::getSelectionType() { return _extension; } Glib::ustring FileDialogBaseWin32::getCurrentDirectory() { return _current_directory; } /*######################################################################### ### F I L E O P E N #########################################################################*/ bool FileOpenDialogImplWin32::_show_preview = true; /** * Constructor. Not called directly. Use the factory. */ FileOpenDialogImplWin32::FileOpenDialogImplWin32(Gtk::Window &parent, const Glib::ustring &dir, FileDialogType fileTypes, const gchar *title) : FileDialogBaseWin32(parent, dir, title, fileTypes, "dialogs.open") { // Initialize to Autodetect _extension = NULL; // Set our dialog type (open, import, etc...) dialogType = fileTypes; _show_preview_button_bitmap = NULL; _preview_wnd = NULL; _file_dialog_wnd = NULL; _base_window_proc = NULL; _preview_file_size = 0; _preview_bitmap = NULL; _preview_file_icon = NULL; _preview_document_width = 0; _preview_document_height = 0; _preview_image_width = 0; _preview_image_height = 0; _preview_emf_image = false; _mutex = NULL; if (dialogType != CUSTOM_TYPE) createFilterMenu(); } /** * Destructor */ FileOpenDialogImplWin32::~FileOpenDialogImplWin32() { if(_filter != NULL) delete[] _filter; if(_extension_map != NULL) delete[] _extension_map; } void FileOpenDialogImplWin32::addFilterMenu(Glib::ustring name, Glib::ustring pattern) { std::list filter_list; Filter all_exe_files; const gchar *all_exe_files_filter_name = name.data(); const gchar *all_exe_files_filter = pattern.data(); // Calculate the amount of memory required int filter_count = 1; int filter_length = 1; int extension_index = 0; _extension_map = new Inkscape::Extension::Extension*[filter_count]; // Filter Executable Files all_exe_files.name = g_utf8_to_utf16(all_exe_files_filter_name, -1, NULL, &all_exe_files.name_length, NULL); all_exe_files.filter = g_utf8_to_utf16(all_exe_files_filter, -1, NULL, &all_exe_files.filter_length, NULL); all_exe_files.mod = NULL; filter_list.push_front(all_exe_files); filter_length = all_exe_files.name_length + all_exe_files.filter_length + 3; // Add 3 for two \0s and a * _filter = new wchar_t[filter_length]; wchar_t *filterptr = _filter; for(std::list::iterator filter_iterator = filter_list.begin(); filter_iterator != filter_list.end(); ++filter_iterator) { const Filter &filter = *filter_iterator; wcsncpy(filterptr, (wchar_t*)filter.name, filter.name_length); filterptr += filter.name_length; g_free(filter.name); *(filterptr++) = L'\0'; *(filterptr++) = L'*'; if(filter.filter != NULL) { wcsncpy(filterptr, (wchar_t*)filter.filter, filter.filter_length); filterptr += filter.filter_length; g_free(filter.filter); } *(filterptr++) = L'\0'; // Associate this input extension with the file type name _extension_map[extension_index++] = filter.mod; } *(filterptr++) = L'\0'; _filter_count = extension_index; _filter_index = 1; // Select the 1st filter in the list } void FileOpenDialogImplWin32::createFilterMenu() { std::list filter_list; int extension_index = 0; int filter_length = 1; if (dialogType == CUSTOM_TYPE) { return; } if (dialogType != EXE_TYPES) { Inkscape::Preferences *prefs = Inkscape::Preferences::get(); _show_preview = prefs->getBool("/dialogs/open/enable_preview", true); // Compose the filter string Inkscape::Extension::DB::InputList extension_list; Inkscape::Extension::db.get_input_list(extension_list); ustring all_inkscape_files_filter, all_image_files_filter, all_vectors_filter, all_bitmaps_filter; Filter all_files, all_inkscape_files, all_image_files, all_vectors, all_bitmaps; const gchar *all_files_filter_name = _("All Files"); const gchar *all_inkscape_files_filter_name = _("All Inkscape Files"); const gchar *all_image_files_filter_name = _("All Images"); const gchar *all_vectors_filter_name = _("All Vectors"); const gchar *all_bitmaps_filter_name = _("All Bitmaps"); // Calculate the amount of memory required int filter_count = 5; // 5 - one for each filter type for (Inkscape::Extension::DB::InputList::iterator current_item = extension_list.begin(); current_item != extension_list.end(); ++current_item) { Filter filter; Inkscape::Extension::Input *imod = *current_item; if (imod->deactivated()) continue; // Type filter.name = g_utf8_to_utf16(imod->get_filetypename(true), -1, NULL, &filter.name_length, NULL); // Extension const gchar *file_extension_name = imod->get_extension(); filter.filter = g_utf8_to_utf16(file_extension_name, -1, NULL, &filter.filter_length, NULL); filter.mod = imod; filter_list.push_back(filter); filter_length += filter.name_length + filter.filter_length + 3; // Add 3 for two \0s and a * // Add to the "All Inkscape Files" Entry if(all_inkscape_files_filter.length() > 0) all_inkscape_files_filter += ";*"; all_inkscape_files_filter += file_extension_name; if( strncmp("image", imod->get_mimetype(), 5) == 0) { // Add to the "All Image Files" Entry if(all_image_files_filter.length() > 0) all_image_files_filter += ";*"; all_image_files_filter += file_extension_name; } // I don't know of any other way to define "bitmap" formats other than by listing them // if you change it here, do the same change in filedialogimpl-gtkmm if ( strncmp("image/png", imod->get_mimetype(), 9)==0 || strncmp("image/jpeg", imod->get_mimetype(), 10)==0 || strncmp("image/gif", imod->get_mimetype(), 9)==0 || strncmp("image/x-icon", imod->get_mimetype(), 12)==0 || strncmp("image/x-navi-animation", imod->get_mimetype(), 22)==0 || strncmp("image/x-cmu-raster", imod->get_mimetype(), 18)==0 || strncmp("image/x-xpixmap", imod->get_mimetype(), 15)==0 || strncmp("image/bmp", imod->get_mimetype(), 9)==0 || strncmp("image/vnd.wap.wbmp", imod->get_mimetype(), 18)==0 || strncmp("image/tiff", imod->get_mimetype(), 10)==0 || strncmp("image/x-xbitmap", imod->get_mimetype(), 15)==0 || strncmp("image/x-tga", imod->get_mimetype(), 11)==0 || strncmp("image/x-pcx", imod->get_mimetype(), 11)==0 ) { if(all_bitmaps_filter.length() > 0) all_bitmaps_filter += ";*"; all_bitmaps_filter += file_extension_name; } else { if(all_vectors_filter.length() > 0) all_vectors_filter += ";*"; all_vectors_filter += file_extension_name; } filter_count++; } _extension_map = new Inkscape::Extension::Extension*[filter_count]; // Filter bitmap files all_bitmaps.name = g_utf8_to_utf16(all_bitmaps_filter_name, -1, NULL, &all_bitmaps.name_length, NULL); all_bitmaps.filter = g_utf8_to_utf16(all_bitmaps_filter.data(), -1, NULL, &all_bitmaps.filter_length, NULL); all_bitmaps.mod = NULL; filter_list.push_front(all_bitmaps); // Filter vector files all_vectors.name = g_utf8_to_utf16(all_vectors_filter_name, -1, NULL, &all_vectors.name_length, NULL); all_vectors.filter = g_utf8_to_utf16(all_vectors_filter.data(), -1, NULL, &all_vectors.filter_length, NULL); all_vectors.mod = NULL; filter_list.push_front(all_vectors); // Filter Image Files all_image_files.name = g_utf8_to_utf16(all_image_files_filter_name, -1, NULL, &all_image_files.name_length, NULL); all_image_files.filter = g_utf8_to_utf16(all_image_files_filter.data(), -1, NULL, &all_image_files.filter_length, NULL); all_image_files.mod = NULL; filter_list.push_front(all_image_files); // Filter Inkscape Files all_inkscape_files.name = g_utf8_to_utf16(all_inkscape_files_filter_name, -1, NULL, &all_inkscape_files.name_length, NULL); all_inkscape_files.filter = g_utf8_to_utf16(all_inkscape_files_filter.data(), -1, NULL, &all_inkscape_files.filter_length, NULL); all_inkscape_files.mod = NULL; filter_list.push_front(all_inkscape_files); // Filter All Files all_files.name = g_utf8_to_utf16(all_files_filter_name, -1, NULL, &all_files.name_length, NULL); all_files.filter = NULL; all_files.filter_length = 0; all_files.mod = NULL; filter_list.push_front(all_files); filter_length += all_files.name_length + 3 + all_inkscape_files.filter_length + all_inkscape_files.name_length + 3 + all_image_files.filter_length + all_image_files.name_length + 3 + all_vectors.filter_length + all_vectors.name_length + 3 + all_bitmaps.filter_length + all_bitmaps.name_length + 3 + 1; // Add 3 for 2*2 \0s and a *, and 1 for a trailing \0 } else { // Executables only ustring all_exe_files_filter = "*.exe;*.bat;*.com"; Filter all_exe_files, all_files; const gchar *all_files_filter_name = _("All Files"); const gchar *all_exe_files_filter_name = _("All Executable Files"); // Calculate the amount of memory required int filter_count = 2; // 2 - All Files and All Executable Files _extension_map = new Inkscape::Extension::Extension*[filter_count]; // Filter Executable Files all_exe_files.name = g_utf8_to_utf16(all_exe_files_filter_name, -1, NULL, &all_exe_files.name_length, NULL); all_exe_files.filter = g_utf8_to_utf16(all_exe_files_filter.data(), -1, NULL, &all_exe_files.filter_length, NULL); all_exe_files.mod = NULL; filter_list.push_front(all_exe_files); // Filter All Files all_files.name = g_utf8_to_utf16(all_files_filter_name, -1, NULL, &all_files.name_length, NULL); all_files.filter = NULL; all_files.filter_length = 0; all_files.mod = NULL; filter_list.push_front(all_files); filter_length += all_files.name_length + 3 + all_exe_files.filter_length + all_exe_files.name_length + 3 + 1; // Add 3 for 2*2 \0s and a *, and 1 for a trailing \0 } _filter = new wchar_t[filter_length]; wchar_t *filterptr = _filter; for(std::list::iterator filter_iterator = filter_list.begin(); filter_iterator != filter_list.end(); ++filter_iterator) { const Filter &filter = *filter_iterator; wcsncpy(filterptr, (wchar_t*)filter.name, filter.name_length); filterptr += filter.name_length; g_free(filter.name); *(filterptr++) = L'\0'; *(filterptr++) = L'*'; if(filter.filter != NULL) { wcsncpy(filterptr, (wchar_t*)filter.filter, filter.filter_length); filterptr += filter.filter_length; g_free(filter.filter); } *(filterptr++) = L'\0'; // Associate this input extension with the file type name _extension_map[extension_index++] = filter.mod; } *(filterptr++) = L'\0'; _filter_count = extension_index; _filter_index = 2; // Select the 2nd filter in the list - 2 is NOT the 3rd } void FileOpenDialogImplWin32::GetOpenFileName_thread() { OPENFILENAMEW ofn; g_assert(_mutex != NULL); WCHAR* current_directory_string = (WCHAR*)g_utf8_to_utf16( _current_directory.data(), _current_directory.length(), NULL, NULL, NULL); memset(&ofn, 0, sizeof(ofn)); // Copy the selected file name, converting from UTF-8 to UTF-16 memset(_path_string, 0, sizeof(_path_string)); gunichar2* utf16_path_string = g_utf8_to_utf16( myFilename.data(), -1, NULL, NULL, NULL); wcsncpy(_path_string, (wchar_t*)utf16_path_string, _MAX_PATH); g_free(utf16_path_string); ofn.lStructSize = sizeof(ofn); ofn.hwndOwner = _ownerHwnd; ofn.lpstrFile = _path_string; ofn.nMaxFile = _MAX_PATH; ofn.lpstrFileTitle = NULL; ofn.nMaxFileTitle = 0; ofn.lpstrInitialDir = current_directory_string; ofn.lpstrTitle = _title; ofn.Flags = OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST | OFN_EXPLORER | OFN_ENABLEHOOK | OFN_HIDEREADONLY | OFN_ENABLESIZING; ofn.lpstrFilter = _filter; ofn.nFilterIndex = _filter_index; ofn.lpfnHook = GetOpenFileName_hookproc; ofn.lCustData = (LPARAM)this; _result = GetOpenFileNameW(&ofn) != 0; g_assert(ofn.nFilterIndex >= 1 && ofn.nFilterIndex <= _filter_count); _filter_index = ofn.nFilterIndex; _extension = _extension_map[ofn.nFilterIndex - 1]; // Copy the selected file name, converting from UTF-16 to UTF-8 myFilename = utf16_to_ustring(_path_string, _MAX_PATH); // Tidy up g_free(current_directory_string); _mutex->lock(); _finished = true; _mutex->unlock(); } void FileOpenDialogImplWin32::register_preview_wnd_class() { HINSTANCE hInstance = GetModuleHandle(NULL); const WNDCLASSA PreviewWndClass = { CS_HREDRAW | CS_VREDRAW, preview_wnd_proc, 0, 0, hInstance, NULL, LoadCursor(hInstance, IDC_ARROW), (HBRUSH)(COLOR_BTNFACE + 1), NULL, PreviewWindowClassName }; RegisterClassA(&PreviewWndClass); } UINT_PTR CALLBACK FileOpenDialogImplWin32::GetOpenFileName_hookproc( HWND hdlg, UINT uiMsg, WPARAM, LPARAM lParam) { FileOpenDialogImplWin32 *pImpl = reinterpret_cast (GetWindowLongPtr(hdlg, GWLP_USERDATA)); switch(uiMsg) { case WM_INITDIALOG: { HWND hParentWnd = GetParent(hdlg); HINSTANCE hInstance = GetModuleHandle(NULL); // Set the pointer to the object OPENFILENAMEW *ofn = reinterpret_cast(lParam); SetWindowLongPtr(hdlg, GWLP_USERDATA, ofn->lCustData); SetWindowLongPtr(hParentWnd, GWLP_USERDATA, ofn->lCustData); pImpl = reinterpret_cast(ofn->lCustData); // Make the window a bit wider RECT rcRect; GetWindowRect(hParentWnd, &rcRect); // Don't show the preview when opening executable files if ( pImpl->dialogType == EXE_TYPES) { MoveWindow(hParentWnd, rcRect.left, rcRect.top, rcRect.right - rcRect.left, rcRect.bottom - rcRect.top, FALSE); } else { MoveWindow(hParentWnd, rcRect.left, rcRect.top, rcRect.right - rcRect.left + PREVIEW_WIDENING, rcRect.bottom - rcRect.top, FALSE); } // Subclass the parent pImpl->_base_window_proc = (WNDPROC)GetWindowLongPtr(hParentWnd, GWLP_WNDPROC); SetWindowLongPtr(hParentWnd, GWLP_WNDPROC, reinterpret_cast(file_dialog_subclass_proc)); if ( pImpl->dialogType != EXE_TYPES) { // Add a button to the toolbar pImpl->_toolbar_wnd = FindWindowEx(hParentWnd, NULL, "ToolbarWindow32", NULL); pImpl->_show_preview_button_bitmap = LoadBitmap( hInstance, MAKEINTRESOURCE(IDC_SHOW_PREVIEW)); TBADDBITMAP tbAddBitmap = {NULL, reinterpret_cast(pImpl->_show_preview_button_bitmap)}; const int iBitmapIndex = SendMessage(pImpl->_toolbar_wnd, TB_ADDBITMAP, 1, (LPARAM)&tbAddBitmap); TBBUTTON tbButton; memset(&tbButton, 0, sizeof(TBBUTTON)); tbButton.iBitmap = iBitmapIndex; tbButton.idCommand = IDC_SHOW_PREVIEW; tbButton.fsState = (pImpl->_show_preview ? TBSTATE_CHECKED : 0) | TBSTATE_ENABLED; tbButton.fsStyle = TBSTYLE_CHECK; tbButton.iString = (INT_PTR)_("Show Preview"); SendMessage(pImpl->_toolbar_wnd, TB_ADDBUTTONS, 1, (LPARAM)&tbButton); // Create preview pane register_preview_wnd_class(); } pImpl->_mutex->lock(); pImpl->_file_dialog_wnd = hParentWnd; if ( pImpl->dialogType != EXE_TYPES) { pImpl->_preview_wnd = CreateWindowA(PreviewWindowClassName, "", WS_CHILD | WS_VISIBLE, 0, 0, 100, 100, hParentWnd, NULL, hInstance, NULL); SetWindowLongPtr(pImpl->_preview_wnd, GWLP_USERDATA, ofn->lCustData); } pImpl->_mutex->unlock(); pImpl->layout_dialog(); } break; case WM_NOTIFY: { OFNOTIFY *pOFNotify = reinterpret_cast(lParam); switch(pOFNotify->hdr.code) { case CDN_SELCHANGE: { if(pImpl != NULL) { // Get the file name pImpl->_mutex->lock(); SendMessage(pOFNotify->hdr.hwndFrom, CDM_GETFILEPATH, sizeof(pImpl->_path_string) / sizeof(wchar_t), (LPARAM)pImpl->_path_string); pImpl->_file_selected = true; pImpl->_mutex->unlock(); } } break; } } break; case WM_CLOSE: pImpl->_mutex->lock(); pImpl->_preview_file_size = 0; pImpl->_file_dialog_wnd = NULL; DestroyWindow(pImpl->_preview_wnd); pImpl->_preview_wnd = NULL; DeleteObject(pImpl->_show_preview_button_bitmap); pImpl->_show_preview_button_bitmap = NULL; pImpl->_mutex->unlock(); break; } // Use default dialog behaviour return 0; } LRESULT CALLBACK FileOpenDialogImplWin32::file_dialog_subclass_proc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { FileOpenDialogImplWin32 *pImpl = reinterpret_cast (GetWindowLongPtr(hwnd, GWLP_USERDATA)); LRESULT lResult = CallWindowProc(pImpl->_base_window_proc, hwnd, uMsg, wParam, lParam); switch(uMsg) { case WM_SHOWWINDOW: if(wParam != 0) pImpl->layout_dialog(); break; case WM_SIZE: pImpl->layout_dialog(); break; case WM_COMMAND: if(wParam == IDC_SHOW_PREVIEW) { const bool enable = SendMessage(pImpl->_toolbar_wnd, TB_ISBUTTONCHECKED, IDC_SHOW_PREVIEW, 0) != 0; pImpl->enable_preview(enable); } break; } return lResult; } LRESULT CALLBACK FileOpenDialogImplWin32::preview_wnd_proc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { const int CaptionPadding = 4; const int IconSize = 32; FileOpenDialogImplWin32 *pImpl = reinterpret_cast (GetWindowLongPtr(hwnd, GWLP_USERDATA)); LRESULT lResult = 0; switch(uMsg) { case WM_ERASEBKGND: // Do nothing to erase the background // - otherwise there'll be flicker lResult = 1; break; case WM_PAINT: { // Get the client rect RECT rcClient; GetClientRect(hwnd, &rcClient); // Prepare to paint PAINTSTRUCT paint_struct; HDC dc = BeginPaint(hwnd, &paint_struct); HFONT hCaptionFont = reinterpret_cast(SendMessage(GetParent(hwnd), WM_GETFONT, 0, 0)); HFONT hOldFont = static_cast(SelectObject(dc, hCaptionFont)); SetBkMode(dc, TRANSPARENT); pImpl->_mutex->lock(); if(pImpl->_path_string[0] == 0) { WCHAR* noFileText=(WCHAR*)g_utf8_to_utf16(_("No file selected"), -1, NULL, NULL, NULL); FillRect(dc, &rcClient, reinterpret_cast(COLOR_3DFACE + 1)); DrawTextW(dc, noFileText, -1, &rcClient, DT_CENTER | DT_VCENTER | DT_NOPREFIX); g_free(noFileText); } else if(pImpl->_preview_bitmap != NULL) { BITMAP bitmap; GetObject(pImpl->_preview_bitmap, sizeof(bitmap), &bitmap); const int destX = (rcClient.right - bitmap.bmWidth) / 2; // Render the image HDC hSrcDC = CreateCompatibleDC(dc); HBITMAP hOldBitmap = (HBITMAP)SelectObject(hSrcDC, pImpl->_preview_bitmap); BitBlt(dc, destX, 0, bitmap.bmWidth, bitmap.bmHeight, hSrcDC, 0, 0, SRCCOPY); SelectObject(hSrcDC, hOldBitmap); DeleteDC(hSrcDC); // Fill in the background area HRGN hEraseRgn = CreateRectRgn(rcClient.left, rcClient.top, rcClient.right, rcClient.bottom); HRGN hImageRgn = CreateRectRgn(destX, 0, destX + bitmap.bmWidth, bitmap.bmHeight); CombineRgn(hEraseRgn, hEraseRgn, hImageRgn, RGN_DIFF); FillRgn(dc, hEraseRgn, GetSysColorBrush(COLOR_3DFACE)); DeleteObject(hImageRgn); DeleteObject(hEraseRgn); // Draw the caption on RECT rcCaptionRect = {rcClient.left, rcClient.top + bitmap.bmHeight + CaptionPadding, rcClient.right, rcClient.bottom}; WCHAR szCaption[_MAX_FNAME + 32]; const int iLength = pImpl->format_caption( szCaption, sizeof(szCaption) / sizeof(WCHAR)); DrawTextW(dc, szCaption, iLength, &rcCaptionRect, DT_CENTER | DT_TOP | DT_NOPREFIX | DT_PATH_ELLIPSIS); } else if(pImpl->_preview_file_icon != NULL) { FillRect(dc, &rcClient, reinterpret_cast(COLOR_3DFACE + 1)); // Draw the files icon const int destX = (rcClient.right - IconSize) / 2; DrawIconEx(dc, destX, 0, pImpl->_preview_file_icon, IconSize, IconSize, 0, NULL, DI_NORMAL | DI_COMPAT); // Draw the caption on RECT rcCaptionRect = {rcClient.left, rcClient.top + IconSize + CaptionPadding, rcClient.right, rcClient.bottom}; WCHAR szFileName[_MAX_FNAME], szCaption[_MAX_FNAME + 32]; _wsplitpath(pImpl->_path_string, NULL, NULL, szFileName, NULL); const int iLength = snwprintf(szCaption, sizeof(szCaption), L"%ls\n%d kB", szFileName, pImpl->_preview_file_size); DrawTextW(dc, szCaption, iLength, &rcCaptionRect, DT_CENTER | DT_TOP | DT_NOPREFIX | DT_PATH_ELLIPSIS); } else { // Can't show anything! FillRect(dc, &rcClient, reinterpret_cast(COLOR_3DFACE + 1)); } pImpl->_mutex->unlock(); // Finish painting SelectObject(dc, hOldFont); EndPaint(hwnd, &paint_struct); } break; case WM_DESTROY: pImpl->free_preview(); break; default: lResult = DefWindowProc(hwnd, uMsg, wParam, lParam); break; } return lResult; } void FileOpenDialogImplWin32::enable_preview(bool enable) { if (_show_preview != enable) { Inkscape::Preferences *prefs = Inkscape::Preferences::get(); prefs->setBool("/dialogs/open/enable_preview", enable); } _show_preview = enable; // Relayout the dialog ShowWindow(_preview_wnd, enable ? SW_SHOW : SW_HIDE); layout_dialog(); // Load or unload the preview if(enable) { _mutex->lock(); _file_selected = true; _mutex->unlock(); } else free_preview(); } void FileOpenDialogImplWin32::layout_dialog() { union RECTPOINTS { RECT r; POINT p[2]; }; const float MaxExtentScale = 2.0f / 3.0f; RECT rcClient; GetClientRect(_file_dialog_wnd, &rcClient); // Re-layout the dialog HWND hFileListWnd = GetDlgItem(_file_dialog_wnd, lst2); HWND hFolderComboWnd = GetDlgItem(_file_dialog_wnd, cmb2); RECT rcFolderComboRect; RECTPOINTS rcFileList; GetWindowRect(hFileListWnd, &rcFileList.r); GetWindowRect(hFolderComboWnd, &rcFolderComboRect); const int iPadding = rcFileList.r.top - rcFolderComboRect.bottom; MapWindowPoints(NULL, _file_dialog_wnd, rcFileList.p, 2); RECT rcPreview; RECT rcBody = {rcFileList.r.left, rcFileList.r.top, rcClient.right - iPadding, rcFileList.r.bottom}; rcFileList.r.right = rcBody.right; if(_show_preview && dialogType != EXE_TYPES) { rcPreview.top = rcBody.top; rcPreview.left = rcClient.right - (rcBody.bottom - rcBody.top); const int iMaxExtent = (int)(MaxExtentScale * (float)(rcBody.left + rcBody.right)) + iPadding / 2; if(rcPreview.left < iMaxExtent) rcPreview.left = iMaxExtent; rcPreview.bottom = rcBody.bottom; rcPreview.right = rcBody.right; // Re-layout the preview box _mutex->lock(); _preview_width = rcPreview.right - rcPreview.left; _preview_height = rcPreview.bottom - rcPreview.top; _mutex->unlock(); render_preview(); MoveWindow(_preview_wnd, rcPreview.left, rcPreview.top, _preview_width, _preview_height, TRUE); rcFileList.r.right = rcPreview.left - iPadding; } // Re-layout the file list box MoveWindow(hFileListWnd, rcFileList.r.left, rcFileList.r.top, rcFileList.r.right - rcFileList.r.left, rcFileList.r.bottom - rcFileList.r.top, TRUE); // Re-layout the toolbar RECTPOINTS rcToolBar; GetWindowRect(_toolbar_wnd, &rcToolBar.r); MapWindowPoints(NULL, _file_dialog_wnd, rcToolBar.p, 2); MoveWindow(_toolbar_wnd, rcToolBar.r.left, rcToolBar.r.top, rcToolBar.r.right - rcToolBar.r.left, rcToolBar.r.bottom - rcToolBar.r.top, TRUE); } void FileOpenDialogImplWin32::file_selected() { // Destroy any previous previews free_preview(); // Determine if the file exists DWORD attributes = GetFileAttributesW(_path_string); if(attributes == 0xFFFFFFFF || attributes == FILE_ATTRIBUTE_DIRECTORY) { InvalidateRect(_preview_wnd, NULL, FALSE); return; } // Check the file exists and get the file size HANDLE file_handle = CreateFileW(_path_string, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); if(file_handle == INVALID_HANDLE_VALUE) return; const DWORD file_size = GetFileSize(file_handle, NULL); if (file_size == INVALID_FILE_SIZE) return; _preview_file_size = file_size / 1024; CloseHandle(file_handle); if(_show_preview) load_preview(); } void FileOpenDialogImplWin32::load_preview() { // Destroy any previous previews free_preview(); // Try to get the file icon SHFILEINFOW fileInfo; if(SUCCEEDED(SHGetFileInfoW(_path_string, 0, &fileInfo, sizeof(fileInfo), SHGFI_ICON | SHGFI_LARGEICON))) _preview_file_icon = fileInfo.hIcon; // Will this file be too big? if(_preview_file_size > MaxPreviewFileSize) { InvalidateRect(_preview_wnd, NULL, FALSE); return; } // Prepare to render a preview const Glib::ustring svg = ".svg"; const Glib::ustring svgz = ".svgz"; const Glib::ustring emf = ".emf"; const Glib::ustring wmf = ".wmf"; const Glib::ustring path = utf16_to_ustring(_path_string); bool success = false; _preview_document_width = _preview_document_height = 0; if ((dialogType == SVG_TYPES || dialogType == IMPORT_TYPES) && (hasSuffix(path, svg) || hasSuffix(path, svgz))) success = set_svg_preview(); else if (hasSuffix(path, emf) || hasSuffix(path, wmf)) success = set_emf_preview(); else if (isValidImageFile(path)) success = set_image_preview(); else { // Show no preview } if(success) render_preview(); InvalidateRect(_preview_wnd, NULL, FALSE); } void FileOpenDialogImplWin32::free_preview() { _mutex->lock(); if(_preview_bitmap != NULL) DeleteObject(_preview_bitmap); _preview_bitmap = NULL; if(_preview_file_icon != NULL) DestroyIcon(_preview_file_icon); _preview_file_icon = NULL; _preview_bitmap_image.reset(); _preview_emf_image = false; _mutex->unlock(); } bool FileOpenDialogImplWin32::set_svg_preview() { const int PreviewSize = 512; gchar *utf8string = g_utf16_to_utf8((const gunichar2*)_path_string, _MAX_PATH, NULL, NULL, NULL); std::unique_ptr svgDoc(SPDocument::createNewDoc(utf8string, 0)); g_free(utf8string); // Check the document loaded properly if (svgDoc == NULL) { return false; } if (svgDoc->getRoot() == NULL) { return false; } // Get the size of the document Inkscape::Util::Quantity svgWidth = svgDoc->getWidth(); Inkscape::Util::Quantity svgHeight = svgDoc->getHeight(); const double svgWidth_px = svgWidth.value("px"); const double svgHeight_px = svgHeight.value("px"); // Find the minimum scale to fit the image inside the preview area const double scaleFactorX = PreviewSize / svgWidth_px; const double scaleFactorY = PreviewSize / svgHeight_px; const double scaleFactor = (scaleFactorX > scaleFactorY) ? scaleFactorY : scaleFactorX; const double dpi = 96 * scaleFactor; Geom::Rect area(0, 0, svgWidth_px, svgHeight_px); Inkscape::Pixbuf *pixbuf = sp_generate_internal_bitmap(svgDoc.get(), area, dpi); // Tidy up if (pixbuf == NULL) { return false; } // Create the GDK pixbuf _mutex->lock(); _preview_bitmap_image = Glib::wrap(pixbuf->getPixbufRaw(), /* ref */ true); delete pixbuf; _preview_document_width = svgWidth_px; _preview_document_height = svgHeight_px; _preview_image_width = round(scaleFactor * svgWidth_px); _preview_image_height = round(scaleFactor * svgHeight_px); _mutex->unlock(); return true; } void FileOpenDialogImplWin32::destroy_svg_rendering(const guint8 *buffer) { g_assert(buffer != NULL); g_free((void*)buffer); } bool FileOpenDialogImplWin32::set_image_preview() { const Glib::ustring path = utf16_to_ustring(_path_string, _MAX_PATH); bool successful = false; _mutex->lock(); try { _preview_bitmap_image = Gdk::Pixbuf::create_from_file(path); if (_preview_bitmap_image) { _preview_image_width = _preview_bitmap_image->get_width(); _preview_document_width = _preview_image_width; _preview_image_height = _preview_bitmap_image->get_height(); _preview_document_height = _preview_image_height; successful = true; } } catch (const Gdk::PixbufError&) {} catch (const Glib::FileError&) {} _mutex->unlock(); return successful; } // Aldus Placeable Header =================================================== // Since we are a 32bit app, we have to be sure this structure compiles to // be identical to a 16 bit app's version. To do this, we use the #pragma // to adjust packing, we use a WORD for the hmf handle, and a SMALL_RECT // for the bbox rectangle. #pragma pack( push ) #pragma pack( 2 ) struct APMHEADER { DWORD dwKey; WORD hmf; SMALL_RECT bbox; WORD wInch; DWORD dwReserved; WORD wCheckSum; }; using PAPMHEADER = APMHEADER *; #pragma pack( pop ) static HENHMETAFILE MyGetEnhMetaFileW( const WCHAR *filename ) { // Try open as Enhanced Metafile HENHMETAFILE hemf = GetEnhMetaFileW(filename); if (!hemf) { // Try open as Windows Metafile HMETAFILE hmf = GetMetaFileW(filename); METAFILEPICT mp; HDC hDC; if (!hmf) { WCHAR szTemp[MAX_PATH]; DWORD dw = GetShortPathNameW( filename, szTemp, MAX_PATH ); if (dw) { hmf = GetMetaFileW( szTemp ); } } if (hmf) { // Convert Windows Metafile to Enhanced Metafile DWORD nSize = GetMetaFileBitsEx( hmf, 0, NULL ); if (nSize) { BYTE *lpvData = new BYTE[nSize]; if (lpvData) { DWORD dw = GetMetaFileBitsEx( hmf, nSize, lpvData ); if (dw) { // Fill out a METAFILEPICT structure mp.mm = MM_ANISOTROPIC; mp.xExt = 1000; mp.yExt = 1000; mp.hMF = NULL; // Get a reference DC hDC = GetDC( NULL ); // Make an enhanced metafile from the windows metafile hemf = SetWinMetaFileBits( nSize, lpvData, hDC, &mp ); // Clean up ReleaseDC( NULL, hDC ); DeleteMetaFile( hmf ); } delete[] lpvData; } else { DeleteMetaFile( hmf ); } } else { DeleteMetaFile( hmf ); } } else { // Try open as Aldus Placeable Metafile HANDLE hFile; hFile = CreateFileW( filename, GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL ); if (hFile != INVALID_HANDLE_VALUE) { DWORD nSize = GetFileSize( hFile, NULL ); if (nSize) { BYTE *lpvData = new BYTE[nSize]; if (lpvData) { DWORD dw = ReadFile( hFile, lpvData, nSize, &nSize, NULL ); if (dw) { if ( ((PAPMHEADER)lpvData)->dwKey == 0x9ac6cdd7l ) { // Fill out a METAFILEPICT structure mp.mm = MM_ANISOTROPIC; mp.xExt = ((PAPMHEADER)lpvData)->bbox.Right - ((PAPMHEADER)lpvData)->bbox.Left; mp.xExt = ( mp.xExt * 2540l ) / (DWORD)(((PAPMHEADER)lpvData)->wInch); mp.yExt = ((PAPMHEADER)lpvData)->bbox.Bottom - ((PAPMHEADER)lpvData)->bbox.Top; mp.yExt = ( mp.yExt * 2540l ) / (DWORD)(((PAPMHEADER)lpvData)->wInch); mp.hMF = NULL; // Get a reference DC hDC = GetDC( NULL ); // Create an enhanced metafile from the bits hemf = SetWinMetaFileBits( nSize, lpvData+sizeof(APMHEADER), hDC, &mp ); // Clean up ReleaseDC( NULL, hDC ); } } delete[] lpvData; } } CloseHandle( hFile ); } } } return hemf; } bool FileOpenDialogImplWin32::set_emf_preview() { _mutex->lock(); BOOL ok = FALSE; DWORD w = 0; DWORD h = 0; HENHMETAFILE hemf = MyGetEnhMetaFileW( _path_string ); if (hemf) { ENHMETAHEADER emh; ZeroMemory(&emh, sizeof(emh)); ok = GetEnhMetaFileHeader(hemf, sizeof(emh), &emh) != 0; w = (emh.rclFrame.right - emh.rclFrame.left); h = (emh.rclFrame.bottom - emh.rclFrame.top); DeleteEnhMetaFile(hemf); } if (ok) { const int PreviewSize = 512; // Get the size of the document const double emfWidth = w; const double emfHeight = h; _preview_document_width = emfWidth / 2540 * 96; // width is in units of 0.01 mm _preview_document_height = emfHeight / 2540 * 96; // height is in units of 0.01 mm _preview_image_width = emfWidth; _preview_image_height = emfHeight; _preview_emf_image = true; } _mutex->unlock(); return ok; } void FileOpenDialogImplWin32::render_preview() { double x, y; const double blurRadius = 8; const double halfBlurRadius = blurRadius / 2; const int shaddowOffsetX = 0; const int shaddowOffsetY = 2; const int pagePadding = 5; const double shaddowAlpha = 0.75; // Is the preview showing? if(!_show_preview) return; // Do we have anything to render? _mutex->lock(); if(!_preview_bitmap_image && !_preview_emf_image) { _mutex->unlock(); return; } // Tidy up any previous bitmap renderings if(_preview_bitmap != NULL) DeleteObject(_preview_bitmap); _preview_bitmap = NULL; // Calculate the size of the caption int captionHeight = 0; if(_preview_wnd != NULL) { RECT rcCaptionRect; WCHAR szCaption[_MAX_FNAME + 32]; const int iLength = format_caption(szCaption, sizeof(szCaption) / sizeof(WCHAR)); HDC dc = GetDC(_preview_wnd); DrawTextW(dc, szCaption, iLength, &rcCaptionRect, DT_CENTER | DT_TOP | DT_NOPREFIX | DT_PATH_ELLIPSIS | DT_CALCRECT); ReleaseDC(_preview_wnd, dc); captionHeight = rcCaptionRect.bottom - rcCaptionRect.top; } // Find the minimum scale to fit the image inside the preview area const double scaleFactorX = ((double)_preview_width - pagePadding * 2 - blurRadius) / _preview_image_width; const double scaleFactorY = ((double)_preview_height - pagePadding * 2 - shaddowOffsetY - halfBlurRadius - captionHeight) / _preview_image_height; const double scaleFactor = (scaleFactorX > scaleFactorY) ? scaleFactorY : scaleFactorX; // Now get the resized values const double scaledSvgWidth = scaleFactor * _preview_image_width; const double scaledSvgHeight = scaleFactor * _preview_image_height; const int svgX = pagePadding + halfBlurRadius; const int svgY = pagePadding; const int frameX = svgX - pagePadding; const int frameY = svgY - pagePadding; const int frameWidth = scaledSvgWidth + pagePadding * 2; const int frameHeight = scaledSvgHeight + pagePadding * 2; const int totalWidth = (int)ceil(frameWidth + blurRadius); const int totalHeight = (int)ceil(frameHeight + blurRadius); // Prepare the drawing surface HDC hDC = GetDC(_preview_wnd); HDC hMemDC = CreateCompatibleDC(hDC); _preview_bitmap = CreateCompatibleBitmap(hDC, totalWidth, totalHeight); HBITMAP hOldBitmap = (HBITMAP)SelectObject(hMemDC, _preview_bitmap); Cairo::RefPtr surface = Win32Surface::create(hMemDC); Cairo::RefPtr context = Context::create(surface); // Paint the background to match the dialog colour const COLORREF background = GetSysColor(COLOR_3DFACE); context->set_source_rgb( GetRValue(background) / 255.0, GetGValue(background) / 255.0, GetBValue(background) / 255.0); context->paint(); //----- Draw the drop shadow -----// // Left Edge x = frameX + shaddowOffsetX - halfBlurRadius; Cairo::RefPtr leftEdgeFade = LinearGradient::create( x, 0.0, x + blurRadius, 0.0); leftEdgeFade->add_color_stop_rgba (0, 0, 0, 0, 0); leftEdgeFade->add_color_stop_rgba (1, 0, 0, 0, shaddowAlpha); context->set_source(leftEdgeFade); context->rectangle (x, frameY + shaddowOffsetY + halfBlurRadius, blurRadius, frameHeight - blurRadius); context->fill(); // Right Edge x = frameX + frameWidth + shaddowOffsetX - halfBlurRadius; Cairo::RefPtr rightEdgeFade = LinearGradient::create( x, 0.0, x + blurRadius, 0.0); rightEdgeFade->add_color_stop_rgba (0, 0, 0, 0, shaddowAlpha); rightEdgeFade->add_color_stop_rgba (1, 0, 0, 0, 0); context->set_source(rightEdgeFade); context->rectangle (frameX + frameWidth + shaddowOffsetX - halfBlurRadius, frameY + shaddowOffsetY + halfBlurRadius, blurRadius, frameHeight - blurRadius); context->fill(); // Top Edge y = frameY + shaddowOffsetY - halfBlurRadius; Cairo::RefPtr topEdgeFade = LinearGradient::create( 0.0, y, 0.0, y + blurRadius); topEdgeFade->add_color_stop_rgba (0, 0, 0, 0, 0); topEdgeFade->add_color_stop_rgba (1, 0, 0, 0, shaddowAlpha); context->set_source(topEdgeFade); context->rectangle (frameX + shaddowOffsetX + halfBlurRadius, y, frameWidth - blurRadius, blurRadius); context->fill(); // Bottom Edge y = frameY + frameHeight + shaddowOffsetY - halfBlurRadius; Cairo::RefPtr bottomEdgeFade = LinearGradient::create( 0.0, y, 0.0, y + blurRadius); bottomEdgeFade->add_color_stop_rgba (0, 0, 0, 0, shaddowAlpha); bottomEdgeFade->add_color_stop_rgba (1, 0, 0, 0, 0); context->set_source(bottomEdgeFade); context->rectangle (frameX + shaddowOffsetX + halfBlurRadius, y, frameWidth - blurRadius, blurRadius); context->fill(); // Top Left Corner x = frameX + shaddowOffsetX - halfBlurRadius; y = frameY + shaddowOffsetY - halfBlurRadius; Cairo::RefPtr topLeftCornerFade = RadialGradient::create( x + blurRadius, y + blurRadius, 0, x + blurRadius, y + blurRadius, blurRadius); topLeftCornerFade->add_color_stop_rgba (0, 0, 0, 0, shaddowAlpha); topLeftCornerFade->add_color_stop_rgba (1, 0, 0, 0, 0); context->set_source(topLeftCornerFade); context->rectangle (x, y, blurRadius, blurRadius); context->fill(); // Top Right Corner x = frameX + frameWidth + shaddowOffsetX - halfBlurRadius; y = frameY + shaddowOffsetY - halfBlurRadius; Cairo::RefPtr topRightCornerFade = RadialGradient::create( x, y + blurRadius, 0, x, y + blurRadius, blurRadius); topRightCornerFade->add_color_stop_rgba (0, 0, 0, 0, shaddowAlpha); topRightCornerFade->add_color_stop_rgba (1, 0, 0, 0, 0); context->set_source(topRightCornerFade); context->rectangle (x, y, blurRadius, blurRadius); context->fill(); // Bottom Left Corner x = frameX + shaddowOffsetX - halfBlurRadius; y = frameY + frameHeight + shaddowOffsetY - halfBlurRadius; Cairo::RefPtr bottomLeftCornerFade = RadialGradient::create( x + blurRadius, y, 0, x + blurRadius, y, blurRadius); bottomLeftCornerFade->add_color_stop_rgba (0, 0, 0, 0, shaddowAlpha); bottomLeftCornerFade->add_color_stop_rgba (1, 0, 0, 0, 0); context->set_source(bottomLeftCornerFade); context->rectangle (x, y, blurRadius, blurRadius); context->fill(); // Bottom Right Corner x = frameX + frameWidth + shaddowOffsetX - halfBlurRadius; y = frameY + frameHeight + shaddowOffsetY - halfBlurRadius; Cairo::RefPtr bottomRightCornerFade = RadialGradient::create( x, y, 0, x, y, blurRadius); bottomRightCornerFade->add_color_stop_rgba (0, 0, 0, 0, shaddowAlpha); bottomRightCornerFade->add_color_stop_rgba (1, 0, 0, 0, 0); context->set_source(bottomRightCornerFade); context->rectangle (frameX + frameWidth + shaddowOffsetX - halfBlurRadius, frameY + frameHeight + shaddowOffsetY - halfBlurRadius, blurRadius, blurRadius); context->fill(); // Draw the frame context->set_line_width(1); context->rectangle (frameX, frameY, frameWidth, frameHeight); context->set_source_rgb(1.0, 1.0, 1.0); context->fill_preserve(); context->set_source_rgb(0.25, 0.25, 0.25); context->stroke_preserve(); // Draw the image if(_preview_bitmap_image) // Is the image a pixbuf? { // Set the transformation const Cairo::Matrix matrix( scaleFactor, 0, 0, scaleFactor, svgX, svgY); context->set_matrix (matrix); // Render the image set_source_pixbuf (context, _preview_bitmap_image, 0, 0); context->paint(); // Reset the transformation context->set_identity_matrix(); } // Draw the inner frame context->set_source_rgb(0.75, 0.75, 0.75); context->rectangle (svgX, svgY, scaledSvgWidth, scaledSvgHeight); context->stroke(); _mutex->unlock(); // Finish drawing surface->finish(); if (_preview_emf_image) { HENHMETAFILE hemf = MyGetEnhMetaFileW(_path_string); if (hemf) { RECT rc; rc.top = svgY+2; rc.left = svgX+2; rc.bottom = scaledSvgHeight-2; rc.right = scaledSvgWidth-2; PlayEnhMetaFile(hMemDC, hemf, &rc); DeleteEnhMetaFile(hemf); } } SelectObject(hMemDC, hOldBitmap) ; DeleteDC(hMemDC); // Refresh the preview pane InvalidateRect(_preview_wnd, NULL, FALSE); } int FileOpenDialogImplWin32::format_caption(wchar_t *caption, int caption_size) { wchar_t szFileName[_MAX_FNAME]; _wsplitpath(_path_string, NULL, NULL, szFileName, NULL); return snwprintf(caption, caption_size, L"%ls\n%d\u2009kB\n%d\u2009px \xD7 %d\u2009px", szFileName, _preview_file_size, (int)_preview_document_width, (int)_preview_document_height); } /** * Show this dialog modally. Return true if user hits [OK] */ bool FileOpenDialogImplWin32::show() { // We can only run one worker thread at a time if(_mutex != NULL) return false; _result = false; _finished = false; _file_selected = false; _main_loop = g_main_loop_new(g_main_context_default(), FALSE); _mutex = std::make_unique(); std::thread thethread([this] { GetOpenFileName_thread(); }); { while(1) { g_main_context_iteration(g_main_context_default(), FALSE); if(_mutex->try_lock()) { // Read mutexed data const bool finished = _finished; const bool is_file_selected = _file_selected; _file_selected = false; _mutex->unlock(); if(finished) break; if(is_file_selected) file_selected(); } Sleep(10); } } thethread.join(); // Tidy up _mutex = NULL; return _result; } /** * To Get Multiple filenames selected at-once. */ std::vectorFileOpenDialogImplWin32::getFilenames() { std::vector result; result.push_back(getFilename()); return result; } /*######################################################################### ### F I L E S A V E #########################################################################*/ /** * Constructor */ FileSaveDialogImplWin32::FileSaveDialogImplWin32(Gtk::Window &parent, const Glib::ustring &dir, FileDialogType fileTypes, const char *title, const Glib::ustring &/*default_key*/, const char *docTitle, const Inkscape::Extension::FileSaveMethod save_method) : FileDialogBaseWin32(parent, dir, title, fileTypes, (save_method == Inkscape::Extension::FILE_SAVE_METHOD_SAVE_COPY) ? "dialogs.save_copy" : "dialogs.save_as") , save_method(save_method) , _title_label(NULL) , _title_edit(NULL) { FileSaveDialog::myDocTitle = docTitle; if (dialogType != CUSTOM_TYPE) createFilterMenu(); /* The code below sets the default file name */ myFilename = ""; if (dir.size() > 0) { Glib::ustring udir(dir); Glib::ustring::size_type len = udir.length(); // leaving a trailing backslash on the directory name leads to the infamous // double-directory bug on win32 if (len != 0 && udir[len - 1] == '\\') udir.erase(len - 1); // Remove the extension: remove everything past the last period found past the last slash // (not for CUSTOM_TYPE as we can not automatically add a file extension in that case yet) if (dialogType == CUSTOM_TYPE) { myFilename = udir; } else { size_t last_slash_index = udir.find_last_of( '\\' ); size_t last_period_index = udir.find_last_of( '.' ); if (last_period_index > last_slash_index) { myFilename = udir.substr(0, last_period_index ); } } // remove one slash if double if (1 + myFilename.find("\\\\",2)) { myFilename.replace(myFilename.find("\\\\",2), 1, ""); } } } FileSaveDialogImplWin32::~FileSaveDialogImplWin32() { } void FileSaveDialogImplWin32::createFilterMenu() { std::list filter_list; knownExtensions.clear(); // Compose the filter string Glib::ustring all_inkscape_files_filter, all_image_files_filter; Inkscape::Extension::DB::OutputList extension_list; Inkscape::Extension::db.get_output_list(extension_list); int filter_count = 0; int filter_length = 1; bool is_raster = dialogType == RASTER_TYPES; for (auto omod : extension_list) { // FIXME: would be nice to grey them out instead of not listing them if (omod->deactivated() || (omod->is_raster() != is_raster)) continue; // This extension is limited to save copy only. if (omod->savecopy_only() && save_method != Inkscape::Extension::FILE_SAVE_METHOD_SAVE_COPY) continue; filter_count++; Filter filter; // Extension const gchar *filter_extension = omod->get_extension(); filter.filter = g_utf8_to_utf16( filter_extension, -1, NULL, &filter.filter_length, NULL); knownExtensions.insert(std::pair(Glib::ustring(filter_extension).casefold(), omod)); // Type filter.name = g_utf8_to_utf16(omod->get_filetypename(true), -1, NULL, &filter.name_length, NULL); filter.mod = omod; filter_length += filter.name_length + filter.filter_length + 3; // Add 3 for two \0s and a * filter_list.push_back(filter); } int extension_index = 0; _extension_map = new Inkscape::Extension::Extension*[filter_count]; _filter = new wchar_t[filter_length]; wchar_t *filterptr = _filter; for(std::list::iterator filter_iterator = filter_list.begin(); filter_iterator != filter_list.end(); ++filter_iterator) { const Filter &filter = *filter_iterator; wcsncpy(filterptr, (wchar_t*)filter.name, filter.name_length); filterptr += filter.name_length; g_free(filter.name); *(filterptr++) = L'\0'; *(filterptr++) = L'*'; wcsncpy(filterptr, (wchar_t*)filter.filter, filter.filter_length); filterptr += filter.filter_length; g_free(filter.filter); *(filterptr++) = L'\0'; // Associate this input extension with the file type name _extension_map[extension_index++] = filter.mod; } *(filterptr++) = 0; _filter_count = extension_index; _filter_index = 1; // A value of 1 selects the 1st filter - NOT the 2nd } void FileSaveDialogImplWin32::addFileType(Glib::ustring name, Glib::ustring pattern) { std::list filter_list; knownExtensions.clear(); Filter all_exe_files; const gchar *all_exe_files_filter_name = name.data(); const gchar *all_exe_files_filter = pattern.data(); // Calculate the amount of memory required int filter_count = 1; int filter_length = 1; // Filter Executable Files all_exe_files.name = g_utf8_to_utf16(all_exe_files_filter_name, -1, NULL, &all_exe_files.name_length, NULL); all_exe_files.filter = g_utf8_to_utf16(all_exe_files_filter, -1, NULL, &all_exe_files.filter_length, NULL); all_exe_files.mod = NULL; filter_list.push_front(all_exe_files); filter_length = all_exe_files.name_length + all_exe_files.filter_length + 3; // Add 3 for two \0s and a * knownExtensions.insert(std::pair(Glib::ustring(all_exe_files_filter).casefold(), NULL)); int extension_index = 0; _extension_map = new Inkscape::Extension::Extension*[filter_count]; _filter = new wchar_t[filter_length]; wchar_t *filterptr = _filter; for(std::list::iterator filter_iterator = filter_list.begin(); filter_iterator != filter_list.end(); ++filter_iterator) { const Filter &filter = *filter_iterator; wcsncpy(filterptr, (wchar_t*)filter.name, filter.name_length); filterptr += filter.name_length; g_free(filter.name); *(filterptr++) = L'\0'; *(filterptr++) = L'*'; if(filter.filter != NULL) { wcsncpy(filterptr, (wchar_t*)filter.filter, filter.filter_length); filterptr += filter.filter_length; g_free(filter.filter); } *(filterptr++) = L'\0'; // Associate this input extension with the file type name _extension_map[extension_index++] = filter.mod; } *(filterptr++) = L'\0'; _filter_count = extension_index; _filter_index = 1; // Select the 1st filter in the list } void FileSaveDialogImplWin32::GetSaveFileName_thread() { OPENFILENAMEW ofn; g_assert(_main_loop != NULL); WCHAR* current_directory_string = (WCHAR*)g_utf8_to_utf16( _current_directory.data(), _current_directory.length(), NULL, NULL, NULL); // Copy the selected file name, converting from UTF-8 to UTF-16 memset(_path_string, 0, sizeof(_path_string)); gunichar2* utf16_path_string = g_utf8_to_utf16( myFilename.data(), -1, NULL, NULL, NULL); wcsncpy(_path_string, (wchar_t*)utf16_path_string, _MAX_PATH); g_free(utf16_path_string); ZeroMemory(&ofn, sizeof(ofn)); ofn.lStructSize = sizeof(ofn); ofn.hwndOwner = _ownerHwnd; ofn.lpstrFile = _path_string; ofn.nMaxFile = _MAX_PATH; ofn.nFilterIndex = _filter_index; ofn.lpstrFileTitle = NULL; ofn.nMaxFileTitle = 0; ofn.lpstrInitialDir = current_directory_string; ofn.lpstrTitle = _title; ofn.Flags = OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST | OFN_EXPLORER | OFN_ENABLEHOOK | OFN_ENABLESIZING; ofn.lpstrFilter = _filter; ofn.nFilterIndex = _filter_index; ofn.lpfnHook = GetSaveFileName_hookproc; ofn.lpstrDefExt = L"svg\0"; ofn.lCustData = (LPARAM)this; _result = GetSaveFileNameW(&ofn) != 0; g_assert(ofn.nFilterIndex >= 1 && ofn.nFilterIndex <= _filter_count); _filter_index = ofn.nFilterIndex; _extension = _extension_map[ofn.nFilterIndex - 1]; // Copy the selected file name, converting from UTF-16 to UTF-8 myFilename = utf16_to_ustring(_path_string, _MAX_PATH); // Tidy up g_free(current_directory_string); g_main_loop_quit(_main_loop); } /** * Show this dialog modally. Return true if user hits [OK] */ bool FileSaveDialogImplWin32::show() { _result = false; _main_loop = g_main_loop_new(g_main_context_default(), FALSE); if(_main_loop != NULL) { std::thread thethread([this] { GetSaveFileName_thread(); }); g_main_loop_run(_main_loop); if(_result && _extension) appendExtension(myFilename, (Inkscape::Extension::Output*)_extension); thethread.join(); } return _result; } void FileSaveDialogImplWin32::setSelectionType( Inkscape::Extension::Extension * /*key*/ ) { // If no pointer to extension is passed in, look up based on filename extension. } UINT_PTR CALLBACK FileSaveDialogImplWin32::GetSaveFileName_hookproc( HWND hdlg, UINT uiMsg, WPARAM, LPARAM lParam) { FileSaveDialogImplWin32 *pImpl = reinterpret_cast (GetWindowLongPtr(hdlg, GWLP_USERDATA)); switch(uiMsg) { case WM_INITDIALOG: { HWND hParentWnd = GetParent(hdlg); HINSTANCE hInstance = GetModuleHandle(NULL); // get size/pos of typical combo box RECT rEDT1, rCB1, rROOT, rST; GetWindowRect(GetDlgItem(hParentWnd, cmb1), &rCB1); GetWindowRect(GetDlgItem(hParentWnd, cmb13), &rEDT1); GetWindowRect(GetDlgItem(hParentWnd, stc2), &rST); GetWindowRect(hdlg, &rROOT); int ydelta = rCB1.top - rEDT1.top; if ( ydelta < 0 ) { g_warning("Negative dialog ydelta"); ydelta = 0; } // Make the window a bit longer // Note: we have a width delta of 1 because there is a suspicion that MoveWindow() to the same size causes zero-width results. RECT rcRect; GetWindowRect(hParentWnd, &rcRect); MoveWindow(hParentWnd, rcRect.left, rcRect.top, sanitizeWindowSizeParam( rcRect.right - rcRect.left, 1, WINDOW_WIDTH_MINIMUM, WINDOW_WIDTH_FALLBACK ), sanitizeWindowSizeParam( rcRect.bottom - rcRect.top, ydelta, WINDOW_HEIGHT_MINIMUM, WINDOW_HEIGHT_FALLBACK ), FALSE); // It is not necessary to delete stock objects by calling DeleteObject HGDIOBJ dlgFont = GetStockObject(DEFAULT_GUI_FONT); // Set the pointer to the object OPENFILENAMEW *ofn = reinterpret_cast(lParam); SetWindowLongPtr(hdlg, GWLP_USERDATA, ofn->lCustData); SetWindowLongPtr(hParentWnd, GWLP_USERDATA, ofn->lCustData); pImpl = reinterpret_cast(ofn->lCustData); // Create the Title label and edit control wchar_t *title_label_str = (wchar_t *)g_utf8_to_utf16(_("Title:"), -1, NULL, NULL, NULL); pImpl->_title_label = CreateWindowExW(0, L"STATIC", title_label_str, WS_VISIBLE|WS_CHILD, CW_USEDEFAULT, CW_USEDEFAULT, rCB1.left-rST.left, rST.bottom-rST.top, hParentWnd, NULL, hInstance, NULL); g_free(title_label_str); if(pImpl->_title_label) { if(dlgFont) SendMessage(pImpl->_title_label, WM_SETFONT, (WPARAM)dlgFont, MAKELPARAM(FALSE, 0)); SetWindowPos(pImpl->_title_label, NULL, rST.left-rROOT.left, rST.top+ydelta-rROOT.top, rCB1.left-rST.left, rST.bottom-rST.top, SWP_SHOWWINDOW|SWP_NOZORDER); } pImpl->_title_edit = CreateWindowEx(WS_EX_CLIENTEDGE, "EDIT", "", WS_VISIBLE|WS_CHILD|WS_TABSTOP|ES_AUTOHSCROLL, CW_USEDEFAULT, CW_USEDEFAULT, rCB1.right-rCB1.left, rCB1.bottom-rCB1.top, hParentWnd, NULL, hInstance, NULL); if(pImpl->_title_edit) { if(dlgFont) SendMessage(pImpl->_title_edit, WM_SETFONT, (WPARAM)dlgFont, MAKELPARAM(FALSE, 0)); SetWindowPos(pImpl->_title_edit, NULL, rCB1.left-rROOT.left, rCB1.top+ydelta-rROOT.top, rCB1.right-rCB1.left, rCB1.bottom-rCB1.top, SWP_SHOWWINDOW|SWP_NOZORDER); SetWindowTextW(pImpl->_title_edit, (const wchar_t*)g_utf8_to_utf16(pImpl->myDocTitle.c_str(), -1, NULL, NULL, NULL)); } } break; case WM_DESTROY: { if(pImpl->_title_edit) { int length = GetWindowTextLengthW(pImpl->_title_edit)+1; wchar_t* temp_title = new wchar_t[length]; GetWindowTextW(pImpl->_title_edit, temp_title, length); pImpl->myDocTitle = g_utf16_to_utf8((gunichar2*)temp_title, -1, NULL, NULL, NULL); delete[] temp_title; DestroyWindow(pImpl->_title_label); pImpl->_title_label = NULL; DestroyWindow(pImpl->_title_edit); pImpl->_title_edit = NULL; } } break; } // Use default dialog behaviour return 0; } } } } // namespace Dialog, UI, Inkscape #endif // ifdef _WIN32 /* Local Variables: mode:c++ c-file-style:"stroustrup" c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) indent-tabs-mode:nil fill-column:99 End: */ // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :