KiCad PCB EDA Suite
Loading...
Searching...
No Matches
native_model_file_picker_windows.cpp
Go to the documentation of this file.
1/*
2 * This program source code file is part of KiCad, a free EDA CAD application.
3 *
4 * Copyright The KiCad Developers, see AUTHORS.txt for contributors.
5 *
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 3 of the License, or (at your option)
9 * any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, you may find one here:
18 * http://www.gnu.org/licenses/old-licenses/gpl-3.0.html
19 */
20
22
23#include <trace_helpers.h>
24
25#include <wx/filedlg.h>
26#include <wx/log.h>
27#include <wx/translation.h>
28#include <wx/utils.h>
29#include <wx/window.h>
30
31#include <shobjidl.h>
32
33#include <vector>
34
35namespace
36{
37constexpr DWORD EMBED_FILE_CONTROL_ID = 0x4B33444D;
38
39template <typename T>
40class COM_PTR
41{
42public:
43 COM_PTR() = default;
44 COM_PTR( const COM_PTR& ) = delete;
45 COM_PTR& operator=( const COM_PTR& ) = delete;
46
47 ~COM_PTR()
48 {
49 if( m_pointer )
50 m_pointer->Release();
51 }
52
53 T* Get() const { return m_pointer; }
54 T** Put()
55 {
56 if( m_pointer )
57 {
58 m_pointer->Release();
59 m_pointer = nullptr;
60 }
61
62 return &m_pointer;
63 }
64 T* operator->() const { return m_pointer; }
65 explicit operator bool() const { return m_pointer != nullptr; }
66
67private:
68 T* m_pointer = nullptr;
69};
70
71class COTASKMEM_STRING
72{
73public:
74 COTASKMEM_STRING() = default;
75 COTASKMEM_STRING( const COTASKMEM_STRING& ) = delete;
76 COTASKMEM_STRING& operator=( const COTASKMEM_STRING& ) = delete;
77
78 ~COTASKMEM_STRING() { CoTaskMemFree( m_pointer ); }
79
80 PWSTR* Put() { return &m_pointer; }
81 PWSTR Get() const { return m_pointer; }
82
83private:
84 PWSTR m_pointer = nullptr;
85};
86
87class COM_APARTMENT
88{
89public:
90 COM_APARTMENT() :
91 m_result( CoInitializeEx( nullptr, COINIT_APARTMENTTHREADED ) )
92 {
93 }
94
95 ~COM_APARTMENT()
96 {
97 if( SUCCEEDED( m_result ) )
98 CoUninitialize();
99 }
100
101 HRESULT Result() const { return m_result; }
102
103private:
104 HRESULT m_result;
105};
106
107} // namespace
108
109
112{
114 COM_APARTMENT apartment;
115
116 if( FAILED( apartment.Result() ) )
117 {
118 wxLogTrace( traceModelPreview, wxS( "Unable to enter a COM apartment for the model picker: 0x%08lx" ),
119 static_cast<unsigned long>( apartment.Result() ) );
120 return result;
121 }
122
123 COM_PTR<IFileOpenDialog> dialog;
124 HRESULT hr = CoCreateInstance( CLSID_FileOpenDialog, nullptr, CLSCTX_INPROC_SERVER, IID_PPV_ARGS( dialog.Put() ) );
125
126 if( FAILED( hr ) )
127 {
128 wxLogTrace( traceModelPreview, wxS( "Unable to create the Windows model picker: 0x%08lx" ),
129 static_cast<unsigned long>( hr ) );
130 return result;
131 }
132
133 FILEOPENDIALOGOPTIONS options =
134 FOS_FORCEFILESYSTEM | FOS_PATHMUSTEXIST | FOS_FILEMUSTEXIST | FOS_FORCEPREVIEWPANEON;
135
136 if( !( aOptions.style & wxFD_CHANGE_DIR ) )
137 options |= FOS_NOCHANGEDIR;
138
139 if( aOptions.style & wxFD_SHOW_HIDDEN )
140 options |= FOS_FORCESHOWHIDDEN;
141
142 if( aOptions.style & wxFD_NO_FOLLOW )
143 options |= FOS_NODEREFERENCELINKS;
144
145 hr = dialog->SetOptions( options );
146
147 if( SUCCEEDED( hr ) && !aOptions.title.empty() )
148 hr = dialog->SetTitle( aOptions.title.wc_str() );
149
150 if( SUCCEEDED( hr ) && !aOptions.filename.empty() )
151 hr = dialog->SetFileName( aOptions.filename.wc_str() );
152
153 COM_PTR<IShellItem> initialFolder;
154
155 if( SUCCEEDED( hr ) && !aOptions.directory.empty() )
156 {
157 HRESULT folderResult = SHCreateItemFromParsingName( aOptions.directory.wc_str(), nullptr,
158 IID_PPV_ARGS( initialFolder.Put() ) );
159
160 if( SUCCEEDED( folderResult ) )
161 dialog->SetFolder( initialFolder.Get() );
162 }
163
164 // wxParseCommonDialogsFilter is what the rest of KiCad parses these strings with, and it
165 // already falls back to a single "all files" entry for anything it cannot split.
166 wxArrayString descriptions;
167 wxArrayString patterns;
168 wxParseCommonDialogsFilter( aOptions.wildcard, descriptions, patterns );
169
170 std::vector<COMDLG_FILTERSPEC> filterSpecs;
171 filterSpecs.reserve( descriptions.GetCount() );
172
173 for( size_t i = 0; i < descriptions.GetCount(); ++i )
174 filterSpecs.push_back( { descriptions[i].wc_str(), patterns[i].wc_str() } );
175
176 if( SUCCEEDED( hr ) )
177 hr = dialog->SetFileTypes( static_cast<UINT>( filterSpecs.size() ), filterSpecs.data() );
178
179 if( SUCCEEDED( hr ) )
180 {
181 UINT filterIndex = aOptions.filterIndex >= 0 && static_cast<size_t>( aOptions.filterIndex ) < filterSpecs.size()
182 ? static_cast<UINT>( aOptions.filterIndex ) + 1
183 : 1;
184 hr = dialog->SetFileTypeIndex( filterIndex );
185 }
186
187 COM_PTR<IFileDialogCustomize> customize;
188 bool hasEmbedControl = false;
189
190 if( SUCCEEDED( hr ) )
191 {
192 HRESULT customizeResult = dialog->QueryInterface( IID_PPV_ARGS( customize.Put() ) );
193
194 if( SUCCEEDED( customizeResult ) )
195 {
196 customizeResult = customize->AddCheckButton( EMBED_FILE_CONTROL_ID, _( "Embed file" ).wc_str(),
197 aOptions.embedFile ? TRUE : FALSE );
198 hasEmbedControl = SUCCEEDED( customizeResult );
199 }
200
201 if( FAILED( customizeResult ) )
202 {
203 wxLogTrace( traceModelPreview, wxS( "Unable to add the Embed file control: 0x%08lx" ),
204 static_cast<unsigned long>( customizeResult ) );
205 }
206 }
207
208 wxWindow* ownerWindow = aOptions.parent ? wxGetTopLevelParent( aOptions.parent ) : nullptr;
209 HWND owner = ownerWindow ? static_cast<HWND>( ownerWindow->GetHandle() ) : nullptr;
210
211 if( SUCCEEDED( hr ) )
212 {
213 wxWindowDisabler disableOtherWindows( nullptr, ownerWindow );
214 hr = dialog->Show( owner );
215 }
216
217 if( owner && IsWindow( owner ) )
218 SetForegroundWindow( owner );
219
220 if( hr == HRESULT_FROM_WIN32( ERROR_CANCELLED ) )
221 return result;
222
223 if( FAILED( hr ) )
224 {
225 wxLogTrace( traceModelPreview, wxS( "Windows model picker failed: 0x%08lx" ),
226 static_cast<unsigned long>( hr ) );
227 return result;
228 }
229
230 COM_PTR<IShellItem> selectedItem;
231 hr = dialog->GetResult( selectedItem.Put() );
232
233 COTASKMEM_STRING selectedPath;
234
235 if( SUCCEEDED( hr ) )
236 hr = selectedItem->GetDisplayName( SIGDN_FILESYSPATH, selectedPath.Put() );
237
238 BOOL embedFile = aOptions.embedFile ? TRUE : FALSE;
239
240 if( SUCCEEDED( hr ) && hasEmbedControl )
241 {
242 HRESULT checkboxResult = customize->GetCheckButtonState( EMBED_FILE_CONTROL_ID, &embedFile );
243
244 if( FAILED( checkboxResult ) )
245 {
246 wxLogTrace( traceModelPreview, wxS( "Unable to read the Embed file control: 0x%08lx" ),
247 static_cast<unsigned long>( checkboxResult ) );
248 return result;
249 }
250 }
251
252 if( SUCCEEDED( hr ) )
253 {
254 UINT filterIndex = 1;
255
256 if( SUCCEEDED( dialog->GetFileTypeIndex( &filterIndex ) ) && filterIndex > 0 )
257 result.filterIndex = static_cast<int>( filterIndex - 1 );
258
259 result.accepted = true;
260 result.path = selectedPath.Get();
261 result.embedFile = embedFile != FALSE;
262 }
263
264 return result;
265}
#define _(s)
const wxChar *const traceModelPreview
Flag to enable native operating-system model preview debug output.
NATIVE_MODEL_FILE_PICKER_RESULT ShowNativeModelFilePickerWindows(const NATIVE_MODEL_FILE_PICKER_OPTIONS &aOptions)
wxString result
Test unit parsing edge cases and error handling.
wxLogTrace helper definitions.