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