1 /** @file baseguiapp.cpp Base class for GUI applications.
2 *
3 * @authors Copyright (c) 2014-2017 Jaakko Keränen <jaakko.keranen@iki.fi>
4 *
5 * @par License
6 * LGPL: http://www.gnu.org/licenses/lgpl.html
7 *
8 * <small>This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU Lesser General Public License as published by
10 * the Free Software Foundation; either version 3 of the License, or (at your
11 * option) any later version. This program is distributed in the hope that it
12 * will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
13 * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
14 * General Public License for more details. You should have received a copy of
15 * the GNU Lesser General Public License along with this program; if not, see:
16 * http://www.gnu.org/licenses</small>
17 */
18
19 #include "de/BaseGuiApp"
20 #include "de/VRConfig"
21
22 #include <de/ArrayValue>
23 #include <de/CommandLine>
24 #include <de/Config>
25 #include <de/ConstantRule>
26 #include <de/DictionaryValue>
27 #include <de/FileSystem>
28 #include <de/Function>
29 #include <de/NativeFont>
30 #include <de/BaseWindow>
31 #include <de/ScriptSystem>
32 #include <QFontDatabase>
33
34 #ifdef WIN32
35 # define CONST const
36 # include <d2d1.h>
37 #endif
38
39 namespace de {
40
Function_App_LoadFont(Context &,Function::ArgumentValues const & args)41 static Value *Function_App_LoadFont(Context &, Function::ArgumentValues const &args)
42 {
43 try
44 {
45 // Try to load the specific font.
46 Block data(App::rootFolder().locate<File const>(args.at(0)->asText()));
47 int id;
48 id = QFontDatabase::addApplicationFontFromData(data);
49 if (id < 0)
50 {
51 LOG_RES_WARNING("Failed to load font:");
52 }
53 else
54 {
55 LOG_RES_VERBOSE("Loaded font: %s") << args.at(0)->asText();
56 //qDebug() << args.at(0)->asText();
57 //qDebug() << "Families:" << QFontDatabase::applicationFontFamilies(id);
58 }
59 }
60 catch (Error const &er)
61 {
62 LOG_RES_WARNING("Failed to load font:\n") << er.asText();
63 }
64 return 0;
65 }
66
Function_App_AddFontMapping(Context &,Function::ArgumentValues const & args)67 static Value *Function_App_AddFontMapping(Context &, Function::ArgumentValues const &args)
68 {
69 // arg 0: family name
70 // arg 1: dictionary with [Text style, Number weight] => Text fontname
71
72 // styles: regular, italic
73 // weight: 0-99 (25=light, 50=normal, 75=bold)
74
75 NativeFont::StyleMapping mapping;
76
77 DictionaryValue const &dict = args.at(1)->as<DictionaryValue>();
78 DENG2_FOR_EACH_CONST(DictionaryValue::Elements, i, dict.elements())
79 {
80 NativeFont::Spec spec;
81 ArrayValue const &key = i->first.value->as<ArrayValue>();
82 if (key.at(0).asText() == "italic")
83 {
84 spec.style = NativeFont::Italic;
85 }
86 spec.weight = roundi(key.at(1).asNumber());
87 mapping.insert(spec, i->second->asText());
88 }
89
90 NativeFont::defineMapping(args.at(0)->asText(), mapping);
91
92 return 0;
93 }
94
DENG2_PIMPL(BaseGuiApp)95 DENG2_PIMPL(BaseGuiApp)
96 , DENG2_OBSERVES(Variable, Change)
97 {
98 Binder binder;
99 QScopedPointer<PersistentState> uiState;
100 GLShaderBank shaders;
101 WaveformBank waveforms;
102 VRConfig vr;
103 float windowPixelRatio = 1.0f; ///< Without user's Config.ui.scaleConfig
104 ConstantRule *pixelRatio = new ConstantRule;
105
106 Impl(Public *i) : Base(i)
107 {
108 #if defined(WIN32)
109 // Use the Direct2D API to find out the desktop pixel ratio.
110 ID2D1Factory *d2dFactory = nullptr;
111 HRESULT hr = D2D1CreateFactory(D2D1_FACTORY_TYPE_SINGLE_THREADED, &d2dFactory);
112 if (SUCCEEDED(hr))
113 {
114 FLOAT dpiX = 96;
115 FLOAT dpiY = 96;
116 d2dFactory->GetDesktopDpi(&dpiX, &dpiY);
117 windowPixelRatio = dpiX / 96.0;
118 d2dFactory->Release();
119 d2dFactory = nullptr;
120 }
121 #endif
122 }
123
124 ~Impl() override
125 {
126 releaseRef(pixelRatio);
127 }
128
129 void variableValueChanged(Variable &, const Value &) override
130 {
131 self().setPixelRatio(windowPixelRatio);
132 }
133 };
134
BaseGuiApp(int & argc,char ** argv)135 BaseGuiApp::BaseGuiApp(int &argc, char **argv)
136 : GuiApp(argc, argv), d(new Impl(this))
137 {
138 d->binder.init(scriptSystem()["App"])
139 << DENG2_FUNC (App_AddFontMapping, "addFontMapping", "family" << "mappings")
140 << DENG2_FUNC (App_LoadFont, "loadFont", "fileName");
141 }
142
glDeinit()143 void BaseGuiApp::glDeinit()
144 {
145 GLWindow::glActiveMain();
146
147 d->vr.oculusRift().deinit();
148 d->shaders.clear();
149 }
150
initSubsystems(SubsystemInitFlags flags)151 void BaseGuiApp::initSubsystems(SubsystemInitFlags flags)
152 {
153 GuiApp::initSubsystems(flags);
154
155 #if !defined(WIN32)
156 d->windowPixelRatio = float(devicePixelRatio());
157 #endif
158 // The "-dpi" option overrides the detected pixel ratio.
159 if (auto dpi = commandLine().check("-dpi", 1))
160 {
161 d->windowPixelRatio = dpi.params.at(0).toFloat();
162 }
163 setPixelRatio(d->windowPixelRatio);
164
165 Config::get("ui.scaleFactor").audienceForChange() += d;
166
167 d->uiState.reset(new PersistentState("UIState"));
168 }
169
pixelRatio() const170 const Rule &BaseGuiApp::pixelRatio() const
171 {
172 return *d->pixelRatio;
173 }
174
setPixelRatio(float pixelRatio)175 void BaseGuiApp::setPixelRatio(float pixelRatio)
176 {
177 d->windowPixelRatio = pixelRatio;
178
179 // Apply the overall UI scale factor.
180 pixelRatio *= config().getf("ui.scaleFactor", 1.0f);
181
182 if (!fequal(d->pixelRatio->value(), pixelRatio))
183 {
184 LOG_VERBOSE("Pixel ratio changed to %.1f") << pixelRatio;
185
186 d->pixelRatio->set(pixelRatio);
187 scriptSystem()["DisplayMode"].set("PIXEL_RATIO", Value::Number(pixelRatio));
188 }
189 }
190
app()191 BaseGuiApp &BaseGuiApp::app()
192 {
193 return static_cast<BaseGuiApp &>(App::app());
194 }
195
persistentUIState()196 PersistentState &BaseGuiApp::persistentUIState()
197 {
198 return *app().d->uiState;
199 }
200
shaders()201 GLShaderBank &BaseGuiApp::shaders()
202 {
203 return app().d->shaders;
204 }
205
waveforms()206 WaveformBank &BaseGuiApp::waveforms()
207 {
208 return app().d->waveforms;
209 }
210
vr()211 VRConfig &BaseGuiApp::vr()
212 {
213 return app().d->vr;
214 }
215
beginNativeUIMode()216 void BaseGuiApp::beginNativeUIMode()
217 {
218 #if !defined (DENG_MOBILE)
219 // Switch temporarily to windowed mode. Not needed on macOS because the display mode
220 // is never changed on that platform.
221 #if !defined (MACOSX)
222 {
223 auto &win = static_cast<BaseWindow &>(GLWindow::main());
224 win.saveState();
225 int const windowedMode[] = {
226 BaseWindow::Fullscreen, false,
227 BaseWindow::End
228 };
229 win.changeAttributes(windowedMode);
230 }
231 #endif
232 #endif
233 }
234
endNativeUIMode()235 void BaseGuiApp::endNativeUIMode()
236 {
237 #if !defined (DENG_MOBILE)
238 # if !defined (MACOSX)
239 {
240 static_cast<BaseWindow &>(GLWindow::main()).restoreState();
241 }
242 # endif
243 #endif
244 }
245
246
247 } // namespace de
248