1
2 //
3 // This source file is part of appleseed.
4 // Visit https://appleseedhq.net/ for additional information and resources.
5 //
6 // This software is released under the MIT license.
7 //
8 // Copyright (c) 2010-2013 Francois Beaune, Jupiter Jazz Limited
9 // Copyright (c) 2014-2018 Francois Beaune, The appleseedhq Organization
10 //
11 // Permission is hereby granted, free of charge, to any person obtaining a copy
12 // of this software and associated documentation files (the "Software"), to deal
13 // in the Software without restriction, including without limitation the rights
14 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
15 // copies of the Software, and to permit persons to whom the Software is
16 // furnished to do so, subject to the following conditions:
17 //
18 // The above copyright notice and this permission notice shall be included in
19 // all copies or substantial portions of the Software.
20 //
21 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
22 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
23 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
24 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
25 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
26 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
27 // THE SOFTWARE.
28 //
29
30 // Interface header.
31 #include "miscellaneous.h"
32
33 // appleseed.studio headers.
34 #include "utility/interop.h"
35 #include "utility/settingskeys.h"
36
37 // appleseed.shared headers.
38 #include "application/application.h"
39
40 // appleseed.renderer headers.
41 #include "renderer/api/utility.h"
42
43 // appleseed.foundation headers.
44 #include "foundation/utility/string.h"
45
46 // OpenImageIO headers.
47 #include "foundation/platform/_beginoiioheaders.h"
48 #include "OpenImageIO/texture.h"
49 #include "foundation/platform/_endoiioheaders.h"
50
51 // Qt headers.
52 #include <QDir>
53 #include <QFile>
54 #include <QFileInfo>
55 #include <QGridLayout>
56 #include <QIcon>
57 #include <QKeySequence>
58 #include <QLayout>
59 #include <QLayoutItem>
60 #include <QMessageBox>
61 #include <QMutex>
62 #include <QMutexLocker>
63 #include <QPixmap>
64 #include <QShortcut>
65 #include <QSpacerItem>
66 #include <QString>
67 #include <QStringList>
68 #include <Qt>
69 #include <QWidget>
70
71 // Standard headers.
72 #include <sstream>
73 #include <string>
74 #include <vector>
75
76 using namespace appleseed::shared;
77 using namespace foundation;
78 using namespace renderer;
79 using namespace std;
80
81 namespace appleseed {
82 namespace studio {
83
84 const QString g_qt_image_files_filter = "Bitmap Files (*.bmp *.jpg *.png *.tif);;BMP (*.bmp);;JPEG (*.jpg);;PNG (*.png);;TIFF (*.tif);;All Files (*.*)";
85
86 namespace
87 {
join_exts(const vector<string> & exts)88 string join_exts(const vector<string>& exts)
89 {
90 stringstream sstr;
91
92 for (size_t i = 0, e = exts.size(); i < e; ++i)
93 {
94 if (i > 0)
95 sstr << " ";
96 sstr << "*." << exts[i];
97 }
98
99 return sstr.str();
100 }
101 }
102
get_oiio_image_files_filter()103 QString get_oiio_image_files_filter()
104 {
105 static QString filter;
106 static QMutex mutex;
107
108 QMutexLocker locker(&mutex);
109
110 if (filter.isEmpty())
111 {
112 stringstream sstr;
113
114 // Retrieve the extension list as a string of the form "bmp:bmp;openexr:exr,sxr,mxr;png:png".
115 string extension_list;
116 OIIO::getattribute("extension_list", extension_list);
117
118 // Split the extension list into an array of the form [ "bmp:bmp", "openexr:exr,sxr,mxr", "png:png" ].
119 vector<string> formats;
120 split(extension_list, ";", formats);
121
122 // Collect all extensions into an array of the form [ "bmp", "exr", "png" ].
123 vector<string> all_exts;
124 for (const auto& format : formats)
125 {
126 const auto sep = format.find_first_of(':');
127 const auto extlist = format.substr(sep + 1);
128 split(extlist, ",", all_exts);
129 }
130
131 // The first filter shows all bitmap files.
132 sstr << "Bitmap Files (" << join_exts(all_exts) << ");;";
133
134 // Then one filter per file format.
135 for (const auto& format : formats)
136 {
137 const auto sep = format.find_first_of(':');
138 const auto name = format.substr(0, sep);
139 const auto extlist = format.substr(sep + 1);
140 vector<string> exts;
141 split(extlist, ",", exts);
142 sstr << upper_case(name) << " Files (" << join_exts(exts) << ");;";
143 }
144
145 // The last filter shows all files.
146 sstr << "All Files (*.*)";
147
148 filter = QString::fromStdString(sstr.str());
149 }
150
151 return filter;
152 }
153
get_project_files_filter(const int filters)154 QString get_project_files_filter(const int filters)
155 {
156 QStringList filter_list;
157
158 if (filters & ProjectFilesFilterAllProjects)
159 filter_list << "Project Files (*.appleseed *.appleseedz)";
160
161 if (filters & ProjectFilesFilterPlainProjects)
162 filter_list << "Plain Project Files (*.appleseed)";
163
164 if (filters & ProjectFilesFilterPackedProjects)
165 filter_list << "Packed Project Files (*.appleseedz)";
166
167 filter_list << "All Files (*.*)";
168
169 return filter_list.join(";;");
170 }
171
combine_paths(const QString & lhs,const QString & rhs)172 QString combine_paths(const QString& lhs, const QString& rhs)
173 {
174 QString result(lhs);
175
176 if (!result.endsWith(QDir::separator()))
177 result.append(QDir::separator());
178
179 result.append(rhs);
180
181 return QDir::toNativeSeparators(result);
182 }
183
make_app_path(const QString & path)184 QString make_app_path(const QString& path)
185 {
186 return combine_paths(Application::get_root_path(), path);
187 }
188
combine_name_and_shortcut(const QString & name,const QKeySequence & shortcut)189 QString combine_name_and_shortcut(const QString& name, const QKeySequence& shortcut)
190 {
191 return name + " (" + shortcut.toString(QKeySequence::NativeText) + ")";
192 }
193
file_exists(const QString & path)194 bool file_exists(const QString& path)
195 {
196 const QFileInfo info(path);
197 return info.exists() && info.isFile();
198 }
199
load_gl_shader(const QString & base_name)200 QByteArray load_gl_shader(const QString& base_name)
201 {
202 const QString resource_path(QString(":/shaders/%1").arg(base_name));
203
204 QFile file(resource_path);
205 file.open(QFile::ReadOnly);
206
207 return file.readAll();
208 }
209
load_icons(const QString & base_name)210 QIcon load_icons(const QString& base_name)
211 {
212 const QString base_icon_filepath(make_app_path("icons/%1.png").arg(base_name));
213 const QString hover_icon_filepath(make_app_path("icons/%1_hover.png").arg(base_name));
214 const QString disabled_icon_filepath(make_app_path("icons/%1_disabled.png").arg(base_name));
215
216 QIcon icon(base_icon_filepath);
217
218 if (file_exists(hover_icon_filepath))
219 icon.addPixmap(QPixmap(hover_icon_filepath), QIcon::Active);
220
221 if (file_exists(disabled_icon_filepath))
222 icon.addPixmap(QPixmap(disabled_icon_filepath), QIcon::Disabled);
223
224 return icon;
225 }
226
227 namespace
228 {
get_value(const ParamArray & settings,const QString & key)229 QString get_value(const ParamArray& settings, const QString& key)
230 {
231 return settings.get_path_optional<QString>(key.toUtf8().constData());
232 }
233
set_value(ParamArray & settings,const QString & key,const QString & value)234 void set_value(ParamArray& settings, const QString& key, const QString& value)
235 {
236 settings.insert_path(key.toUtf8().constData(), value);
237 }
238 }
239
get_open_filename(QWidget * parent,const QString & caption,const QString & filter,ParamArray & settings,const QString & settings_key,QFileDialog::Options options)240 QString get_open_filename(
241 QWidget* parent,
242 const QString& caption,
243 const QString& filter,
244 ParamArray& settings,
245 const QString& settings_key,
246 QFileDialog::Options options)
247 {
248 const QString dir = get_value(settings, settings_key + SETTINGS_LAST_DIRECTORY);
249 QString selected_filter = get_value(settings, settings_key + SETTINGS_SELECTED_FILTER);
250
251 const QString filepath =
252 QFileDialog::getOpenFileName(
253 parent,
254 caption,
255 dir,
256 filter,
257 &selected_filter,
258 options);
259
260 set_value(settings, settings_key + SETTINGS_SELECTED_FILTER, selected_filter);
261
262 if (filepath.isEmpty())
263 return filepath;
264
265 set_value(
266 settings,
267 settings_key + SETTINGS_LAST_DIRECTORY,
268 QDir::toNativeSeparators(QFileInfo(filepath).path()));
269
270 return filepath;
271 }
272
get_open_filenames(QWidget * parent,const QString & caption,const QString & filter,ParamArray & settings,const QString & settings_key,QFileDialog::Options options)273 QStringList get_open_filenames(
274 QWidget* parent,
275 const QString& caption,
276 const QString& filter,
277 ParamArray& settings,
278 const QString& settings_key,
279 QFileDialog::Options options)
280 {
281 const QString dir = get_value(settings, settings_key + SETTINGS_LAST_DIRECTORY);
282 QString selected_filter = get_value(settings, settings_key + SETTINGS_SELECTED_FILTER);
283
284 const QStringList filepaths =
285 QFileDialog::getOpenFileNames(
286 parent,
287 caption,
288 dir,
289 filter,
290 &selected_filter,
291 options);
292
293 set_value(settings, settings_key + SETTINGS_SELECTED_FILTER, selected_filter);
294
295 if (filepaths.isEmpty())
296 return filepaths;
297
298 set_value(
299 settings,
300 settings_key + SETTINGS_LAST_DIRECTORY,
301 QDir::toNativeSeparators(QFileInfo(filepaths.first()).path()));
302
303 return filepaths;
304 }
305
get_save_filename(QWidget * parent,const QString & caption,const QString & filter,ParamArray & settings,const QString & settings_key,QFileDialog::Options options)306 QString get_save_filename(
307 QWidget* parent,
308 const QString& caption,
309 const QString& filter,
310 ParamArray& settings,
311 const QString& settings_key,
312 QFileDialog::Options options)
313 {
314 const QString dir = get_value(settings, settings_key + SETTINGS_LAST_DIRECTORY);
315 QString selected_filter = get_value(settings, settings_key + SETTINGS_SELECTED_FILTER);
316
317 QString filepath =
318 QFileDialog::getSaveFileName(
319 parent,
320 caption,
321 dir,
322 filter,
323 &selected_filter,
324 options);
325
326 set_value(settings, settings_key + SETTINGS_SELECTED_FILTER, selected_filter);
327
328 if (filepath.isEmpty())
329 return filepath;
330
331 QFileInfo file_info(filepath);
332
333 set_value(
334 settings,
335 settings_key + SETTINGS_LAST_DIRECTORY,
336 QDir::toNativeSeparators(file_info.path()));
337
338 // If the file name has no extension and the selected filter has a single extension
339 // (i.e. it contains one *.ext substring) then add that extension to the file name.
340 if (file_info.suffix().isEmpty() && selected_filter.count('*') == 1)
341 {
342 const int begin = selected_filter.indexOf("*") + 1;
343 const int end = selected_filter.indexOf(")", begin);
344 assert(begin > 0 && end > begin);
345 filepath += selected_filter.mid(begin, end - begin);
346 }
347
348 return filepath;
349 }
350
disable_osx_focus_rect(QWidget * widget)351 void disable_osx_focus_rect(QWidget* widget)
352 {
353 widget->setAttribute(Qt::WA_MacShowFocusRect, false);
354 }
355
set_minimum_width(QMessageBox & msgbox,const int minimum_width)356 void set_minimum_width(QMessageBox& msgbox, const int minimum_width)
357 {
358 QSpacerItem* spacer =
359 new QSpacerItem(
360 minimum_width,
361 0,
362 QSizePolicy::Minimum,
363 QSizePolicy::Expanding);
364
365 QGridLayout* layout = static_cast<QGridLayout*>(msgbox.layout());
366
367 layout->addItem(
368 spacer,
369 layout->rowCount(), // row
370 0, // column
371 1, // row span
372 layout->columnCount()); // column span
373 }
374
create_window_local_shortcut(QWidget * parent,const QKeySequence key_sequence)375 QShortcut* create_window_local_shortcut(QWidget* parent, const QKeySequence key_sequence)
376 {
377 return
378 new QShortcut(
379 QKeySequence(key_sequence),
380 parent,
381 nullptr,
382 nullptr,
383 Qt::WidgetWithChildrenShortcut);
384 }
385
clear_layout(QLayout * layout)386 void clear_layout(QLayout* layout)
387 {
388 for (int i = layout->count(); i > 0; --i)
389 {
390 QLayoutItem* item = layout->takeAt(0);
391
392 if (item->layout())
393 clear_layout(item->layout());
394 else item->widget()->deleteLater();
395
396 delete item;
397 }
398 }
399
400 } // namespace studio
401 } // namespace appleseed
402