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